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 = 存在性判断:左表每行只看「右表有没有匹配」,不看有几条。
  • INEXISTS 都是 Semi-Join,天然去重,重复子查询值不会放大结果、不会重复更新。
  • 手动加 DISTINCTIN 子查询里通常是负优化
  • 只有普通 JOIN 的一对多场景才需要考虑去重。
相关推荐
NiceCloud喜云1 小时前
腾讯云国际版云数据库选型:MySQL、Redis 怎么按业务架构来判断
数据库·mysql·腾讯云
ew452182 小时前
MySQL JOIN 关联语法、差异、标准用法、经典坑点汇总
数据库·mysql
Rain的Java大神之路2 小时前
线上接口负载满了如何解决
java·数据库·redis·后端·缓存·面试·架构
Nturmoils2 小时前
给运营导个 CSV,在 ksql 里用 \copy 就够了
数据库
ciqingloveless2 小时前
Oracle打开数据库加密
数据库·oracle
m0_462605223 小时前
大模型week02
数据库
DBA_G3 小时前
GBase 8a数据库集群多维度资源管控策略矩阵解析
数据库·oracle
逐米时代4 小时前
智能招聘与人岗匹配:可解释匹配让录用依据有迹可循
大数据·数据库·人工智能
承渊政道4 小时前
KES专业技能包发布:覆盖数据库开发、迁移与运维全流程
运维·数据库·gitee·数据库开发·金仓数据库