5

给定以下 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 )

我正在尝试获取所有事件的列表,以及任何成员的数量以及任何捐赠的总和。问题是捐赠的总和返回错误(似乎是捐赠的笛卡尔结果 * # of 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

sum(donations.amount) 返回 = 实际捐款总和 * event_members 中的行数。如果我注释掉 count(distinct event_members.id) 和 event_members 左外连接,则总和是正确的。

编辑:欧文为我指出了正确的方向。查询重写为:

选择 events.name, COUNT(DISTINCT event_members.id),
  从捐赠中选择(SUM(donations.amount),其中donations.profile_id = profiles.id 和profiles.event_id = events.id 的个人资料)作为total_donations
    从事件
    左外连接 event_members ON event_members.event_id = events.id
    GROUP BY events.name
4

4 回答 4

5

正如我在引用的问题下详细解释的那样,您需要先聚合,然后加入表以避免代理CROSS JOIN。像这样:

SELECT e.name, e.sum_donations, m.ct_members
FROM (
    SELECT e.id, e.name, SUM(d.amount) AS sum_donations
    FROM   events             e
    LEFT   JOIN profiles      p ON p.event_id = e.id
    LEFT   JOIN donations     d ON d.profile_id = p.id
    GROUP  BY 1, 2
    ) e
LEFT   JOIN (
    SELECT event_id, COUNT(DISTINCT id) AS ct_members
    FROM   event_members
    GROUP  BY 1
    ) m ON m.event_id = e.id

IFevent_members.id是主键(正如人们可能假设的那样),您可以简化为

COUNT(*) AS ct_members

since id is guaranteed to be UNIQUE NOT NULL. That's a little faster.

于 2013-02-05T07:48:17.943 回答
2

您似乎有这两个独立的结构(-[意味着1-N关联):

events -[ profiles -[ donations
events -[ event members

我将第二个包装到子查询中:

SELECT events.name,
  member_count.the_member_count
  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 (
  SELECT
    event_id,
    COUNT(*) AS the_member_count
  FROM event_members
  GROUP BY event_id
) AS member_count
  ON member_count.event_id = events.id

GROUP BY events.name
于 2013-02-05T07:44:38.230 回答
1

当然,您会在每个事件的捐赠和事件之间得到一个笛卡尔积,因为两者都只绑定到事件,除了事件 ID 之外,捐赠和 event_members 之间没有连接关系,这当然意味着每个成员都匹配每个捐赠。

于 2013-02-05T07:42:23.287 回答
0

当您进行查询时,您会询问所有事件- 假设有两个事件,事件 Alpha 和事件 Beta - 然后与成员一起加入。假设有一个成员 Alice 参与了这两个事件。

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

在每一行上,您都询问爱丽丝的捐款总额。如果 Alice 捐赠了 100 美元,那么您要求:

Alpha  Alice  100USD
Beta   Alice  100USD

所以毫不奇怪,当询问总额时, Alice 捐赠了 200 美元。

如果您想要所有捐赠的总和,您最好使用两个不同的查询。尝试使用单个查询完成所有事情,虽然可能,但将是经典的SQL 反模式(实际上是第 18 章“意大利面条查询”中的那个):

非预期产品

在一个查询中生成所有结果的一个常见结果是笛卡尔积。当查询中的两个表没有限制它们的关系的条件时,就会发生这种情况。如果没有这样的限制,两个表的连接会将第一个表中的每一行与另一个表中的每一行配对。每个这样的配对都成为结果集中的一行,最终得到的行数比您预期的要多得多。

于 2013-02-05T07:42:59.233 回答