在文章 Why Arel? 中,作者提出了问题:
假设我们有一个用户表和一张照片表,我们想要选择他们创建的照片的所有用户数据和*计数*。
他建议的解决方案(添加换行符)是
SELECT users.*, photos_aggregation.cnt
FROM users
LEFT OUTER JOIN (SELECT user_id, count(*) as cnt FROM photos GROUP BY user_id)
AS photos_aggregation
ON photos_aggregation.user_id = users.id
当我尝试编写这样的查询时,我想出了
select users.*, if(count(photos.id) = 0, null, count(photos.id)) as cnt
from users
left join photos on photos.user_id = users.id
group by users.id
(列列表中的if()
只是为了让用户在没有照片时表现相同。)
文章的作者接着说
只有高级SQL程序员才知道如何写这个(我经常在求职面试中问过这个问题,我从来没有见过任何人做对了)。它应该不难!
我不认为自己是“高级SQL程序员”,所以我认为我错过了一些微妙的东西。我错过了什么?
答案 0 :(得分:2)
我相信你的版本会产生错误,至少在某些数据库引擎中是这样。在MSSQL中,您的选择将生成[Column Name] is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
。这是因为您选择只能包含组中的值或计数。
您可以将您的版本修改为select users.id, count(photo.id)
,但这样可行,但与查询结果不一样。
我不会说你必须特别先进才能提出一个有效的解决方案(或者他提出的具体解决方案)但是有必要在联接中作为一个单独的查询来执行该组或者作为@ron托纳姆建议。
答案 1 :(得分:1)
在大多数DBMS中(MySQL和Postgres都是例外),您问题中的版本将无效。
您需要编写不使用派生表的查询
select users.*, CASE WHEN count(photos.id) > 0 THEN count(photos.id) END as cnt
from users
left join photos on photos.user_id = users.id
group by users.id, users.name, users.email /* and so on*/
MySQL允许您选择不在group by
列表中的非聚合项目,但只有在功能上依赖于group by
中的列时,这才是安全的。
虽然group by
列表在没有派生表的情况下更详细,但我希望大多数优化器能够将其中一个转换为另一个。当然,在SQL Server中,如果它看到你正在通过PK和其他一些列进行分组,那么它实际上并不会通过对这些其他列的比较进行分组。
有关此MySQL行为与标准SQL的讨论在Debunking GROUP BY myths
中答案 2 :(得分:0)
也许这篇文章的作者是错的。您的解决方案也可以正常运行,而且速度可能会更快。
就个人而言,我会全部放弃if
。如果您想计算图片数量,那么“无图片”会导致0
而不是null
。
答案 3 :(得分:0)
作为替代方案,您还可以编写相关的子查询:
SELECT u.*, (SELECT Count(*) FROM photos p WHERE p.userid=u.id) as cnt
FROM users u