单个查询中的多个 array_agg() 调用

Posted

技术标签:

【中文标题】单个查询中的多个 array_agg() 调用【英文标题】:Multiple array_agg() calls in a single query 【发布时间】:2015-02-21 17:10:38 【问题描述】:

我试图用我的查询来完成一些事情,但它并没有真正起作用。我的应用程序曾经有一个 mongo db,因此该应用程序用于获取字段中的数组,现在我们不得不更改为 Postgres,我不想更改我的应用程序代码以保持 v1 正常工作。

为了在 Postgres 中的 1 个字段中获取数组,我使用了 array_agg() 函数。到目前为止,这工作得很好。但是,我现在需要另一个不同表的字段中的另一个数组。

例如:

我有我的员工。员工有多个地址和多个工作日。

SELECT name, age, array_agg(ad.street) FROM employees e 
JOIN address ad ON e.id = ad.employeeid
GROUP BY name, age

现在这对我来说很好,这将导致例如:

| name  | age| array_agg(ad.street)
| peter | 25 | 1st street, 2nd street|

现在我想在工作日加入另一张桌子,所以我这样做了:

SELECT name, age, array_agg(ad.street), arrag_agg(wd.day) FROM employees e 
JOIN address ad ON e.id = ad.employeeid 
JOIN workingdays wd ON e.id = wd.employeeid
GROUP BY name, age

这会导致:

| peter | 25 | 1st street, 1st street, 1st street, 1st street, 1st street, 2nd street, 2nd street, 2nd street, 2nd street, 2nd street| "Monday,Tuesday,Wednesday,Thursday,Friday,Monday,Tuesday,Wednesday,Thursday,Friday

但我需要它来产生结果:

| peter | 25 | 1st street, 2nd street| Monday,Tuesday,Wednesday,Thursday,Friday

我知道这与我的联接有关,因为多个联接行多个但我不知道如何完成此操作,谁能给我正确的提示?

【问题讨论】:

【参考方案1】:

DISTINCT 通常用于修复从内部腐烂的查询,这通常很慢和/或不正确。开始时不要将行相乘,这样您就不必在最后整理出不需要的重复项。

同时加入多个 n 表(“有很多”)会使结果集中的行数相乘。这就像 CROSS JOIN 或 Cartesian product 通过代理

Two SQL LEFT JOINS produce incorrect result

有多种方法可以避免这个错误。

先聚合,后加入

从技术上讲,只要您在聚合之前一次加入 一个 包含多行的表,查询就会起作用:

SELECT e.id, e.name, e.age, e.streets, arrag_agg(wd.day) AS days
FROM  (
   SELECT e.id, e.name, e.age, array_agg(ad.street) AS streets
   FROM   employees e 
   JOIN   address  ad ON ad.employeeid = e.id
   GROUP  BY e.id    -- id enough if it is defined PK
   ) e
JOIN   workingdays wd ON wd.employeeid = e.id
GROUP  BY e.id, e.name, e.age;

最好也包括主键idGROUP BY 它,因为nameage 不一定是唯一的。您可能会错误地合并两名员工。

但是您可以在加入之前在子查询中进行聚合,除非您对employees 有选择性的WHERE 条件,否则这会更好:

SELECT e.id, e.name, e.age, ad.streets, arrag_agg(wd.day) AS days
FROM   employees e 
JOIN  (
   SELECT employeeid, array_agg(ad.street) AS streets
   FROM   address
   GROUP  BY 1
   ) ad ON ad.employeeid = e.id
JOIN   workingdays wd ON e.id = wd.employeeid
GROUP  BY e.id, e.name, e.age, ad.streets;

或两者兼而有之:

SELECT name, age, ad.streets, wd.days
FROM   employees e 
JOIN  (
   SELECT employeeid, array_agg(ad.street) AS streets
   FROM   address
   GROUP  BY 1
   ) ad ON ad.employeeid = e.id
JOIN  (
   SELECT employeeid, arrag_agg(wd.day) AS days
   FROM   workingdays
   GROUP  BY 1
   ) wd ON wd.employeeid = e.id;

如果您检索基表中的所有或大部分行,最后一个通常更快

请注意,使用 JOIN 而不是 LEFT JOIN 会从结果中删除没有地址没有工作日的员工。这可能是也可能不是有意的。切换到LEFT JOIN 以保留结果中的所有 员工。

相关子查询/横向连接

对于少量选择,我会考虑相关子查询:

SELECT name, age
    , (SELECT array_agg(street) FROM address WHERE employeeid = e.id) AS streets
    , (SELECT arrag_agg(day) FROM workingdays WHERE employeeid = e.id) AS days
FROM   employees e
WHERE  e.namer = 'peter';  -- very selective

或者,对于 Postgres 9.3 或更高版本,您可以为此使用 LATERAL 连接:

SELECT e.name, e.age, a.streets, w.days
FROM   employees e
LEFT   JOIN LATERAL (
   SELECT array_agg(street) AS streets
   FROM   address
   WHERE  employeeid = e.id
   GROUP  BY 1
   ) a ON true
LEFT   JOIN LATERAL (
   SELECT array_agg(day) AS days
   FROM   workingdays
   WHERE  employeeid = e.id
   GROUP  BY 1
   ) w ON true
WHERE  e.name = 'peter';  -- very selective
What is the difference between LATERAL and a subquery in PostgreSQL?

任一查询都会在结果中保留所有名员工。

【讨论】:

您好,谢谢,非常清楚的解释。谢谢你,我可以继续 :)【参考方案2】:

当您需要不重复的值时,请使用 DISTINCT,如下所示:

SELECT name, age, array_agg(DISTINCT ad.street), array_agg(DISTINCT wd.day) FROM employees e 
JOIN address ad ON e.id = ad.employeeid 
JOIN workingdays wd ON e.id = wd.employeeid
GROUP BY name, age

【讨论】:

谢谢,这是真的,在我的例子中它会起作用,但在我的例子中,有时值可能是相同的。在我的情况下,它实际上是一个产品的状态,它可以是 IN_USE 或 FREE,所以如果我使用 distinct 我只会得到 1 个值而不是我需要的两个值,这两种产品可能都是免费的。 (而且对于第三次加入,我也可以有重复) 这是不正确的一种昂贵的方式,不要使用它。

以上是关于单个查询中的多个 array_agg() 调用的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL -- ARRAY_AGG聚合函数

PostgreSQL -- ARRAY_AGG聚合函数

Android SQLite 性能:单个查询中的多个选择与多个查询中的单个选择?

多个 ARRAY_AGG 是不是为所有这些保留相同的顺序?

单个 MongoDB 查询中的多个操作

单个 jasper 文档中的多个查询