我有三张桌子:
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 BY
和SELECT
子句,这些子句返回不同的结果,但不是我正在寻找的结果。我认为我在使用array_agg()
函数时遇到了问题,但我不确定是不是这种情况或如何解决它。
答案 0 :(得分:8)
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
)之前,最多将一个 1
- 表加入GROUP BY
- 表。您可以聚合两次,但在将加入n
- 表之前,将所有1
- 表单独聚合更清晰,更快捷。
与原始版本(隐式INNER JOIN
)相对。我使用 LEFT JOIN
来避免丢失foo
或foo_bar
中没有匹配行的tag
行。
从查询中删除非预期的CROSS JOIN
后,您无需再添加DISTINCT
- 假设foo.id
是唯一的,即使您没有澄清这一点。