2013-03-07 116 views
2

我有三個表:聚合函數連接表

CREATE TABLE foo (
    id bigint PRIMARY KEY, 
    name text NOT NULL 
); 

CREATE TABLE foo_bar (
    id bigint PRIMARY KEY, 
    foo_id bigint NOT NULL 
); 

CREATE TABLE tag (
    name text NOT NULL, 
    target_id bigint NOT NULL, 
    PRIMARY KEY (name, target_id) 
); 

我試圖創建一個視圖,這樣我得到的所有表foo的領域,項目的foo_bar其中foo.id = foo_bar.foo_id計數,以及所有標籤的文本數組,其中foo.id = tag.target_id。如果我們有:

INSERT INTO foo VALUES (1, 'one'); 
INSERT INTO foo VALUES (2, 'two'); 
INSERT INTO foo_bar VALUES (1, 1); 
INSERT INTO foo_bar VALUES (2, 1); 
INSERT INTO foo_bar VALUES (3, 2); 
INSERT INTO foo_bar VALUES (4, 1); 
INSERT INTO foo_bar VALUES (5, 2); 
INSERT INTO tag VALUES ('a', 1); 
INSERT INTO tag VALUES ('b', 1); 
INSERT INTO tag VALUES ('c', 2); 

結果應該返回:

foo.id | foo.name  | count  | array_agg 
-------------------------------------------------- 
1   | one   | 3   | {a, b} 
2         | two          | 2           | {c} 

這是我到目前爲止有:

SELECT DISTINCT f.id, f.name, COUNT(b.id), array_agg(t.name) 
FROM foo AS f, foo_bar AS b, tag AS t 
WHERE f.id = t.target_id AND f.id = b.foo_id 
GROUP BY f.id, b.id; 

這是我得到的結果(注意count是不正確的):

foo.id | foo.name  | count  | array_agg 
-------------------------------------------------- 
1   | one   | 2   | {a, b} 
2         | two          | 1           | {c} 

count始終是標記的計數,而不是不同的foo_bar值的計數。我試過重新排序/修改GROUP BYSELECT子句,它們會返回不同的結果,但不是我正在查找的結果。我認爲我在array_agg()函數中遇到了問題,但我不確定是否如此,或者如何解決它。

回答

7
SELECT f.id, f.name, b.fb_ct, t.tag_names 
FROM foo f 
LEFT JOIN (
    SELECT foo_id AS id, count(*) AS fb_ct 
    FROM foo_bar 
    GROUP BY 1 
    ) b USING (id) 
LEFT JOIN (
    SELECT target_id AS id, array_agg(name) AS tag_names 
    FROM tag 
    GROUP BY 1 
    ) t USING (id) 
ORDER BY f.id; 

產生所需的結果。

  • 用明確的ANSI JOIN語法重寫。使它更容易閱讀和理解(和調試)。

  • 通過加入多個1:n相關表格,行之間會相互乘以產生Cartesian product - 這是非常昂貴的廢話。這是代理人無意識的CROSS JOIN。閱讀詳細說明在此密切相關的答案:
    Two SQL LEFT JOINS produce incorrect result

  • 爲了避免這種情況,請加入最多一個n -table到1 - 表您彙總(GROUP BY)前。您可以彙總兩次,但在這裏彙總所有n-表中各個之前的您可以將它們加入到1表中。

  • 與您原來的相反(隱含INNER JOIN)。我使用LEFT JOIN以避免在foo_bartag中沒有匹配行的foo的行丟失。

  • 一旦從查詢中刪除意外CROSS JOIN,你有沒有必要添加任何DISTINCT更多 - 假設foo.id是獨一無二的,即使你沒有澄清。

+0

感謝您的詳細解釋! – Bill 2013-03-07 01:41:42

+0

@ Bill:這應該是非常快的,即使是一百萬行。但爲什麼猜測你是否可以測試?用100k行填充你的表,並用'EXPLAIN ANALYZE'運行查詢。你可以找到一個例子[如何使用'generate_series()'輕鬆地在這裏構建一個測試](http://stackoverflow.com/questions/15169410/how-do-you-do-date-math-that-ignores-the - 年/ 15179731#15179731)。 SO上還有更多。另外考慮我的答案的補充位。 – 2013-03-07 01:43:16

+0

感謝球場,這就是我的興趣所在。我一定會測試,但目前沒有生產硬件,我的虛擬機測試不會產生有用的結果。我只需要知道這是否是一個明顯可怕的想法,我應該立即糾正。再次感謝! – Bill 2013-03-07 01:48:32