注册
视图内 11 个 UNION ALL 分支改 IN + DECODE 的等价改写
专栏/技术分享/ 文章详情 /

视图内 11 个 UNION ALL 分支改 IN + DECODE 的等价改写

Gyyyyy 2026/10/23 47 1 0
摘要

达梦 SQL 优化:视图内 11 个 UNION ALL 分支改 IN + DECODE 的等价改写

导读:业务视图里常见一种写法——为了把某个字段的不同取值映射成不同的「页面类型」,写了 N 个 UNION ALL 分支,每个分支 WHERE usage_detail_code='C0X',各自输出一个 page_type 字面量。表一大(几百万行)这种写法就特别慢:每个分支全表扫一次,N 个分支就是 N 次全表扫。本文用 600 万行的实测数据,演示如何把它等价改写为 IN + DECODE,让全表扫描次数从 11 次降到 1 次。


一、为什么会慢:N 个分支 = N 次全表扫

先看这种写法的原始面貌。一个视图为了给字段 usage_detail_code 的不同取值映射成不同「页面类型」,写了 11 个 UNION ALL 分支:

CREATE OR REPLACE VIEW V_USAGE_DETAIL AS SELECT u.USAGE_ID, u.BILL_ID, u.AMOUNT, u.USE_TIME, 'P1' AS PAGE_TYPE FROM USAGE_RECORD u WHERE u.USAGE_DETAIL_CODE = 'C01' UNION ALL SELECT u.USAGE_ID, u.BILL_ID, u.AMOUNT, u.USE_TIME, 'P2' AS PAGE_TYPE FROM USAGE_RECORD u WHERE u.USAGE_DETAIL_CODE = 'C02' UNION ALL -- ...(中间省略 C03 ~ C10) SELECT u.USAGE_ID, u.BILL_ID, u.AMOUNT, u.USE_TIME, 'P11' AS PAGE_TYPE FROM USAGE_RECORD u WHERE u.USAGE_DETAIL_CODE = 'C11';

问题就出在这 11 个分支上:UNION ALL 的每一个分支,都是对 USAGE_RECORD 表的一次独立扫描。11 个分支就是 11 次全表扫,最后再 UNION ALL 合并,代价成倍放大。

  • 执行时间 ≈ 分支数 × 单次全表扫时间
  • 表越大、分支越多,差距越明显

二、改写思路:IN 收口条件,DECODE 收口映射

观察这 11 个分支,可以发现它们的规律性极强:

  1. 11 个 WHERE usage_detail_code='C0X' 只是取值不同;
  2. 11 个 SELECT 'PX' page_type 只是字面量映射不同。

于是可以两步收口:

  • 第一步:把 11 个 WHERE ... = 'C0X' 合并成 一个 IN (...11 个值...);
  • 第二步:把 11 个 SELECT 'PX' 字面量,用 DECODE(usage_detail_code, ...) 函数映射。

改写后的视图:

CREATE OR REPLACE VIEW V_USAGE_DETAIL_NEW AS SELECT u.USAGE_ID, u.BILL_ID, u.AMOUNT, u.USE_TIME, DECODE(u.USAGE_DETAIL_CODE, 'C01','P1', 'C02','P2', 'C03','P3', 'C04','P4', 'C05','P5', 'C06','P6', 'C07','P7', 'C08','P8', 'C09','P9', 'C10','P10', 'C11','P11', 'UNKNOWN') AS PAGE_TYPE FROM USAGE_RECORD u WHERE u.USAGE_DETAIL_CODE IN ('C01','C02','C03','C04','C05', 'C06','C07','C08','C09','C10','C11');

逻辑完全等价:原来按 code 分流输出不同 page_type,改写后 IN 一次取全量、DECODE 按 code 映射同一 page_type。DECODE 找不到匹配时返回默认值 'UNKNOWN'(原写法里不在 C01~C11 范围内的行本来就不会被取到,加默认值是为保险起见)。

三、实测:600 万行,5.6 倍提速

测试环境:达梦 DM8(db1 / 端口 5236),USAGE_RECORD 表 600 万行。

3.1 结果等价性校验

校验项 原版(11 UNION ALL) 改写版(IN + DECODE) 是否一致
总行数 600 万 600 万 ✅
业务 SQL 结果(视图 JOIN 账单维表 STATUS='V') 540 万 540 万 ✅
各 PAGE_TYPE 分组计数 P1~P11 各约 54.5 万 与左完全一致 ✅

3.2 执行耗时对比

方案 执行时间 提升
原版(11 UNION ALL) 2.56 s 基线
改写版(IN + DECODE) 0.46 s ↓ 5.6 倍

数据量再放大(几千万~上亿行)、分支再多时,差距会更大,因为原版耗时 ≈ 分支数 × 单次全表扫。

四、执行计划对比:从「扫 11 次」到「扫 1 次」

4.1 原版(11 UNION ALL)—— 估算代价 10598

#NSET2
└ #HASH2 INNER JOIN (DIM_BILL)
  └ #UNION ALL            ← 10 层嵌套 UNION ALL
    └ #UNION ALL
      └ ... 共 11 个分支 ...
        └ #SLCT2 U.USAGE_DETAIL_CODE='C01' → #CSCN2 USAGE_RECORD (扫 600 万)
        └ #SLCT2 U.USAGE_DETAIL_CODE='C02' → #CSCN2 USAGE_RECORD (扫 600 万)
        └ ...(共 11 个 #CSCN2,每次全扫 600 万)

4.2 改写版(IN + DECODE)—— 估算代价 1200(↓ 8.8 倍)

#NSET2
└ #HASH RIGHT SEMI JOIN2 (USAGE_DETAIL_CODE = 值列表)
  ├ #CONST VALUE LIST: row_num(11)      ← 11 个 code 常量化成值列表
  └ #HASH2 INNER JOIN (DIM_BILL)
    └ #SLCT2 BILL_STATUS='V'
    └ #CSCN2 USAGE_RECORD (只扫 1 次 600 万)

关键差异:

  1. 原版:11 个分支各自 #CSCN2 全扫 → 扫 11 次,每次 600 万行;
  2. 改写版:IN (...) 被优化成 #CONST VALUE LIST(11) + 半连接,只扫 1 次,DECODE 在投影层完成 page_type 映射。

五、关于 DECODE 函数的补充

DECODE 是达梦兼容 Oracle 的条件取值函数,等价于 CASE WHEN 的简写形式:

-- DECODE 写法 DECODE(expr, 值1, 结果1, 值2, 结果2, ..., 默认值) -- 等价于 CASE 写法 CASE expr WHEN 值1 THEN 结果1 WHEN 值2 THEN 结果2 ... ELSE 默认值 END

在这个场景里,用 DECODE 或 CASE WHEN 都能达到同样效果,DECODE 更简洁。

六、总结

  1. 「多分支 UNION ALL + 条件分流」是典型的慢写法:每个分支独立全表扫,N 分支耗时 ≈ N 倍单查询。
  2. 等价改写为 IN + DECODE 后:全表扫描次数从 N → 1,逻辑等价、结果不变。
  3. 分支越多、表越大,收益越明显。本例 11 分支 600 万行即提速 5.6 倍。
  4. 如果 usage_detail_code 上有索引,改写后还能走索引范围扫描进一步加速;本例为突出 UNION ALL 的代价,故意不建索引做全表扫对比,效果更直观。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服