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 搞定多表优先级逻辑,无需存储过程。

相关推荐
IndulgeCui2 小时前
Kingbase 身份认证与权限控制实践—数据库安全的第一道防线
数据库
Yushan Bai2 小时前
RAC环境数据库节点异常重启问题的分析(存储光纤信号问题)
数据库
WINDHILL_风丘科技2 小时前
FlexPro高级应用之模板定制
数据库·汽车·汽车测试·flexpro
慎久2 小时前
SAP CDS对数据进行排序后读取第一条数据
数据库·cds·amdp
敢敢のwings2 小时前
智元 D1 强化学习sim-to-real系列 | Robot Lab 基于 Isaac Lab 的机器人强化学习使用(四)
数据库·redis·机器人
sunwenjian8862 小时前
DVWA靶场通关——SQL Injection篇
数据库·sql
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-03-30
大数据·数据库·人工智能·经验分享·搜索引擎
代码派3 小时前
MySQL数据如何实时同步到StarRocks?NineData实操指南 原创
数据库·starrocks·mysql·数据库管理·慢sql·ninedata·ddl变更
小温冲冲3 小时前
Qt WindowContainer 完整实战示例:QWidget 嵌入 QML
开发语言·数据库·qt