两个 SQL LEFT JOINS 产生不正确的结果
Posted
技术标签:
【中文标题】两个 SQL LEFT JOINS 产生不正确的结果【英文标题】:Two SQL LEFT JOINS produce incorrect result 【发布时间】:2012-09-09 22:49:57 【问题描述】:我有 3 张桌子:
users(id, account_balance)
grocery(user_id, date, amount_paid)
fishmarket(user_id, date, amount_paid)
fishmarket
和 grocery
表都可能多次出现相同的 user_id,但支付的日期和金额不同,或者对于任何给定的用户都没有。
当我尝试以下查询时:
SELECT
t1."id" AS "User ID",
t1.account_balance AS "Account Balance",
count(t2.user_id) AS "# of grocery visits",
count(t3.user_id) AS "# of fishmarket visits"
FROM users t1
LEFT OUTER JOIN grocery t2 ON (t2.user_id=t1."id")
LEFT OUTER JOIN fishmarket t3 ON (t3.user_id=t1."id")
GROUP BY t1.account_balance,t1.id
ORDER BY t1.id
它会产生不正确的结果:"1", "12", "12"
。
但是,当我尝试 LEFT JOIN
到一张表时,它会为 grocery
或 fishmarket
访问产生正确的结果,即 "1", "3", "4"
。
我在这里做错了什么? 我正在使用 PostgreSQL 9.1。
【问题讨论】:
【参考方案1】:联接从左到右处理(除非括号另有说明)。如果你LEFT JOIN
(或者只是JOIN
,类似的效果)三个杂货给一个用户,你会得到 3 行(1 x 3)。如果您随后为同一用户加入 4 个鱼市,您将获得 12 (3 x 4) 行,乘以结果中的先前计数,而不是添加 em> 就像你希望的那样。
从而增加了杂货店和鱼市等的访问量。
你可以让它像这样工作:
SELECT u.id
, u.account_balance
, g.grocery_visits
, f.fishmarket_visits
FROM users u
LEFT JOIN (
SELECT user_id, count(*) AS grocery_visits
FROM grocery
GROUP BY user_id
) g ON g.user_id = u.id
LEFT JOIN (
SELECT user_id, count(*) AS fishmarket_visits
FROM fishmarket
GROUP BY user_id
) f ON f.user_id = u.id
ORDER BY u.id;
要为一个或几个用户获取聚合值,相关子查询like @Vince provided 就可以了。对于整个表或其中的主要部分,聚合 n 表并连接到结果一次会更有效。这样,我们在外部查询中也不需要另一个GROUP BY
。
grocery_visits
和 fishmarket_visits
是 NULL
用于在各自表格中没有任何相关条目的用户。如果您需要 0
代替(或任意数字),请在外部 SELECT
中使用 COALESCE
:
SELECT u.id
, u.account_balance
, COALESCE(g.grocery_visits , 0) AS grocery_visits
, COALESCE(f.fishmarket_visits, 0) AS fishmarket_visits
FROM ...
【讨论】:
@ErwinBrandstetter 我从你的帖子中学到了很多关于 Postgres 的知识。你有没有考虑写一本关于这个主题的书? @MarkMcKelvy:我有。也有几个offer。但后来我没有。无论哪种方式都帮助了很多人,感觉很好。毕竟它是一个开源的 RDBMS。 我已经被这个问题困扰了将近一个星期。这不是一个简单的谷歌搜索(加入具有两个一对多关系的主表)。这正是我所需要的。 我注意到这种方法的一件事——grocery_visits 或fishmarket_visits 将在没有时返回 null 而不是 0。这可能无关紧要,但就我而言,它确实如此。SELECT u.id, ..., (SELECT count(*) FROM grocery g WHERE g.user_id = u.id) as grocery_visits, ...
将返回 0,尽管我不确定性能影响。
@dadasign:我建议COALESCE
。考虑上面的附录。【参考方案2】:
这是因为当用户表加入到grocery 表时,有3 条记录匹配。然后这三个记录中的每一个都与 fishmarket 中的 4 个记录匹配,产生 12 个记录。您需要子查询来获取您要查找的内容。
【讨论】:
【参考方案3】:对于您的原始查询,如果您取出分组依据以查看预先分组的结果,您会看到为什么会创建您收到的计数。
也许以下使用子查询的查询会达到您的预期结果:
SELECT
t1."id" AS "User ID",
t1.account_balance AS "Account Balance",
(SELECT count(*) FROM grocery t2 ON (t2.user_id=t1."id")) AS "# of grocery visits",
(SELECT count(*) FROM fishmarket t3 ON (t3.user_id=t1."id")) AS "# of fishmarket visits"
FROM users t1
ORDER BY t1.id
【讨论】:
以上是关于两个 SQL LEFT JOINS 产生不正确的结果的主要内容,如果未能解决你的问题,请参考以下文章
用于在不同表上具有多个 LEFT OUTER JOINS 的 SQL 的 LINQ
SQL Server JOINS:SQL Server 中是不是默认关联“JOIN”语句“LEFT OUTER”? [复制]