PostgreSQL:分组然后过滤 table,条件为不存在
PostgreSQL: Grouping then filtering table, with condition for nonexistence
在 PostgreSQL 中,我有一个 table 抽象地看起来像这样:
╔═══╦═══╦═══╦═══╗
║ A ║ B ║ C ║ D ║
╠═══╬═══╬═══╬═══╣
║ x ║ 0 ║ y ║ 0 ║
║ x ║ 0 ║ x ║ 1 ║
║ x ║ 1 ║ y ║ 0 ║
║ x ║ 1 ║ z ║ 1 ║
║ y ║ 0 ║ z ║ 0 ║
║ y ║ 0 ║ x ║ 0 ║
║ y ║ 1 ║ y ║ 0 ║
╚═══╩═══╩═══╩═══╝
我想在查询中将其转换为:
╔═══╦═══╦══════╗
║ A ║ B ║ D ║
╠═══╬═══╬══════╣
║ x ║ 0 ║ 1 ║
║ x ║ 1 ║ null ║
║ y ║ 0 ║ null ║
║ y ║ 1 ║ 0 ║
╚═══╩═══╩══════╝
...这样:
- 输入 table 的行按 A 和 B 分组,
对于每个 A 和 B 对:
如果输入table有任意一行A = C,那么输出table有一行(A,B,D),其中D来自A = C.
的同一行
比如输入table有一行(x,0,x,1),其中A和C都是x。这意味着输出 table 有一行 (x, 0, 1),因为 D 是 1。 (x, 0, y, 0) 行(因为它也有 A = x 和 B = 0)是丢弃。
否则,如果不存在这样的行,则输出table有一行(A, B, null)。
例如,输入 table 有两行,其中 A = y 和 B = 0 — 它们是 (y, 0, z, 0) 和 (y, 0, x, 0)。在这两行中都没有 A = C。这意味着输出 table 有一个 (y, 0, null) 行。
我想不出任何方法来使用 aggregate functions, window functions, or subqueries 来执行此转换。
使用 CTE returns A = C
的所有行并加入 table:
with cte as (
select * from tablename
where "A" = "C"
)
select distinct t."A", t."B", c."D"
from tablename t left join cte c
on c."A" = t."A" and c."B" = t."B"
order by t."A", t."B"
参见demo。
结果:
| A | B | D |
| --- | --- | --- |
| x | 0 | 1 |
| x | 1 | |
| y | 0 | |
| y | 1 | 0 |
要从具有相同 (A, B)
的每个组中获取一行,有一种简单、简短且快速的方法:DISTINCT ON
- 不涉及聚合函数、window 函数或子查询:
SELECT DISTINCT ON (A, B)
A, B, CASE WHEN A = C THEN D END AS D
FROM tbl
ORDER BY A, B, (A = C) DESC;
准确地产生您想要的结果。
db<>fiddle here
假设所有涉及的列都已定义NOT NULL
,否则您需要做更多。
最后的 ORDER BY
项 (A = C) DESC
对行进行排序,每组 A = C
第一。这是一个 boolean
表达式,FALSE
在 TRUE
之前排序。如果可以有多行,请添加更多 ORDER BY
项以打破平局。
CASE WHEN A = C THEN D END
实现了 D
仅针对给定条件输出的要求。否则我们会根据需要得到 NULL
(CASE
的默认值)。
详细解释:
- Select first row in each GROUP BY group?
- Sorting null values after all others, except special
大表可能有更多的性能优化:
- Optimize GROUP BY query to retrieve latest row per user
在 PostgreSQL 中,我有一个 table 抽象地看起来像这样:
╔═══╦═══╦═══╦═══╗
║ A ║ B ║ C ║ D ║
╠═══╬═══╬═══╬═══╣
║ x ║ 0 ║ y ║ 0 ║
║ x ║ 0 ║ x ║ 1 ║
║ x ║ 1 ║ y ║ 0 ║
║ x ║ 1 ║ z ║ 1 ║
║ y ║ 0 ║ z ║ 0 ║
║ y ║ 0 ║ x ║ 0 ║
║ y ║ 1 ║ y ║ 0 ║
╚═══╩═══╩═══╩═══╝
我想在查询中将其转换为:
╔═══╦═══╦══════╗
║ A ║ B ║ D ║
╠═══╬═══╬══════╣
║ x ║ 0 ║ 1 ║
║ x ║ 1 ║ null ║
║ y ║ 0 ║ null ║
║ y ║ 1 ║ 0 ║
╚═══╩═══╩══════╝
...这样:
- 输入 table 的行按 A 和 B 分组,
对于每个 A 和 B 对:
如果输入table有任意一行A = C,那么输出table有一行(A,B,D),其中D来自A = C.
的同一行比如输入table有一行(x,0,x,1),其中A和C都是x。这意味着输出 table 有一行 (x, 0, 1),因为 D 是 1。 (x, 0, y, 0) 行(因为它也有 A = x 和 B = 0)是丢弃。
否则,如果不存在这样的行,则输出table有一行(A, B, null)。
例如,输入 table 有两行,其中 A = y 和 B = 0 — 它们是 (y, 0, z, 0) 和 (y, 0, x, 0)。在这两行中都没有 A = C。这意味着输出 table 有一个 (y, 0, null) 行。
我想不出任何方法来使用 aggregate functions, window functions, or subqueries 来执行此转换。
使用 CTE returns A = C
的所有行并加入 table:
with cte as (
select * from tablename
where "A" = "C"
)
select distinct t."A", t."B", c."D"
from tablename t left join cte c
on c."A" = t."A" and c."B" = t."B"
order by t."A", t."B"
参见demo。
结果:
| A | B | D |
| --- | --- | --- |
| x | 0 | 1 |
| x | 1 | |
| y | 0 | |
| y | 1 | 0 |
要从具有相同 (A, B)
的每个组中获取一行,有一种简单、简短且快速的方法:DISTINCT ON
- 不涉及聚合函数、window 函数或子查询:
SELECT DISTINCT ON (A, B)
A, B, CASE WHEN A = C THEN D END AS D
FROM tbl
ORDER BY A, B, (A = C) DESC;
准确地产生您想要的结果。
db<>fiddle here
假设所有涉及的列都已定义NOT NULL
,否则您需要做更多。
最后的 ORDER BY
项 (A = C) DESC
对行进行排序,每组 A = C
第一。这是一个 boolean
表达式,FALSE
在 TRUE
之前排序。如果可以有多行,请添加更多 ORDER BY
项以打破平局。
CASE WHEN A = C THEN D END
实现了 D
仅针对给定条件输出的要求。否则我们会根据需要得到 NULL
(CASE
的默认值)。
详细解释:
- Select first row in each GROUP BY group?
- Sorting null values after all others, except special
大表可能有更多的性能优化:
- Optimize GROUP BY query to retrieve latest row per user