【问题标题】:Performing a left join across a many-to-many table with conditions使用条件在多对多表中执行左连接
【发布时间】:2010-10-01 17:40:12
【问题描述】:

一般情况

当您想向关系的外部添加条件时,如何跨多对多关系执行左连接?

具体案例

我们正在处理两个表:teampool。还有一个team_pool 表作为它们之间的多对多连接表。此外,pool 有一个 stage_id 列。

我想检索具有该团队池的特定阶段的所有团队。如果池不存在,我希望池为 NULL。示例,理想化的结果:

+--------+----------+------+
| team   | stage_id | pool |
+--------+----------+------+
| Team 1 |        2 | C    |
| Team 2 |     NULL | NULL | //this team doesn't belong to a pool for this stage (but it could belong to a pool for a different stage)
| Team 3 |        2 | G    |
| Team 3 |        2 | H    | //if a team belongs to a 2 pools for the same stage
| Team 4 |        2 | D    |

如果相关,我正在使用 MySQL。

SQL

这是用于创建表的(简化的)SQL:

CREATE TABLE team (id BIGINT AUTO_INCREMENT, name VARCHAR(50), PRIMARY KEY(id));
CREATE TABLE team_pool (team_id BIGINT, pool_id BIGINT, PRIMARY KEY(team_id, pool_id));
CREATE TABLE pool (id BIGINT AUTO_INCREMENT, stage_id BIGINT, name VARCHAR(40) NOT NULL, PRIMARY KEY(id));

理想的解决方案

理想的解决方案是:

  • 不需要更改我的架构(ORM 真的希望这样)
  • 需要一个非 UNION 查询。

尝试的解决方案

  • 使用从teamteam_poolteam_poolpool 的INNER JOIN 而不是LEFT JOIN。问题:我们失去了不属于某个池的团队
  • LEFT JOIN 从teamteam_poolteam_poolpool,使用pool 上的stage_id 匹配我们正在寻找的条件。问题:当一个团队属于多个池时,我们会得到多个结果。添加 GROUP BY 没有帮助。

编辑:选择的解决方案

这里有很多好的解决方案。

鉴于我的理想解决方案是不可能的,我宁愿将 stage_id 添加到 team_pool 而不是使用 UNION 或子查询。这有一个额外的好处,就是让我强制一个团队每个阶段只能属于一个池。它还使查询变得简单:

SELECT t.name, p.name, tp.stage_id FROM team t LEFT JOIN team_pool tp ON t.id = tp.team_id AND tp.stage_id = 2 LEFT JOIN pool p ON p.id = tp.pool_id

【问题讨论】:

  • 如果 pool 为空,Team 2 如何拥有 stage_id?
  • 如果一个团队属于多个池,您能否添加到您的理想化结果中以显示您的期望?

标签: sql mysql


【解决方案1】:

如果我了解您的架构背后的概念,那么我认为stage_id 应该是team_pool 中的一列,而不是pool。阶段不是池的属性,它是团队映射到池的一个因素,对吧?

无论如何,这就是我在 Oracle 中编写查询的方式。我不确定这个确切的语法是否适合 MySQL。大概你想参数化 stage_id 的文字值。

SELECT t.name, p.name
  FROM (SELECT team.name, pool_id
          FROM team LEFT JOIN team_pool
            ON team_pool.team_id = team.team_id ) t
       LEFT JOIN (SELECT pool_id, name FROM pool WHERE stage_id = 2) p
            ON p.pool_id = t.pool_id

【讨论】:

  • 阶段是池的一个属性(池存在于某个阶段)。如果在 UNION 和将 stage_id 添加到 team_pool 之间进行选择,我会选择后者。我避免将 stage_id 添加到 team_pool 的主要原因是因为我使用的 ORM 让这样做有点烦人。
【解决方案2】:

简短的回答,不可能满足您的所有条件。

更长的答案:使用 UNION 很可能获得所有结果。上半场将展示在该阶段确实有参赛资格的球队;第二个将显示没有的团队。

select ...
from team
left join team_pool 
   on team.id = team_pool.team_id
left-join pool 
   on pool.id = team_pool.pool_id 
      and pool.stage_id = @stage
UNION
select distinct team.id, @stage, NULL
from team
where not exists 
   (select pool_id 
      from pool
      inner join team_pool
         on team_pool.pool_id = pool.pool_id 
      where team_pool.team_id = team.id 
         and pool.stage_id = @stage

【讨论】:

    【解决方案3】:

    如果您只寻找一个阶段,您可以将表格左连接在一起。如果未找到池,则池名称将为空。例如,对于stage = 2

    select  t.name, 2, pool.name
    from    team t
    left join
            (
            select  team_id, p.name
            from    team_pool tp
            join    pool p
            on      tp.pool_id = p.id
            where   p.stage_id = 2
            ) pool
    on      pool.team_id = t.id
    

    如果您想查询所有阶段,可以使用cross join 为每个团队阶段组合生成一行。然后左连接为每一行搜索一个池:

    select  t.name, s.stage_id, p.name
    from    team t
    cross join
            (
            select  distinct stage_id
            from    pool
            ) s
    left join
            (
            select  tp.team_id, p.stage_id, p.name
            from    team_pool tp
            join    pool p
            on      tp.pool_id = p.id
            ) pool
    on      pool.team_id = t.id
            and pool.stage_id = s.stage_id
    

    因为stage_id来自交叉连接,所以如果没有找到池就不是null了。

    【讨论】:

      【解决方案4】:

      不知道这是否可行,因为我现在没有可以使用的数据库,但我认为这应该可行。基本上创建两个左连接“表”,然后再次左连接。如果它确实有效,我无法想象它会非常高效。

      更新 简化 SQL

      SELECT t.name, p.stage_id, p.pool_name
      FROM team t 
      LEFT JOIN 
        (SELECT pool.name as pool_name, pool.stage_id, team_pool.team_id as junc_team_id
          FROM pool LEFT JOIN team_pool
          ON team_pool.pool_id = pool.pool_id) p
      ON p.junc_team_id = t.team_id
      

      【讨论】:

      • SQL 总结:join pool 和 team_pool 像一个表一样,然后使用 team_id 离开 join team 到那个新的伪表。将 pool 加入 team_pool 可能会使用 INNER JOIN;这只是将 team_id 链接到 pool_id 的一种方法。一旦建立,那么您应该能够将 team.team_id 链接到任何 p.team_id 而没有重复(我认为)。
      【解决方案5】:

      让我们清楚:

      你想提供一个团队和一个舞台

      您想返回: 所有团队在提供的阶段与您提供的团队共享任何池 在提供的阶段中没有游泳池的所有团队 在提供的阶段与您提供的团队共享任何池的任何团队的所有池/团队组合?

      我很确定这可以在没有 UNION 的情况下完成,但我并不完全清楚你所追求的结果

      首先,IMO:

      您的初始选择应该在团队表上

      您应该在JOIN 子句而不是WHERE 子句中添加池条件

      一旦你有一个想要介绍所有团队池的阶段,你应该再次加入团队表

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-19
        • 2011-10-24
        • 2011-12-11
        • 1970-01-01
        相关资源
        最近更新 更多