【问题标题】:Aggregating all values not in the same group聚合不在同一组中的所有值
【发布时间】:2021-08-30 16:38:23
【问题描述】:
PostgreSQL中有没有办法取这个表:
| ID |
country |
name |
values |
| 1 |
USA |
John Smith |
{1,2,3} |
| 2 |
USA |
Jane Smith |
{0,1,3} |
| 3 |
USA |
Jane Doe |
{1,1,1} |
| 4 |
USA |
John Doe |
{0,2,4} |
并使用列 agg_values 从中生成此表:
| ID |
country |
name |
values |
agg_values |
| 1 |
USA |
John Smith |
{1,2,3} |
{0,1,3,1,1,1,0,2,4} |
| 2 |
USA |
Jane Smith |
{0,1,3} |
{1,2,3,1,1,1,0,2,4} |
| 3 |
USA |
Jane Doe |
{1,1,1} |
{1,2,3,0,1,3,0,2,4} |
| 4 |
USA |
John Doe |
{0,2,4} |
{1,2,3,0,1,3,1,1,1} |
每行聚合所有values,但当前行及其对等行除外。
所以如果name = John Smith 那么agg_values = aggregate of all values where name not = John Smith。这可能吗?
【问题讨论】:
标签:
sql
postgresql
aggregate-functions
window-functions
【解决方案1】:
在 Postgres 11 或更高版本中,使用 window function with a custom frame and a frame_exclusion:
SELECT *, array_combine(values) OVER (ROWS BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING
EXCLUDE CURRENT ROW) AS agg_values
FROM tbl;
如果name 不是UNIQUE,并且既然你问了:
name 不为 John Smith 的所有值
SELECT *, array_combine(values) OVER (ORDER BY name
ROWS BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING
EXCLUDE GROUP) AS agg_values
FROM tbl;
db小提琴here
第一个(也)适用于任意顺序的行,仅不包括当前行。第二个需要ORDER BY 来确定哪些行属于同一组。
The manual:
frame_exclusion 选项允许当前行周围的行
从框架中排除,即使它们将根据
帧开始和帧结束选项。 EXCLUDE CURRENT ROW
从框架中排除当前行。 EXCLUDE GROUP 不包括
框架中的当前行及其排序节点。 EXCLUDE TIES
从帧中排除当前行的任何对等点,但不排除
当前行本身。 [...]
我的大胆强调。
这使用自定义聚合函数array_combine(anyarray)provided by a_horse。
或者在这里:
【解决方案2】:
您可以对派生表使用横向连接,将名称不相等的所有行取消嵌套,然后将其聚合回数组中:
select t1.*, xu.agg_values
from the_table t1
cross join lateral (
select array_agg(tu.v) as agg_values
from the_table t2
cross join unnest(t2."values") as tu(v)
where t2.name <> t1.name
) xu
这可以通过创建自定义聚合来简化,以避免取消嵌套和聚合:
create aggregate array_combine(anyarray)
(
sfunc = array_cat(anyarray, anyarray),
stype = anyarray
);
那么这可以写成:
select t1.*, xu.agg_values
from the_table t1
cross join lateral (
select array_combine(t2.values) as agg_values
from the_table t2
where t2.name <> t1.name
) xu