PostgreSQL Semi-Join(半连接)通俗讲解

Semi-Join(半连接)通俗讲解

本文用通俗的方式解释数据库里的 Semi-Join(半连接) ,并说明为什么 IN (子查询) 里通常 不需要手动加 DISTINCT 。

文中所有表名、字段名均为脱敏示例,与实际项目无关。


1. 一句话理解

Semi-Join(半连接) :判断「左表的每一行,在右表里是否至少存在一条匹配」。

  • 存在 → 保留这一行
  • 不存在 → 丢弃这一行

关键点:只关心「有没有」,不关心「有几条」。左表每行最多输出一次,右表匹配到几条都不会让左表这行变多。

WHERE ... IN (子查询) 和 WHERE EXISTS (子查询) 都会被数据库优化成 Semi-Join。


2. 用生活例子理解

假设有两张表(脱敏示例):

student(学生表)

student_id name
1 小明
2 小红
3 小刚

exam_record(考试记录表)

record_id student_id subject
101 1 数学
102 1 英语
103 1 物理
104 2 数学

现在问题是:「哪些学生参加过考试?」

sql 复制代码
SELECT *
FROM student
WHERE student_id IN (SELECT student_id FROM exam_record);

结果:

student_id name
1 小明
2 小红
  • 小明(id=1)在考试记录里出现了 3 次 ,但结果里只出现 1 次。
  • 小刚(id=3)没有任何考试记录,被丢弃。

这就是 Semi-Join:只判断「有没有」,重复不会放大结果。


3. 对比:普通 JOIN 会放大

如果用普通 JOIN,结果会不一样:

sql 复制代码
SELECT s.*
FROM student s
JOIN exam_record e ON s.student_id = e.student_id;

结果:

student_id name
1 小明
1 小明
1 小明
2 小红

小明出现了 3 次!因为普通 JOIN 是「一对多」全部展开。

对比项 Semi-Join(IN/EXISTS) 普通 JOIN
关心匹配数量 否,只看有没有 是,全部展开
左表行会被放大吗 不会 会
右表重复的影响 无影响 会导致重复行

4. 回到核心问题:IN 子查询要不要加 DISTINCT?

看这条更新语句(脱敏示例,把「有子记录的父订单」打个标记):

sql 复制代码
UPDATE orders
SET parent_flag = 'Y'
WHERE parent_flag IS DISTINCT FROM 'Y'
AND order_id IN (
    SELECT parent_order_id          -- 这里会有大量重复值
    FROM orders
    WHERE version = 'v1'
    AND child_flag = 'Y'
    AND parent_order_id IS NOT NULL
);

子查询里的 parent_order_id 确实会大量重复(很多子订单指向同一个父订单)。

那要不要写成 SELECT DISTINCT parent_order_id?答案:不需要。

为什么不需要?

  1. 语义上不会出错

    IN 是 Semi-Join,order_id 只要在子查询里「存在」就更新,存在 1 次和存在 100 次效果完全一样。外层 order_id 是唯一的,最多被更新一次。重复值不会造成重复更新。

  2. 加 DISTINCT 反而更慢

    • 加了 DISTINCT,数据库要额外做一步去重(排序或哈希聚合),必须先把子查询结果全部算出来再去重。
    • 不加 DISTINCT,Semi-Join 可以「边扫描边匹配、找到一条就停」,通常更快。

一句话总结

IN / EXISTS 子查询里的重复值,数据库会自动帮你「当作存在性判断」处理,手动 DISTINCT 是多余的,还可能拖慢速度。


5. 什么时候才真的需要去重?

只有当你用的是 普通 JOIN,而且是「一对多」关系时,才需要担心重复放大。例如:

sql 复制代码
-- 这种 JOIN 会因为一对多而放大行数
SELECT a.record_id, b.material
FROM child_table a
JOIN parent_table b ON b.record_id = a.parent_id;

如果一个父记录对应多个子记录,这里就会产生重复行,可能需要 DISTINCT 或改写。

但 IN (子查询) 不属于这种情况,不用担心。


6. 快速记忆卡

场景 要不要 DISTINCT 原因
WHERE x IN (SELECT y ...) ❌ 不要 Semi-Join 自动处理存在性,重复无影响
WHERE EXISTS (...) ❌ 不要 同上,找到一条就短路
JOIN(一对多) ✅ 可能要 普通 JOIN 会放大行数

7. 小结

  • Semi-Join = 存在性判断:左表每行只看「右表有没有匹配」,不看有几条。
  • IN 和 EXISTS 都是 Semi-Join,天然去重,重复子查询值不会放大结果、不会重复更新。
  • 手动加 DISTINCT 在 IN 子查询里通常是负优化。
  • 只有普通 JOIN 的一对多场景才需要考虑去重。
相关推荐
updayday8548 分钟前
印染产线电脑进程异常占用资源却要逐台排查 - Ping64 远程进程管理
大数据·网络·数据库·安全·智能路由器
QYRdata20 分钟前
年复合增长率8.2%!2026-2032年数据库开发服务赛道增长动能持续释放
数据库·服务发现·数据库开发·恒州博智
坊钰21 分钟前
【LangGraph框架入门级】12. 人机交互:interrupt 中断与 Command 恢复
开发语言·数据库·python·人机交互·ai编程·langgraph
龙腾AI白云27 分钟前
世界模型与智能体协同推理逻辑
大数据·数据库·知识图谱·pygame
ben9518chen38 分钟前
数据库系统概述
数据库
花间相见44 分钟前
【计算基础|MySQL03】【上手】多表查询与聚合
数据库·mysql
小羊没烦恼!1 小时前
T-SQL查询高级—SQL Server索引中的碎片和填充因子
开发语言·前端·javascript·数据库·后端·python·web安全
lv__pf1 小时前
redis【TL redis(6)】
数据库·redis·缓存
冰暮流星1 小时前
mysql索引失效的情形
数据库·笔记·sql·mysql