2012-09-28 40 views
2

在文章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

这里的[一些测试数据(https://gist.github.com/5dee7db1088287f7c5a9)如果你想玩耍。 – Snowball

回答

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的行为VS标准SQL中的一些讨论是Debunking GROUP BY myths

0

也许该文章的作者是错误的。您的解决方案也可以运行,而且速度可能会更快。

就我个人而言,我将全部删除if。如果要计算图片的数量,那么在0而不是null的结果中显示为“无图片”是有意义的。

+0

我同意删除'if',但我保留它以匹配原始查询的行为。 – Snowball

0

作为替代方案,你也可以写一个相关子查询:

SELECT u.*, (SELECT Count(*) FROM photos p WHERE p.userid=u.id) as cnt 
FROM users u 
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 tornambe建议。