两个SQL LEFT JOINS产生不正确的结果

Rya*_*ick 29 sql postgresql aggregate-functions left-join

我有3张桌子:

users(id, account_balance)
grocery(user_id, date, amount_paid)
fishmarket(user_id, date, amount_paid)
Run Code Online (Sandbox Code Playgroud)

对于具有不同日期和金额的相同user_id,两个fishmarketgrocery表可能有多次出现,或者对于任何给定用户都没有任何内容.当我尝试以下查询时:

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
Run Code Online (Sandbox Code Playgroud)

它会产生不正确的结果:"1", "12", "12".
但是,当我尝试LEFT JOIN只有一个表时,它会产生正确的结果,无论是访问grocery还是fishmarket访问"1", "3", "4".

我在这做错了什么?
我正在使用PostgreSQL 9.1.

Erw*_*ter 48

连接从左到右处理(除非括号另有规定).如果你LEFT JOIN(或者只是JOIN,类似的效果)三个杂货给一个用户你得到3行(1 x 3).如果您为同一个用户加入4个鱼市场,您将得到12(3 x 4)行,将结果中的先前计数相乘,而不是像您希望的那样添加它.
从而使杂货和鱼市的访问次数相乘.

它应该像这样工作:

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;
Run Code Online (Sandbox Code Playgroud)

要查找一个或几个用户的聚合值,像@Vince提供的相关子查询 就可以了.对于整个表或其主要部分,聚合n表并连接到结果一次(更高效).这样,我们在外部查询中也不需要另一个.GROUP BY

  • @MarkMcKelvy:我有。也收到了几个offer。但后来我没有。无论哪种方式都帮助了很多人,感觉很好。毕竟它是一个开源的 RDBMS。 (3认同)
  • @ErwinBrandstetter 我从你的帖子中学到了很多关于 Postgres 的知识。你有没有想过写一本关于这个主题的书? (2认同)
  • 关于这种方法,我注意到一件事 -grocery_visits 或 Fishmarket_visits 在没有时将返回 null 而不是 0。这可能并不重要,但就我而言却很重要。`SELECT u.id, ..., (SELECT count(*) FROMgrocery g WHERE g.user_id = u.id) asgrocery_visits, ...` 将返回 0,尽管我不确定性能影响。 (2认同)
  • @dadasign:我建议“合并”。考虑上面的附录。 (2认同)

小智 8

对于您的原始查询,如果您带走该组以查看预先分组的结果,您将看到为什么创建了您接收的计数.

使用子查询的以下查询可能会实现您的预​​期结果:

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
Run Code Online (Sandbox Code Playgroud)