给出以下4个表:
CREATE TABLE events ( id, name )
CREATE TABLE profiles ( id, event_id )
CREATE TABLE donations ( amount, profile_id )
CREATE TABLE event_members( id, event_id, user_id )
我试图获得所有活动的清单,以及任何成员的数量,以及任何捐赠的总和 . 问题是捐款的总和是错误的(似乎是捐赠的笛卡尔结果 event_members) .
这是SQL查询(Postgres)
SELECT events.name, COUNT(DISTINCT event_members.id), SUM(donations.amount)
FROM events
LEFT OUTER JOIN profiles ON events.id = profiles.event_id
LEFT OUTER JOIN donations ON donations.profile_id = profiles.id
LEFT OUTER JOIN event_members ON event_members.event_id = events.id
GROUP BY events.name
总和(donations.amount)将返回=实际的捐款总额* event_members中的行数 . 如果我注释掉count(distinct event_members.id)和event_members left outer join,则总和是正确的 .
编辑:欧文指出我正确的方向 . 查询重写为:
SELECT events.name, COUNT(DISTINCT event_members.id),
select(SUM(donations.amount) from donations,profiles where donations.profile_id = profiles.id and profiles.event_id = events.id) as total_donations
FROM events
LEFT OUTER JOIN event_members ON event_members.event_id = events.id
GROUP BY events.name
4 回答
当然,每次活动都会在捐赠和活动之间获得笛卡尔产品,因为两者都只与事件有关,除了事件ID之外,捐赠和event_members之间没有联系关系,这当然意味着每个成员都匹配每一个捐赠 .
正如我详细解释under the referenced question,您需要首先聚合,然后加入表以避免代理
CROSS JOIN
. 像这样:IF
event_members.id
是主键(可以假设),您可以简化为因为
id
保证是UNIQUE NOT NULL
. 那要快一点 .你似乎有这两个独立的结构(
-[
表示1-N
关联):我将第二个包装到子查询中:
当你进行查询时,你要求所有事件 - 让's say there are two, event Alpha and event Beta - and then JOIN with the members. Let'说有一个成员Alice参与这两个事件 .
在每一行中,您询问了Alice的捐款总额 . 如果爱丽丝捐赠100美元,那么您要求:
因此,当要求 sum total 爱丽丝出来捐赠200美元时,这并不奇怪 .
如果您想要所有捐款的总和,您最好使用两个不同的查询 . 尝试使用单个查询尽可能地执行所有操作,这将是一个经典的SQL Antipattern(实际上是第18章中的一个,"Spaghetti Query"):