37 EXISTS 与派生表
EXISTS (subquery) 在子查询返回至少一行时为真。它是回答“是否有任何相关行满足 X?”这类问题的主力工具——通常写成引用外层行的关联子查询。派生表是在 FROM 子句中使用的子查询,给它指定别名后,就可以像表一样使用。两者结合起来,涵盖了在接触 CTE 和窗口函数之前你会编写的大多数 SQL。
本章要点
EXISTS与NOT EXISTS:测试“是否存在任何行”/“不存在任何行”- 为什么对于可能含空值的子查询,
NOT EXISTS胜过NOT IN - 派生表:在
FROM中使用并带有别名的子查询 - 什么时候派生表比 CTE 更清晰
IN与连接的关系
示例
EXISTS 内部的约定写法 SELECT 1 并不是什么魔法——PostgreSQL 不关心子查询返回哪些列;它只检查是否会产生任何行。SELECT 1、SELECT * 和 SELECT NULL 的效果都一样。SELECT 1 表示“我不关心这些列”,因此成为约定俗成的写法。
派生表也称内联视图,其实就是 FROM 中的子查询。明确的别名(写在右括号后面)为结果指定一个可供引用的名称。派生表很容易组合:可以连接、过滤、分组,甚至嵌套它们。主要缺点是,复杂的派生表会把逻辑埋在查询中间。CTE(下一课)通常是为中间结果命名的更好方式。
运行示例
运行方式(需要 customers_example 和 orders_example):
常见陷阱
IN (SELECT col FROM t)和EXISTS (SELECT 1 FROM t WHERE t.col = outer.col)通常等价——但当 NULL 可能出现时,NOT IN与NOT EXISTS并不等价。只要子查询可能包含 NULL,就优先使用NOT EXISTS。- 像
AS x这样的别名有助于提高清晰度和可移植性。PostgreSQL 16 及更高版本允许FROM中的子查询省略别名;较早的 PostgreSQL 版本则要求提供别名。 - 派生表内部可以使用
ORDER BY,但外层查询使用它时不会保留该顺序——应在外层排序。 - 一些旧指南说
EXISTS“总是比IN慢”。在现代 PostgreSQL 中,规划器对两者处理得都很好;应优先根据正确性和可读性选择。
EXISTS 会短路求值——找到第一条匹配行后就停止。因此,如果较早找到匹配行,WHERE EXISTS (SELECT 1 FROM big_table WHERE ...) 的执行速度可能很快。
找出名称恰好出现一次的 fruits:SELECT name FROM fruits f WHERE NOT EXISTS (SELECT 1 FROM fruits f2 WHERE f2.name = f.name AND f2.id <> f.id);。然后在脑中把派生表改写成 CTE(下一课的预览):WITH spend AS (SELECT customer_id, sum(total) AS total_spent FROM orders_example GROUP BY customer_id) SELECT * FROM spend WHERE total_spent > 30;。
下载
SQL 文件:exists-and-derived-tables.sql · 全部示例文件 · 准备工作