【问题标题】:LEFT JOIN across three tables (with junction table)跨三个表的 LEFT JOIN(带联结表)
【发布时间】:2015-02-23 23:06:26
【问题描述】:

在 Postgres 中,有没有办法在由联结表链接的表之间执行left join,并对链接表进行一些过滤?

假设我有两个表,humanspets,我想执行一个查询,其中包含人类 ID 和宠物名称。如果人类 ID 存在,但他们没有同名的宠物,我仍然希望返回人类的行。

如果我的 FK 关系从 petshumans,这将起作用:

select h.*, p.*
from humans as h
left join pets as p on p.human_id = h.id and p.name = 'fluffy'
where h.id = 13

我会得到一行人类 13 的详细信息和蓬松的价值观。此外,如果人类 13 没有名为“蓬松”的宠物,我将得到一行包含人类 13 的值,而宠物列的值为空。

但是,我没有直接的 FK 关系,我在 humanspets 之间有一个联结表,所以我正在尝试这样的查询:

select h.*, p.*
from humans as h
left join humans_pets_junction as j on j.human_id = h.id
left join pets as p on j.pet_id = p.id and p.name = 'fluffy'
where h.id = 13

返回人类 13 的所有宠物的行,除了 fluffy 的行之外,其他列都是空的。

如果我将 p.name = 'fluffy' 添加到 WHERE 子句中,这会过滤掉所有空行,但也意味着如果人类 13 根本没有名为 fluffy 的宠物,我会得到 0 行。

有没有办法复制 FK 样式 left join 的行为,但与联结表一起使用时?

【问题讨论】:

  • 您的意思是第二个查询中的select h.*, p.*,对吧?
  • 我冒昧地修正了假定的错字。

标签: sql database postgresql many-to-many left-join


【解决方案1】:

一种方法是在where 子句中进行比较:

select h.*, p.*
from humans as h left join
     humans_pets_junction as j
     on j.human_id = h.id left join
     pets as p
     on j.pet_id = p.id and p.name = 'fluffy'
where h.id = 13 and (p.name = 'fluffy' or p.id is null);

或者,将联结表和宠物表连接为子查询或 CTE:

select h.*, p.*
from humans h left join
     (select j.*
      from humans_pets_junction j join
           pets p
           on j.pet_id = p.id and p.name = 'fluffy'
     ) pj
     on pj.human_id = h.id
where h.id = 13;

【讨论】:

  • 第一个查询有一个内置陷阱,用于设置pets.name 可以为空。表达式p.name is null after LEFT JOIN 无法区分人类没有关联宠物的情况和宠物名称缺失(空)的情况。使用.. or p.id is null) 修复。没有缺点。此外,这两个查询都从问题中复制了无效的引用 r.*
  • @ErwinBrandstetter 。 . .确实如此。我把它改成了id
  • 感谢您的回答。我更喜欢第一种形式,但我似乎无法让它的行为类似于我上面的外键示例(也就是说,如果人类 13 没有名为“蓬松”的宠物,现在返回行,而不是一行包含人类列,没有宠物列)。不过,子查询表单有效。
  • 错误,应该是“没有行被返回”。我希望 SO 不会有这样愚蠢的限制,比如五分钟后无法编辑 cmets。
【解决方案2】:

在 Postgres 中,您可以使用括号来确定 JOIN 顺序的优先级。您不需要子查询:

SELECT h.*, p.id AS p_id, p.name AS pet_name
FROM   humans  h
LEFT   JOIN (pets p
       JOIN  humans_pets_junction j ON p.name = 'fluffy'
                                   AND j.pet_id = p.id
                                   AND j.human_id = 13) ON TRUE
WHERE  h.id = 13;

Per documentation:

可以在JOIN 子句周围使用括号来控制连接顺序。 在没有括号的情况下,JOIN 子句从左到右嵌套。

我将谓词j.human_id = 13 添加到您的联结表和宠物之间的联接中,以尽早消除不相关的行。外层LEFT JOIN只需要虚拟条件ON TRUE

SQL Fiddle.

旁白 1:我假设您知道您有一个 n:m(多对多)关系的教科书实现?

旁白 2:示例中不幸的命名约定使得有必要处理列别名。不要在实际表中使用“id”和“name”作为列名,以避免此类冲突。使用“pet_id”、“human_id”等专有名称。

【讨论】:

    猜你喜欢
    • 2016-03-15
    • 1970-01-01
    • 2021-11-06
    • 1970-01-01
    • 1970-01-01
    • 2013-04-19
    • 1970-01-01
    • 1970-01-01
    • 2021-04-01
    相关资源
    最近更新 更多