2013-02-05 101 views
2

考慮以下4個表:左外連接兩層深在的Postgres導致笛卡爾積

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(不同的event_members.id)和event_members離開外部連接,那麼總和是正確的。

編輯:歐文指出我在正確的方向。查詢改寫爲:

 
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 
+0

順便說一句,改變總和(donations.amount)爲count(distinct donations.id)確實會導致正確的捐贈數量 –

+0

嗨,只需重新排序您的問題中的create table語句以反映join參數。 – biziclop

+1

密切相關:http://stackoverflow.com/questions/12464037/two-sql-left-joins-produce-incorrect-result –

回答

1

你似乎有這兩個獨立的結構(-[裝置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 
+0

警告:我的查詢可能不是語法正確的PostgreSQL查詢。 – biziclop

+0

這個工作投票的新方法把一個選擇語句加入一個連接。性能與我的解決方案几乎完全相同。對於後人,需要將member_count.the_member_count添加到組中 –

+0

您確定我的回答是更好的回答,而不是Erwin的回答嗎?隨時不接受我的回答:) – biziclop

1

當然,你得到的捐款和事件之間的笛卡爾積的每一個事件,因爲二者都只能綁定到事件,也沒有參加捐贈和event_members比事件ID等之間的關係,這當然意味着每個成員都會匹配每一筆捐款。

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的捐款總額。如果Alice捐贈100美元,然後你問:

Alpha Alice 100USD 
Beta Alice 100USD 

所以這並不奇怪,問了總和時總翹出來爲已經捐贈了200美元。

如果你想所有捐款的總和,你最好做兩個不同的查詢。試圖做一個查詢的一切,雖然有可能,將是一個經典的SQL Antipattern(實際上是一個在章#18,「意大利麪條查詢」):

意想不到的產品

一種常見的後果在一個查詢中產生所有的 結果是一個笛卡兒積。發生這種情況時,查詢中的兩個 表中沒有限制它們的 關係的條件。沒有這種限制,兩個表的連接會將第一個表中的每一行與第一個表中的每一行配對到另一個表中的每一行。每個這樣的 配對都會成爲結果集的一行,最終會有比您期望的更多的 行。

+0

不幸的是,我正在使用的報告系統必須從單個查詢中獲取所有結果。在列列表中嵌入子選擇的技巧是 –

4

正如我詳細under the referenced question解釋您需要首先彙總,然後加入表格以避免代理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 

IF event_members.id是主鍵(因爲有​​人可能會認爲),你因爲id是保證UNIQUE NOT NULL可以簡化到

COUNT(*) AS ct_members 

。這有點快。

+0

爲什麼在第一個子查詢中'GROUP BY'' event.id'和'event.name'? 'event.name'可能取決於'event.id'。 – biziclop

+1

@biziclop:因爲我可能不得不。每個「SELECT」項必須位於「GROUP BY」列表中或用於聚合函數中。自PostgreSQL 9.1以來,表的主鍵覆蓋了該表的所有列,但從它的*主鍵和我們運行Postgres 9.1+的問題來看,並不清楚。 –

+0

對於我的樣本結果集(2個事件,其他表格中的每個200個),所有解決方案都取對數+ 3ms。我確實喜歡這個解決方案的結構 –