GBase 8a UNION 和 UNION ALL 的使用边界 GBase 8a UNION 和 UNION ALL 的使用边界我最近看资料和整理报表链路时越来越觉得 GBase 8a 里很多“结果总量对不上”的问题并不在 join也不在 group by而是出在UNION和UNION ALL的使用边界上。尤其是多来源数据拼接、主题层宽表汇总、阶段结果合并这些场景只要没先想清楚到底要不要去重后面口径偏差就很容易慢慢放大。现场里很常见的情况是开发图省事直接用union把两个结果拼起来后来业务发现某些本该重复保留的记录被去掉了还有一种反过来大家默认用了union all结果同一批数据从两条链路同时进来最后总量翻倍。这类问题最麻烦的地方在于SQL 完全能跑结果也不像错得很离谱但口径会越往下越难解释。我自己理解下来这条线更接近集合语义和结果口径管理。如果不先明确“这两批数据是互斥的、可重复的还是需要按业务键去重”后面再去追报表差异成本会很高。先把UNION和UNION ALL的业务语义分开从我自己的理解看这两个写法最大的区别不是语法而是你对重复记录的态度。写法我自己的理解适合场景主要风险UNION合并后去重两边结果逻辑上可能重复且确实只保留一份误删本该保留的重复记录UNION ALL合并后不去重两边结果本来就是独立贡献重复数据直接累加真正到现场时我自己更关注的是重复记录到底是不是业务意义上的重复。现场里常见的几种误判把“值一样”误当成“业务上就是重复”。以为两条链路互斥实际上有重叠。以为去重应该交给union没有先定义业务主键。明明只想拼接明细却用了union把同值明细吞掉。明明应该在主键层去重却直接在整行层面去重。这些误判的共同点在于没有先定义什么才算一条该保留的记录。一个更接近现场的例子业务需要合并 APP 和 H5 两个渠道的下单数据原始表结构一致createtablestg_order_app(order_idbigint,user_idbigint,pay_amtdecimal(18,2));createtablestg_order_h5(order_idbigint,user_idbigint,pay_amtdecimal(18,2));如果直接写select*fromstg_order_appunionselect*fromstg_order_h5;看起来很自然但这里隐含了一个非常强的前提只要两边整行一样就只保留一条。可真正落到现场时你要先问清楚同一个order_id会不会真的从两条链路重复进入如果两边记录整行相同业务是否确实只算一单如果同一个订单在两个来源里金额一致、用户一致是重复采集还是不同业务事件很多时候这些问题没有先说清楚union就已经把结果口径先定死了。我实际排查时一般怎么判断该用哪个第一种明确两边互斥优先考虑 UNION ALL如果两条链路业务上就是独立来源且不应该互相吞记录我自己更倾向于先用union all。至少它不会替你偷偷做去重。第二种怀疑有重复但不清楚重复规则不要直接上 UNION这时我更愿意先把数据拼起来再按业务键判断重复而不是直接让数据库按整行去重。select*from(selectorder_id,user_id,pay_amt,APPassrcfromstg_order_appunionallselectorder_id,user_id,pay_amt,H5assrcfromstg_order_h5)t;然后再看业务键分布selectorder_id,count(*)asdup_cntfrom(selectorder_idfromstg_order_appunionallselectorder_idfromstg_order_h5)tgroupbyorder_idhavingcount(*)1;这一步我自己特别看重因为它能把“重复”从模糊感受变成可验证的事实。UNION最容易带来的几个偏差偏差一整行相同就被吞掉但业务上本该保留比如两个来源恰好生成了完全相同的一行明细union会只保留一份。如果业务本来要看的是事件量而不是去重后的订单量这就有偏差。偏差二以为在按主键去重实际是在按整行去重这点我现场里见过很多次。业务说“订单去重”技术却直接用了union。但union去的是整行不是你脑子里的订单主键。偏差三后续再做聚合时已经很难还原原始贡献一旦在前面被union去掉了后面很难知道到底吞掉了哪些记录。我自己更倾向的一套写法如果业务规则还没完全坐实我一般先保留原始贡献再显式做业务去重。createtablestg_order_allasselectorder_id,user_id,pay_amt,APPassrcfromstg_order_appunionallselectorder_id,user_id,pay_amt,H5assrcfromstg_order_h5;然后按业务主键判断selectorder_id,count(*)asrec_cntfromstg_order_allgroupbyorder_idhavingcount(*)1;如果最终业务确定“同一个 order_id 只保留一份”那我更愿意在主键层显式处理而不是直接依赖union的整行去重语义。一个简单的对照表业务问题我更倾向的写法原因两边明确互斥UNION ALL不额外吞记录两边可能重叠但规则未定先 UNION ALL 再分析先保留现场信息需要按业务主键去重先拼接再按主键处理语义更清楚只关心整行唯一值UNION适合整行集合语义一个批检查脚本示意#!/bin/bashDBHOST192.0.2.115DBPORT5258DBNAMEdw_mergeDBUSERmerge_userLOGDIR/data/gbase/log/union_checkDAYSTR$(date%F)mkdir-p${LOGDIR}gccli-h${DBHOST}-P${DBPORT}-u${DBUSER}${DBNAME}SQL${LOGDIR}/union_check_${DAYSTR}.log21select 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 里这类问题时一个很明显的感受是union和union all最大的差别不是性能层面的争论而是你到底把重复记录当成什么。真正落到现场时先把业务重复、整行重复和主键重复分开再决定用哪一种写法通常能比事后追报表偏差省很多时间。参考资料[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