SQL 高级查询技巧:WITH + UNION ALL + EXISTS + WHERE TRUE/FALSE 联合实战

一、四大核心概念速览

概念 一句话解释 类比
WITH (CTE) 给子查询起个临时名字,后面反复引用 临时变量
UNION ALL 把多个查询结果纵向拼接在一起 多张表上下粘贴
EXISTS / NOT EXISTS 判断子查询有没有数据,返回 TRUE/FALSE if 判断
WHERE TRUE / FALSE 控制查询是否返回数据的开关 开关按钮

二、WITH(CTE 公共表表达式)

2.1 基本语法

sql 复制代码
WITH 临时表名 AS (
    SELECT ... FROM 真实表 WHERE 条件
)
SELECT * FROM 临时表名;

2.2 核心用法

① 单个 CTE
sql 复制代码
WITH dept_summary AS (
    SELECT department, COUNT(*) AS cnt
    FROM employee
    GROUP BY department
)
SELECT * FROM dept_summary WHERE cnt > 10;
② 多个 CTE(后面的可以引用前面的
sql 复制代码
WITH 
cte1 AS (
    SELECT * FROM 表A WHERE 条件
),
cte2 AS (
    SELECT * FROM cte1 JOIN 表B ON ...   -- 引用 cte1
)
SELECT * FROM cte2;
③ 递归 CTE(树形结构查询)
sql 复制代码
WITH RECURSIVE tree AS (
    -- 根节点
    SELECT id, name, parent_id, 1 AS level
    FROM menu WHERE parent_id IS NULL
    UNION ALL
    -- 递归子节点
    SELECT m.id, m.name, m.parent_id, t.level + 1
    FROM menu m JOIN tree t ON m.parent_id = t.id
)
SELECT * FROM tree;

2.3 注意事项

注意点 说明
⚠️ 作用域 CTE 只在紧跟其后的一条 SQL 中有效,不能跨语句
⚠️ 不是临时表 CTE 不会持久化存储,语句执行完就消失
⚠️ PG 版本差异 PG 12 之前 CTE 是优化屏障(强制物化),12+ 可被内联优化
⚠️ 递归必须加关键字 PostgreSQL 中必须写 WITH RECURSIVE,不能省略
⚠️ 紧跟使用 WITH ... AS (...) 后面必须立刻跟 SELECT/INSERT/UPDATE/DELETE

三、UNION ALL(纵向合并结果集)

3.1 基本语法

sql 复制代码
SELECT col1, col2 FROM 表A
UNION ALL
SELECT col1, col2 FROM 表B;

3.2 UNION 与 UNION ALL 的区别

sql 复制代码
-- UNION:合并 + 去重(慢)
SELECT name FROM 表A
UNION
SELECT name FROM 表B;

-- UNION ALL:合并 + 不去重(快)
SELECT name FROM 表A
UNION ALL
SELECT name FROM 表B;
对比项 UNION UNION ALL
是否去重 ✅ 去重 ❌ 不去重
性能 慢(需要排序去重) 快
使用场景 需要去重时 确定无重复或允许重复时

3.3 注意事项

注意点 说明
⚠️ 列数必须一致 两个 SELECT 的列数量必须相同
⚠️ 类型要兼容 对应位置的列数据类型要兼容(如不能一边是 int 一边是 text)
⚠️ 列名取第一个 最终结果的列名以第一个 SELECT 的列名为准
⚠️ ORDER BY 放最后 排序只能放在整个 UNION ALL 的最后,不能放在中间
sql 复制代码
-- ✅ 正确:ORDER BY 放最后
SELECT name, age FROM 表A
UNION ALL
SELECT name, age FROM 表B
ORDER BY name;

-- ❌ 错误:ORDER BY 放在中间
SELECT name, age FROM 表A ORDER BY name   -- 报错!
UNION ALL
SELECT name, age FROM 表B;

如果需要对单个部分排序,需要用子查询包裹:

sql 复制代码
(SELECT name, age FROM 表A ORDER BY name LIMIT 10)
UNION ALL
(SELECT name, age FROM 表B ORDER BY name LIMIT 10);

四、EXISTS / NOT EXISTS(存在性判断)

4.1 基本语法

sql 复制代码
-- 子查询有数据 → TRUE
WHERE EXISTS (SELECT 1 FROM 表 WHERE 条件)

-- 子查询没数据 → TRUE
WHERE NOT EXISTS (SELECT 1 FROM 表 WHERE 条件)

4.2 SELECT 1 的含义

sql 复制代码
EXISTS (SELECT 1 FROM ...)
EXISTS (SELECT * FROM ...)
EXISTS (SELECT 'abc' FROM ...)

三种写法效果完全一样! EXISTS 只关心有没有行返回 ,不关心返回的是什么值。SELECT 1 是惯用写法,语义最清晰。

4.3 EXISTS vs IN 的对比

sql 复制代码
-- 写法1:EXISTS
SELECT * FROM 表A
WHERE EXISTS (
    SELECT 1 FROM 表B WHERE 表B.id = 表A.id
);

-- 写法2:IN
SELECT * FROM 表A
WHERE 表A.id IN (
    SELECT id FROM 表B
);
对比项 EXISTS IN
大表驱动小表 ✅ 更快 较慢
小表驱动大表 较慢 ✅ 更快
能处理 NULL ✅ 能 ❌ 可能有问题
可读性 一般 更直观

4.4 注意事项

注意点 说明
⚠️ 不要 SELECT * 虽然结果一样,但 SELECT 1 语义更清晰
⚠️ 关联条件 通常需要内外表的关联条件,否则只要子查询有数据就恒为 TRUE
⚠️ NULL 处理 EXISTS 对 NULL 友好,IN 遇到 NULL 可能返回意外结果

五、WHERE TRUE / FALSE(条件开关)

5.1 基本效果

sql 复制代码
SELECT * FROM 表 WHERE TRUE;   -- 返回全部数据(等于没有WHERE)
SELECT * FROM 表 WHERE FALSE;  -- 返回0条(全部过滤掉)

5.2 实际应用场景

WHERE TRUE ------ 绿灯放行(等于没写)

当 SQL 变成 SELECT * FROM B表 WHERE TRUE AND (其他条件); 时:

  • 含义 :TRUE 永远为真。
  • 作用 :它对查询结果没有任何影响,完全等价于 SELECT * FROM B表 WHERE (其他条件);。
  • 底层执行 :数据库优化器看到 TRUE AND,会自动把它忽略掉(常量折叠) ,然后老老实实去扫描 B 表,根据后面的 (其他条件) 过滤出数据,最后通过 UNION ALL 拼接到上面的结果集中。

WHERE FALSE ------ 拔掉电源(查询剪枝 / 短路)

当 SQL 变成 SELECT * FROM B表 WHERE FALSE AND (其他条件); 时,魔法就发生了:

  • 含义 :FALSE 永远为假。这就意味着,无论后面的 (其他条件) 是什么,无论表里有多少数据,这行记录都不可能满足条件。

  • 作用 :强制让这个查询返回 0 条记录(空结果集)。

  • 底层执行(极度关键) :

    现代关系型数据库(如 PostgreSQL、MySQL、Oracle)的查询优化器(Optimizer)非常聪明。
    当它在解析 SQL 语法树时,只要看到 WHERE FALSE 或者 WHERE 1=2 这种
    恒假条件
    ,它会触发一种叫做 "常量折叠 (Constant Folding)" 或 "查询剪枝 (Query Pruning)" 的优化机制。

    也就是说,数据库根本不会去触碰 B 表的磁盘文件!根本不会去读 B 表的索引! 它在生成执行计划的那一瞬间,就直接把对 B 表的访问操作给"砍掉"了,瞬间返回一个空壳。

① MyBatis 动态 SQL 拼接技巧
sql 复制代码
<!-- 传统写法:用 <where> 标签 -->
<select id="query">
    SELECT * FROM user
    <where>
        <if test="name != null">AND name = #{name}</if>
        <if test="age != null">AND age = #{age}</if>
    </where>
</select>

<!-- 简洁写法:用 WHERE TRUE -->
<select id="query">
    SELECT * FROM user
    WHERE TRUE
    <if test="name != null">AND name = #{name}</if>
    <if test="age != null">AND age = #{age}</if>
</select>

WHERE TRUE 打底,后面所有条件都用 AND 开头,永远不会出现语法错误。

② 调试时只看表结构
sql 复制代码
SELECT * FROM 超大表 WHERE FALSE;
-- 瞬间返回,0条数据,但能看到所有列名和类型
③ 作为查询开关
sql 复制代码
SELECT * FROM 表A
UNION ALL
SELECT * FROM 表B WHERE TRUE;   -- 表B全部数据都参与合并

SELECT * FROM 表A
UNION ALL
SELECT * FROM 表B WHERE FALSE;  -- 表B不参与合并(0条)

-- A查到数据时,SQL 实际变成了:
SELECT * 表A                     -- 返回A的数据
UNION ALL
SELECT * FROM 表B WHERE FALSE;   -- 返回0条,B被"关掉"了

-- A有数据时,SQL 实际变成了:
SELECT * FROM 表A                -- 返回A的数据
UNION ALL
SELECT * FROM 表B WHERE TRUE;    -- 返回B的全部数据
                                 -- 返回A和B的全部数据

-- A没查到数据时,SQL 实际变成了:
SELECT * FROM a_results          -- 返回0条
UNION ALL
SELECT * FROM 表B WHERE TRUE;    -- 返回B的全部数据

5.3 注意事项

注意点 说明
⚠️ WHERE TRUE 不影响性能 数据库优化器会自动忽略 WHERE TRUE
⚠️ WHERE FALSE 不会扫表 优化器直接返回空结果,不消耗资源
⚠️ 不是所有数据库都支持 MySQL、PostgreSQL 支持;Oracle 需要用 WHERE 1=1 代替

六、四者联合使用:多表优先级查询实战

6.1 业务需求

查企业信息:先查危化品登记表(A表),A表有数据就用A的结果;A表没有数据,再查工商登记表(B表)兜底。

6.2 完整 SQL

sql 复制代码
-- 第一步:WITH 把A表的查询结果缓存为临时表
WITH a_results AS (
    SELECT
        company_name          AS companyName,
        company_code          AS companyCode,
        ''                    AS uniscId,
        address_registry      AS addressRegistry,
        representative_person AS representativePerson,
        responsible_phone     AS responsiblePhone
    FROM bussdata.ds_whdjxt_v_tb_base_company_1
    WHERE company_name LIKE '%某企业%'
)

-- 第二步:先返回A表的数据
SELECT * FROM a_results

-- 第三步:UNION ALL 合并B表数据
UNION ALL

-- 第四步:NOT EXISTS 判断A表是否为空,为空才查B表
SELECT
    enterprise_name       AS companyName,
    ''                    AS companyCode,
    unisc_id              AS uniscId,
    regis_addr            AS addressRegistry,
    legal_representative  AS representativePerson,
    contact_number        AS responsiblePhone
FROM bussdata.company_mobile
WHERE NOT EXISTS (SELECT 1 FROM a_results)  -- 关键开关!
  AND enterprise_name LIKE '%某企业%';

6.3 执行流程图

sql 复制代码
┌──────────────────────────────────────┐
│  ① WITH: 执行A表查询,缓存结果        │
│     a_results = SELECT ... FROM 表A   │
└─────────────────┬────────────────────┘
                  │
          a_results 有数据?
                 / \
              是/   \否
               /     \
┌─────────────┐     ┌──────────────────┐
│ a_results   │     │ a_results        │
│ = 5条数据   │     │ = 0条(空)       │
└──────┬──────┘     └────────┬─────────┘
       │                     │
       ▼                     ▼
┌─────────────┐     ┌──────────────────┐
│② SELECT *   │     │② SELECT *        │
│FROM a_results│    │FROM a_results     │
│→ 返回5条    │     │→ 返回0条          │
├─────────────┤     ├──────────────────┤
│③ UNION ALL  │     │③ UNION ALL       │
├─────────────┤     ├──────────────────┤
│④ NOT EXISTS │     │④ NOT EXISTS      │
│= FALSE      │     │= TRUE            │
│→ 相当于     │     │→ 相当于           │
│WHERE FALSE  │     │WHERE TRUE        │
│→ B返回0条   │     │→ 查B表,返回B数据 │
└──────┬──────┘     └────────┬─────────┘
       │                     │
       ▼                     ▼
  最终:A的5条数据      最终:B的数据

七、完整对照表

概念 作用 等价理解 在本例中的角色
WITH 缓存A表查询结果 临时变量 让A的结果可被多次引用
UNION ALL 拼接A和B的结果 上下粘贴两张表 合并两个数据源
NOT EXISTS 判断A是否为空 if 判断 控制B是否执行的开关
WHERE TRUE/FALSE NOT EXISTS 的返回值 开关的两个状态 TRUE=查B,FALSE=不查B

八、常见踩坑总结

坑 表现 解决方案
CTE 后面没紧跟 SELECT 语法报错 WITH ... AS (...) SELECT ... 必须连写
UNION ALL 两边列数不同 列数不匹配报错 用 '' 或 NULL 补齐缺少的列
UNION ALL 中间加 ORDER BY 语法报错 ORDER BY 只能放最后面
MyBatis 中 <if> 前面缺 AND SQL 语法错误 用 WHERE TRUE 或 <where> 标签
PG 递归 CTE 忘写 RECURSIVE 语法报错 WITH RECURSIVE tree AS (...)
Docker 中导入 SQL 报 \N 错误 换行符问题 sed -i 's/\r$//' file.sql

九、一句话总结

WITH 负责缓存,UNION ALL 负责合并,NOT EXISTS 负责判断,WHERE TRUE/FALSE 是最终的开关 ------ 四者配合实现了"A表优先、B表兜底"的优雅查询模式,一条 SQL 搞定多表优先级逻辑,无需存储过程。

相关推荐
IvorySQL2 小时前
PostgreSQL 日报|修复截断 zstd 备份检测(10 月 9 日)
数据库·postgresql
刘胡子大叔3 小时前
SQL 脚本的导入顺序
数据库·sql
代码什么用3 小时前
Spring对IoC的实现
数据库·spring
hz567895 小时前
涉密视频会议设备配置指南:终端、音视频采集与配套设施选型
服务器·网络·数据库·安全·实时音视频·信息与通信·智能硬件
广州浮点FLOATLIC5 小时前
许可证服务器迁移后软件打不开:研发 IT 怎样定位连接问题
linux·服务器·数据库
程序员Sunday5 小时前
MySQL 为什么使用 B+ 树索引?把范围查询、回表和覆盖索引连起来
数据库·mysql
半杯咖啡半行码5 小时前
Qt开发实战:数据库、MV 模式、QProcess与串口通信全攻略
数据库·qt
小米里的大麦6 小时前
16 MySQL 事务
数据库·mysql
西柚小萌新6 小时前
【LLM&&AI应用开发 八股文】--4.3.Agent智能体(下)
java·开发语言·数据库
邓工说电7 小时前
智慧断路器安全吗?数据加密、离线保护与合规认证全解读
大数据·数据库·人工智能·智能断路器·炜晔科技