两个 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)

fishmarketgrocery 表都可能多次出现相同的 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 到一张表时,它会为 groceryfishmarket 访问产生正确的结果,即 "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_visitsfishmarket_visitsNULL 用于在各自表格中没有任何相关条目的用户。如果您需要 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”? [复制]

Rails/MySQL:使用 LEFT JOINS 的 Group/Distinct 使查询时间加倍/性能降低

是不是真的可以到处使用JOINS来代替SQL中的子查询

sql joins

带有两个 INNER JOINS 的 SQL 查询抛出 HAVING COUNT