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    ║
╚═══╩═══╩══════╝

...这样:

  1. 输入 table 的行按 A 和 B 分组,
  2. 对于每个 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 表达式,FALSETRUE 之前排序。如果可以有多行,请添加更多 ORDER BY 项以打破平局。

CASE WHEN A = C THEN D END 实现了 D 仅针对给定条件输出的要求。否则我们会根据需要得到 NULLCASE 的默认值)。

详细解释:

  • 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