【问题标题】:Aggregate functions on multiple joined tables多个连接表上的聚合函数
【发布时间】:2013-02-22 01:19:12
【问题描述】:

我有三张桌子:

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() 函数时遇到了问题,但我不确定是否是这种情况或如何解决它。

【问题讨论】:

    标签: sql postgresql left-join aggregate-functions cross-join


    【解决方案1】:
    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;
    

    产生所需的结果。

    • 使用显式 JOIN 语法重写。使其更易于阅读和理解(和调试)。

    • 通过加入多个1:n 相关表,行将相互相乘产生Cartesian product - 这是非常昂贵的废话。这是一个无意的CROSS JOIN 代理。相关:

    • 为避免这种情况,在聚合 (GROUP BY) 之前,最多将 n-table 加入 1-table。您可以聚合两次,但将n-table 单独聚合将它们加入1-table 之前会更加简洁和快速。

    • 与您的原始版本相反(带有隐式 INNER JOIN)。我使用 LEFT JOIN 来避免从 foo 丢失在 foo_bartag 中没有匹配行的行。

    • 一旦从查询中删除了意外的CROSS JOIN,就无需再添加DISTINCT - 假设foo.id 是唯一的。

    【讨论】:

    • 感谢详细解释!
    • @Bill:即使对于一百万行,这也应该非常快。但是为什么要猜测你是否可以测试呢?使用 100k 行填充您的表并使用 EXPLAIN ANALYZE 运行查询。你可以找到一个例子how to build a a test with generate_series() easily here。关于 SO 的内容还有很多。还要考虑一下我的答案。
    • 感谢您的介绍,这正是我感兴趣的。我肯定会进行测试,但目前没有可用的生产硬件,并且在我的 VM 中进行测试不会产生有用的结果。我只需要知道这是否是一个明显可怕的想法,我应该立即纠正。再次感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-07
    • 1970-01-01
    • 2019-06-12
    相关资源
    最近更新 更多