PC TOOLS

EP04. "进阶查询语法:EXISTS / ANY / ALL / CASE"

首页 PC 工具 Calculating · SQL · EP04
约 15 分钟· #EP04#SQL
🔒 登录后可标记已读
  • 「找出至少下过一次单的用户」「找出比某个分类里所有商品都贵的商品」——这类判断句式,直接翻译成 SQL 就是 EXISTS / ANY / ALL 的用武之地。
  • EP02 的语法速查表已经提过一行 EXISTS (subquery)COALESCE(col, default),这篇把两者真正展开讲清楚怎么写、什么时候比 JOIN/IN 更合适。
  • CASE 表达式是 SQL 里的 if-else,能把判断逻辑直接写进查询结果的栏位。
  • SELECT INTO 和 INSERT INTO SELECT 是两种「复制数据到别的表」的写法,一个负责建新表、一个负责塞进已存在的表,容易搞混。
  • 最后补 NULL 值替代函数在不同数据库的写法差异——COALESCE、IFNULL、ISNULL、NVL 名字都不一样,但做的事一样。

重点内容


EXISTS:只看「有没有」,不看「是什么」

EXISTS 后面接一个子查询,只判断子查询有没有返回至少一行,不关心返回的具体内容是什么,成立就是 TRUE。因为不需要真的把子查询的值取出来比对,条件符合的当下数据库就能提前停止往下找,这也是它常常比同等效果的 JOIN 更省资源的原因。

-- 找出至少下过一次订单的用户
SELECT u.id, u.name
FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

-- 反过来:找出从来没下过单的用户(NOT EXISTS)
SELECT u.id, u.name
FROM users u
WHERE NOT EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.id
);

上面这种子查询里的条件(o.user_id = u.id)引用了外层查询的栏位,叫「关联子查询」(correlated subquery)——子查询会针对外层的每一行各跑一次,专门配合 EXISTS/NOT EXISTS 使用。

📌 为什么很多人放弃 NOT IN、改用 NOT EXISTS:如果子查询结果集里混进一个 NULL,NOT IN 的比对会整个失效、直接返回空结果——这是子查询栏位可能为 NULL 时的经典陷阱。NOT EXISTS 不会受 NULL 影响,正式环境要判断"不存在",优先用 NOT EXISTS 而不是 NOT IN。


ANY / ALL:拿单一值跟一整组值比较

ANY 和 ALL 都是搭配比较运算符(=><>=<=<>)跟一个子查询的结果集比较,差别在于门槛松紧:

  • ANY:子查询结果里只要有一个值满足条件就算成立
  • ALL:子查询结果里每一个值都要满足条件才算成立
-- ANY:找出比「电子产品」分类里最便宜商品还贵的商品(价格 > 该分类中任一个价格)
SELECT name, price
FROM products
WHERE price > ANY (
  SELECT price FROM products WHERE category_id = 3
);

-- ALL:找出比「电子产品」分类里最贵商品还贵的商品(价格要大于该分类全部价格)
SELECT name, price
FROM products
WHERE price > ALL (
  SELECT price FROM products WHERE category_id = 3
);

💡 > ANY (subquery) 等价于 > (SELECT MIN(...) FROM subquery)> ALL (subquery) 等价于 > (SELECT MAX(...) FROM subquery)——看不懂 ANY/ALL 的时候,换成 MIN()/MAX() 的写法想一遍会更直觉。另外 = ANY (subquery) 效果等同于 IN (subquery),两种写法可以互换。


CASE:把判断逻辑写进查询结果

CASE 表达式依序检查每个 WHEN 条件,碰到第一个成立的条件就停下、返回对应的 THEN 结果,全部都不成立就用 ELSE 的值兜底(没写 ELSE 则返回 NULL,不是报错)。

-- 把订单按金额分级,直接在查询结果多一栏「订单等级」
SELECT
  id,
  total_amount,
  CASE
    WHEN total_amount >= 500 THEN '大额订单'
    WHEN total_amount >= 100 THEN '中额订单'
    ELSE '小额订单'
  END AS 订单等级
FROM orders;

CASE 也能配合聚合函数做「条件式计数」,一次统计出各等级各自有几笔,不用分开跑三次查询:

SELECT
  COUNT(CASE WHEN total_amount >= 500 THEN 1 END) AS 大额订单数,
  COUNT(CASE WHEN total_amount >= 100 AND total_amount < 500 THEN 1 END) AS 中额订单数,
  COUNT(CASE WHEN total_amount < 100 THEN 1 END) AS 小额订单数
FROM orders;

SELECT INTO 与 INSERT INTO SELECT:复制数据到别的表

两者都是「用查询结果去建立/填充另一张表」,但目标表的状态不一样:

语法目标表用途
SELECT ... INTO new_table FROM ...全新的表(自动建立)备份、建临时分析表
INSERT INTO existing_table SELECT ... FROM ...已存在的表把数据追加进现有表,原有数据不受影响
-- INSERT INTO SELECT:把一年前已完成的订单搬进历史归档表(orders_archive 要事先建好)
INSERT INTO orders_archive (id, user_id, status, total_amount, created_at)
SELECT id, user_id, status, total_amount, created_at
FROM orders
WHERE status = 'completed'
  AND created_at < NOW() - INTERVAL '1 year';

📌 SELECT INTO 在 PostgreSQL / MySQL 上不是这样用的SELECT ... INTO new_table 是 SQL Server / MS Access 的写法。PostgreSQL 要做「建一张新表塞进查询结果」这件事,对应语法是 CREATE TABLE ... AS SELECT(MySQL 也支持同样写法):

-- PostgreSQL / MySQL:建新表并塞进查询结果,效果等同 SQL Server 的 SELECT INTO
CREATE TABLE orders_2025_backup AS
SELECT * FROM orders WHERE created_at < '2026-01-01';
数据库建新表并复制数据的写法
SQL Server / MS AccessSELECT * INTO new_table FROM old_table
PostgreSQLCREATE TABLE new_table AS SELECT * FROM old_table
MySQLCREATE TABLE new_table AS SELECT * FROM old_table

NULL 值替代函数:COALESCE 与各数据库写法

EP02 提过 COALESCE(col, default) 能把 NULL 换成指定的默认值,这里展开讲:COALESCE 其实可以传两个以上参数,会依序找第一个不是 NULL 的值,适合做「多层退回」。

-- 显示名称优先用 nickname,没填就退回用 name,两个都没有才显示 'Guest'
SELECT id, COALESCE(nickname, name, 'Guest') AS display_name
FROM users;

不同数据库处理 NULL 替代的函数名不一样,功能是一样的:

数据库函数语法备注
PostgreSQL / SQL Server / OracleCOALESCECOALESCE(val1, val2, ..., valN)ANSI 标准写法,可以传 2 个以上参数
MySQLIFNULLIFNULL(expr, alt)只能传 2 个参数;MySQL 也支持 COALESCE
SQL ServerISNULLISNULL(expr, alt)只能传 2 个参数;SQL Server 也支持 COALESCE
OracleNVLNVL(expr, alt)只能传 2 个参数

📌 PostgreSQL 没有 IFNULL、也没有 ISNULL,只有 COALESCE——从 MySQL 或 SQL Server 转来 PostgreSQL 最容易踩这个坑,写 IFNULL(...) 会直接报函数不存在。


语法速查

任务语法
判断子查询有没有结果WHERE EXISTS (subquery)
判断子查询完全没有结果WHERE NOT EXISTS (subquery)
跟子查询任一值比较WHERE col > ANY (subquery)
跟子查询全部值比较WHERE col > ALL (subquery)
条件判断出结果栏位CASE WHEN cond THEN val ELSE default END
建新表塞进查询结果(PostgreSQL)CREATE TABLE t AS SELECT ...
塞进已存在的表INSERT INTO t SELECT ...
Null 值替代(标准写法)COALESCE(col, default)

适用版本

内容以 PostgreSQL 语法为主。EXISTS / ANY / ALL / CASE 在 PostgreSQL、MySQL、SQL Server 上写法一致;SELECT INTO 和 NULL 替代函数在不同数据库上差异较大,已在上面各自的对照表列出,实际使用前建议对照所用数据库的官方文档确认。

常见错误

  • ❌ 子查询结果可能含 NULL 时还是用 NOT IN 判断「不存在」——NULL 会让 NOT IN 整个失效返回空结果,改用 NOT EXISTS 更安全
  • ❌ 看到 ANY/ALL 就死记语法,没意识到 > ANY 等于跟子查询最小值比、> ALL 等于跟子查询最大值比——想不通的时候换成 MIN()/MAX() 改写会更好懂
  • ❌ CASE 没写 ELSE,以为条件都不满足时会报错——实际上会静默返回 NULL,报表里容易出现看不出原因的空白栏位
  • ❌ 在 PostgreSQL 或 MySQL 里照抄网上教程的 SELECT ... INTO new_table 语法建表——两者都不支持这种写法,要改用 CREATE TABLE ... AS SELECT
  • ❌ 从 MySQL/SQL Server 转到 PostgreSQL,还在用 IFNULL()/ISNULL()——PostgreSQL 只认 COALESCE()

Sources

Blog / Website:

  1. SQL Exists
  2. SQL Any, All
  3. SQL All Keyword
  4. SQL Case
  5. SQL Select Into
  6. SQL Insert Into Select
  7. SQL Null Functions