FULL OUTER JOIN 和 LEFT JOIN 的区别 | 使用 UNION ALL 替代 FULL OUTER JOIN 有什么好处

JOIN 类型对比

  1. INNER JOIN(内连接)
SELECT * FROM table_a a INNER JOIN table_b b ON a.id = b.id

结果:只返回两个表中都存在的记录 交集部分

  1. LEFT JOIN(左外连接)
SELECT * FROM table_a a LEFT JOIN table_b b ON a.id = b.id

结果:返回左表的所有记录,右表匹配则显示,不匹配则右表字段为NULL。 左表全部 + 右表匹配的部分

  1. RIGHT JOIN(右外连接)
SELECT * FROM table_a a RIGHT JOIN table_b b ON a.id = b.id

结果:返回右表的所有记录,左表匹配则显示,不匹配则左表字段为NULL。右表全部 + 左表匹配的部分

  1. FULL OUTER JOIN(全外连接)
SELECT * FROM table_a a FULL OUTER JOIN table_b b ON a.id = b.id

结果:返回两个表的所有记录,无论是否匹配。左表全部 + 右表全部

实际例子演示

假设有两个表:
table_a(手动编码数据)

time_point | manual_count
-----------|-------------
2025-10-21 |     100
2025-10-23 |     200

table_b(AI生成数据)

time_point | ai_count
-----------|----------
2025-10-22 |   1000
2025-10-23 |   2000
2025-10-24 |   3000

使用 LEFT JOIN:

SELECT 
    a.time_point,
    a.manual_count,
    b.ai_count
FROM table_a a
LEFT JOIN table_b b ON a.time_point = b.time_point

结果:

time_point | manual_count | ai_count
-----------|--------------|----------
2025-10-21 |     100      |   NULL    ← 手动编码的日期,无AI数据
2025-10-23 |     200      |   2000    ← 两者都有的日期

使用 FULL OUTER JOIN

SELECT 
    COALESCE(a.time_point, b.time_point) as time_point,
    a.manual_count,
    b.ai_count
FROM table_a a
FULL OUTER JOIN table_b b ON a.time_point = b.time_point

结果:

time_point | manual_count | ai_count
-----------|--------------|----------
2025-10-21 |     100      |   NULL    ← 只有手动编码
2025-10-22 |     NULL     |   1000    ← 只有AI生成  
2025-10-23 |     200      |   2000    ← 都有
2025-10-24 |     NULL     |   3000    ← 只有AI生成

案例中FULL OUTER JOIN 中使用 COALESCE(a.time_point, b.time_point) 问题点

问题描述:
如果某一行中 a.time_point 和 b.time_point 都为 NULL,那么 COALESCE(a.time_point, b.time_point) 结果也会是 NULL。这在语义上意味着这一行的时间信息完全缺失。

解决思路:
使用「日期维度表」+ LEFT JOIN 确保每一天都有数据点

clickhouse sql demo:
日期维度的基础 CTE 来完成 “LEFT JOIN” 补全时间的目的

WITH 
startDate := '2025-10-20',
endDate := '2025-10-25',

date_range AS (
    SELECT 
        addDays(toDate(startDate), number) AS time_point
    FROM numbers(dateDiff('day', toDate(startDate), toDate(endDate)) + 1)
)

SELECT 
    d.time_point,
    a.manual_count,
    b.ai_count
FROM date_range d
LEFT JOIN table_a a ON d.time_point = a.time_point
LEFT JOIN table_b b ON d.time_point = b.time_point
ORDER BY d.time_point;

使用 UNION ALL 替代 FULL OUTER JOIN 有什么好处

UNION ALL 会将两张表的所有记录合并在一起,包括重复的记录。它不会去除重复项,也不会进行任何匹配。具体来说:

  • 表 A 的所有记录会被列出。
  • 表 B 的所有记录会被列出。
  • 如果表 A 和表 B 中有相同的记录,它们会在结果中重复出现。

UNION ALL 允许分别查询两个表,然后将结果集合并,而无需复杂的合并逻辑,并且在结果集较大时可以显著减少资源消耗。

FULL OUTER JOIN 在 ClickHouse 中的支持相对较弱,某些版本甚至不支持。

  1. 性能优势

UNION ALL:
* 不需要进行连接操作,各个子查询可以并行执行(每个 UNION 分支可以独立执行,减少锁等待时间)
* 没有 JOIN 的匹配开销,直接合并结果集
* 对于大数据量,性能通常更好
FULL OUTER JOIN:
* 需要在两个表之间进行匹配操作
* 可能产生笛卡尔积的风险
* 需要额外的内存来维护 JOIN 的哈希表或排序

  1. 更好的分布式性能
-- 在分布式表上,UNION ALL 避免了全局 JOIN 的网络开销
-- 每个分支可以在本地节点执行,减少数据传输

SELECT * FROM dist_table_a WHERE condition_a
UNION ALL  
SELECT * FROM dist_table_b WHERE condition_b
  1. 可预测的结果:
    UNION ALL 操作简单地将两个结果集垂直堆叠起来,生成的结果集更易于理解,而 FULL OUTER JOIN 的行为可能更复杂,并且难以预测。

  2. 灵活性:
    UNION ALL 允许用户对每个表的查询进行优化,例如对每个表应用不同的筛选条件,然后再将结果合并。

  3. 使用 UNION ALL ,添加新的维度非常简单:

manual_row AS (...),
ai_rows AS (...),
other_rows AS (...)  -- 新增维度

SELECT * FROM manual_row
UNION ALL
SELECT * FROM ai_rows
UNION ALL
SELECT * FROM other_rows  -- 轻松扩展

选择 UNION ALL 的信号

选择 UNION ALL 的信号:

  1. 逻辑互斥:数据来自不同的业务分类
    例如:手动 vs AI、付费 vs 免费、线上 vs 线下

  2. 结构相同:子查询的列数、列类型完全一致
    必须保证 SELECT 的字段顺序和类型匹配

  3. 无需匹配:不需要根据某个字段关联两个数据集
    只是简单地"堆叠"数据

  4. 性能敏感:大数据量场景
    UNION ALL 不去重,比 UNION 快
    避免 JOIN 的哈希表开销

不要用 UNION ALL 的信号:

  1. 需要匹配:数据需要根据某个键关联
  2. 需要去重:合并后的数据可能有重复
  3. 列结构不匹配:子查询的列数或类型不一致

总结
UNION ALL 的核心使用场景:
✅ 异构数据合并:不同来源/分类的数据(例如:同一个表不同的事件类型,数据通过 WHERE 条件分成两类,彼此不重叠。关键点:同一行数据不会同时满足这两个条件
✅ 时间分区合并:跨时间段查询
✅ 汇总+明细:既要分组汇总,又要展示总计
✅ 多表结构相似:不同业务实体的统一视图
✅ A/B测试:实验分组数据对比
关键判断标准:
数据是否逻辑互斥、不需要匹配?→ UNION ALL
数据是否需要根据某字段关联?→ JOIN

既然都在同一个表,为什么不直接用一个查询 + CASE WHEN?
一般是因为下面的原因:

  • WHERE 条件冲突
  • 聚合逻辑不同
  • 性能问题
    – 使用 CASE WHEN 会扫描整个表
    – 使用 UNION ALL 可以针对不同条件优化索引
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西京刀客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值