在具有条件的多对多表中执行左连接

时间:2010-10-01 17:40:12

标签: sql mysql

一般情况

如果要在关系的外方添加条件,如何在多对多关系中执行左连接?

具体案例

我们正在处理两个表: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查询。

尝试解决方案

  • 使用INNER JOIN而不是从teamteam_poolteam_poolpool的LEFT JOIN。问题:我们失去了不属于游泳池的团队
  • 使用WITH条件从team team_poolteam_pool左右加入@ poolstage_id pool与我们'匹配正在寻找。问题:当一个团队属于许多池时,我们会得到多个结果。添加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

5 个答案:

答案 0 :(得分:1)

简短回答,无法满足您的所有标准。

更长的答案:使用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

答案 1 :(得分: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

答案 2 :(得分:1)

如果您只想找一个舞台,可以将桌子连在一起。如果未找到池,则池名称将为null。例如,对于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

答案 3 :(得分:0)

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

更新 的 简化了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

答案 4 :(得分:0)

我们很清楚:

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

您想要返回: 所有团队在提供的阶段与您提供的团队共享任何池 所有在提供的阶段都没有游泳池的球队 在提供的舞台上,与您提供的团队共享任何游泳池的团队的所有游泳池/团队组合?

我很确定这可以在没有UNION的情况下完成,但我不完全清楚你的结果是什么

作为首发,IMO:

您的初始选择应该在球队表

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

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