GBase 8a UNION 和 UNION ALL 的使用边界

GBase 8a UNION 和 UNION ALL 的使用边界

我最近看资料和整理报表链路时,越来越觉得 GBase 8a 里很多"结果总量对不上"的问题,并不在 join,也不在 group by,而是出在 UNIONUNION ALL 的使用边界上。

尤其是多来源数据拼接、主题层宽表汇总、阶段结果合并这些场景,只要没先想清楚到底要不要去重,后面口径偏差就很容易慢慢放大。

现场里很常见的情况是:

开发图省事直接用 union 把两个结果拼起来,后来业务发现某些本该重复保留的记录被去掉了;还有一种反过来,大家默认用了 union all,结果同一批数据从两条链路同时进来,最后总量翻倍。

这类问题最麻烦的地方在于,SQL 完全能跑,结果也不像错得很离谱,但口径会越往下越难解释。

我自己理解下来,这条线更接近集合语义和结果口径管理

如果不先明确"这两批数据是互斥的、可重复的,还是需要按业务键去重",后面再去追报表差异,成本会很高。

先把 UNIONUNION ALL 的业务语义分开

从我自己的理解看,这两个写法最大的区别不是语法,而是你对重复记录的态度。

写法 我自己的理解 适合场景 主要风险
UNION 合并后去重 两边结果逻辑上可能重复,且确实只保留一份 误删本该保留的重复记录
UNION ALL 合并后不去重 两边结果本来就是独立贡献 重复数据直接累加

真正到现场时,我自己更关注的是:
重复记录到底是不是业务意义上的重复。

现场里常见的几种误判

  1. 把"值一样"误当成"业务上就是重复"。
  2. 以为两条链路互斥,实际上有重叠。
  3. 以为去重应该交给 union,没有先定义业务主键。
  4. 明明只想拼接明细,却用了 union 把同值明细吞掉。
  5. 明明应该在主键层去重,却直接在整行层面去重。

这些误判的共同点在于:没有先定义什么才算一条该保留的记录。

一个更接近现场的例子

业务需要合并 APP 和 H5 两个渠道的下单数据,原始表结构一致:

sql 复制代码
create table stg_order_app (
    order_id   bigint,
    user_id    bigint,
    pay_amt    decimal(18,2)
);

create table stg_order_h5 (
    order_id   bigint,
    user_id    bigint,
    pay_amt    decimal(18,2)
);

如果直接写:

sql 复制代码
select * from stg_order_app
union
select * from stg_order_h5;

看起来很自然,但这里隐含了一个非常强的前提:
只要两边整行一样,就只保留一条。

可真正落到现场时,你要先问清楚:

  • 同一个 order_id 会不会真的从两条链路重复进入?
  • 如果两边记录整行相同,业务是否确实只算一单?
  • 如果同一个订单在两个来源里金额一致、用户一致,是重复采集还是不同业务事件?

很多时候,这些问题没有先说清楚,union 就已经把结果口径先定死了。

我实际排查时一般怎么判断该用哪个

第一种:明确两边互斥,优先考虑 UNION ALL

如果两条链路业务上就是独立来源,且不应该互相吞记录,我自己更倾向于先用 union all

至少它不会替你偷偷做去重。

第二种:怀疑有重复,但不清楚重复规则,不要直接上 UNION

这时我更愿意先把数据拼起来,再按业务键判断重复,而不是直接让数据库按整行去重。

sql 复制代码
select *
from (
    select order_id, user_id, pay_amt, 'APP' as src from stg_order_app
    union all
    select order_id, user_id, pay_amt, 'H5'  as src from stg_order_h5
) t;

然后再看业务键分布:

sql 复制代码
select
    order_id,
    count(*) as dup_cnt
from (
    select order_id from stg_order_app
    union all
    select order_id from stg_order_h5
) t
group by order_id
having count(*) > 1;

这一步我自己特别看重,因为它能把"重复"从模糊感受变成可验证的事实。

UNION 最容易带来的几个偏差

偏差一:整行相同就被吞掉,但业务上本该保留

比如两个来源恰好生成了完全相同的一行明细,union 会只保留一份。

如果业务本来要看的是事件量,而不是去重后的订单量,这就有偏差。

偏差二:以为在按主键去重,实际是在按整行去重

这点我现场里见过很多次。

业务说"订单去重",技术却直接用了 union

union 去的是整行,不是你脑子里的订单主键。

偏差三:后续再做聚合时,已经很难还原原始贡献

一旦在前面被 union 去掉了,后面很难知道到底吞掉了哪些记录。

我自己更倾向的一套写法

如果业务规则还没完全坐实,我一般先保留原始贡献,再显式做业务去重。

sql 复制代码
create table stg_order_all as
select order_id, user_id, pay_amt, 'APP' as src from stg_order_app
union all
select order_id, user_id, pay_amt, 'H5'  as src from stg_order_h5;

然后按业务主键判断:

sql 复制代码
select
    order_id,
    count(*) as rec_cnt
from stg_order_all
group by order_id
having count(*) > 1;

如果最终业务确定"同一个 order_id 只保留一份",那我更愿意在主键层显式处理,而不是直接依赖 union 的整行去重语义。

一个简单的对照表

业务问题 我更倾向的写法 原因
两边明确互斥 UNION ALL 不额外吞记录
两边可能重叠,但规则未定 先 UNION ALL 再分析 先保留现场信息
需要按业务主键去重 先拼接再按主键处理 语义更清楚
只关心整行唯一值 UNION 适合整行集合语义

一个批检查脚本示意

bash 复制代码
#!/bin/bash

DBHOST=192.0.2.115
DBPORT=5258
DBNAME=dw_merge
DBUSER=merge_user
LOGDIR=/data/gbase/log/union_check
DAYSTR=$(date +%F)

mkdir -p "${LOGDIR}"

gccli -h ${DBHOST} -P ${DBPORT} -u ${DBUSER} ${DBNAME} <<'SQL' >> "${LOGDIR}/union_check_${DAYSTR}.log" 2>&1
select count(*) as app_cnt from stg_order_app;
select count(*) as h5_cnt from stg_order_h5;

select count(*) as union_cnt
from (
    select order_id, user_id, pay_amt from stg_order_app
    union
    select order_id, user_id, pay_amt from stg_order_h5
) t;

select count(*) as union_all_cnt
from (
    select order_id, user_id, pay_amt from stg_order_app
    union all
    select order_id, user_id, pay_amt from stg_order_h5
) t;
SQL

我自己更关注的不是哪种写法看起来更短,而是它是不是准确表达了业务对重复记录的态度。

结尾

我最近回头看 GBase 8a 里这类问题时,一个很明显的感受是:
unionunion all 最大的差别,不是性能层面的争论,而是你到底把重复记录当成什么。

真正落到现场时,先把业务重复、整行重复和主键重复分开,再决定用哪一种写法,通常能比事后追报表偏差省很多时间。

参考资料

text 复制代码
[1] GBase 社区个人中心
https://www.gbase.cn/community/user/46723

[2] GBase 8a 社区优质文章区
https://www.gbase.cn/community/section/11

[3] GBase 8a MPP Cluster SQL 参考手册
https://www.gbase.cn/community/post/1772

[4] GBase 8a
https://www.gbase.cn/community/section/11
相关推荐
u0107475462 小时前
mysql如何实现高可用集群架构_基于MHA环境搭建与部署
jvm·数据库·python
一 乐2 小时前
工会管理|基于springboot + vue工会管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·工会管理系统
qq_380619162 小时前
如何在phpMyAdmin中处理特殊字符账号名的授权_反引号的正确包裹
jvm·数据库·python
2201_756847332 小时前
HTML函数在老旧浏览器运行慢是硬件问题吗_软硬协同分析【教程】
jvm·数据库·python
志栋智能2 小时前
当巡检遇上超自动化:一场运维质量的系统性升级
运维·服务器·网络·数据库·人工智能·机器学习·自动化
Micro麦可乐2 小时前
Redis只会用来做缓存?解锁Redis非缓存的九个应用场景,90%程序员不知道的隐藏技能
数据库·redis·缓存·消息队列·分布式锁·延迟队列·布隆过滤器
跨境卫士—小依2 小时前
平台流量分发机制变化跨境卖家如何重新获取曝光
大数据·人工智能·跨境电商·亚马逊·营销策略
21号 12 小时前
10.Redis 缓存
数据库·redis·缓存
jixingkj2 小时前
避开设置误区,让免打扰模式真正适配你的生活
大数据·安全·智能手机