PostgreSQL JOIN 大白话指南

以 bp_master_data × bp_level_lock 为真实案例讲解


先建立一个直觉模型

把两张表想象成两摞扑克牌:

  • 左牌堆 = bp_master_data(全量 BP 信息,5000 张)
  • 右牌堆 = bp_level_lock_fy2627_q1(某财季的 BP 等级,只有 300 张)

不同 JOIN 就是"用不同规则把两摞牌拼在一起"。


1. INNER JOIN(普通 JOIN)--- 只要两边都有的

规则: 左边有、右边也有,才出现在结果里。两边任意一边没有 → 这行直接消失。

sql 复制代码
SELECT bmd.*, lck.bp_level
FROM bp_master_data bmd
INNER JOIN bp_level_lock lck
    ON lck.incentive_id = bmd.incentive_id
   AND lck.fiscal_year  = 'FY2627'
   AND lck.fiscal_quarter = 'Q1'

结果: 只有在 bp_level_lock_fy2627_q1 里有记录的 BP 才会出现。

没有等级记录的 BP → 直接丢掉,不出现在结果里。

复制代码
bp_master_data   bp_level_lock_fy2627_q1   结果
PA001            ✅ 有记录                 → 出现(bp_level = Gold)
PA002            ❌ 没记录                 → 消失
PA003            ✅ 有记录                 → 出现(bp_level = Silver)

适用场景: 只想看"有等级"的 BP。


2. LEFT JOIN(左连接)--- 左边全要,右边没有就填 NULL

规则: 左边的每一行必须出现在结果里。右边有就拼进来,右边没有就用 NULL 补位。

sql 复制代码
SELECT bmd.*, lck.bp_level
FROM bp_master_data bmd
LEFT JOIN bp_level_lock lck
    ON lck.incentive_id = bmd.incentive_id
   AND lck.fiscal_year  = 'FY2627'
   AND lck.fiscal_quarter = 'Q1'

结果: 全量 5000 条 BP 都出现,有等级的填 bp_level,没等级的 bp_level = NULL。

复制代码
bp_master_data   bp_level_lock_fy2627_q1   结果
PA001            ✅ 有记录                 → bp_level = Gold
PA002            ❌ 没记录                 → bp_level = NULL
PA003            ✅ 有记录                 → bp_level = Silver

适用场景: 想看全量 BP,顺便附上等级信息(没有等级也要显示这个 BP)。

这就是本次 queryMasterWithLevel 用的方式。


3. RIGHT JOIN(右连接)--- 右边全要,左边没有就填 NULL

LEFT JOIN 的镜像版。右边每一行必须出现,左边没有就 NULL 补位。

实际工作中很少用,通常把表顺序换一下改成 LEFT JOIN 更直观。


4. FULL JOIN(全外连接)--- 两边都全要,缺哪边就 NULL 补

规则: 不管左边还是右边,所有行都必须出现在结果里。对应不上的位置用 NULL 填充。

sql 复制代码
SELECT bmd.*, lck.bp_level
FROM bp_master_data bmd
FULL JOIN bp_level_lock lck
    ON lck.incentive_id = bmd.incentive_id

结果:

复制代码
bp_master_data   bp_level_lock   结果
PA001            Gold            → 两边都有,正常拼
PA002            NULL            → bp_master_data 有,lock 没有 → bp_level = NULL
NULL             Silver          → lock 有,但 bp_master_data 没有这个 incentive_id → bmd 字段全 NULL

适用场景: 数据对账、找"孤儿数据"(在 lock 表里有但 master 里找不到的 BP)。

实际业务查询用得很少。


5. CROSS JOIN --- 笛卡尔积,两边每行互相组合一遍

规则: 没有 ON 条件,左边每一行和右边每一行两两配对。

左边 M 行 × 右边 N 行 = 结果 M×N 行。

sql 复制代码
-- 如果 bp_master_data 有 5000 行,右边有 2 个财季
SELECT bmd.*, periods.fiscal_year, periods.fiscal_quarter
FROM bp_master_data bmd
CROSS JOIN (VALUES ('FY2627', 'Q1'), ('FY2526', 'Q4')) AS periods(fiscal_year, fiscal_quarter)

结果: 5000 × 2 = 10000 行,每个 BP 和每个财季都组合了一次。

复制代码
PA001  ×  FY2627 Q1  →  一行
PA001  ×  FY2526 Q4  →  一行
PA002  ×  FY2627 Q1  →  一行
PA002  ×  FY2526 Q4  →  一行
...

适用场景: 把一个小的"维度列表"(财季、大区、产品线)扩展到每一条主数据上,

然后再 LEFT JOIN 找对应的事实数据。


本次 SQL 的组合拳解析

sql 复制代码
FROM bp_master_data bmd                          -- ① 5000 条 BP 全量
CROSS JOIN (
    VALUES ('FY2627', 'Q1'), ('FY2526', 'Q4')    -- ② 2 个财季
) AS periods(fiscal_year, fiscal_quarter)
LEFT JOIN bp_level_lock lck                      -- ③ 找等级
    ON  lck.incentive_id   = bmd.incentive_id
    AND lck.fiscal_year    = periods.fiscal_year
    AND lck.fiscal_quarter = periods.fiscal_quarter

执行逻辑三步走:

复制代码
第①步  CROSS JOIN
       bp_master_data(5000) × periods(2) = 10000 行
       每个 BP 被复制了 2 份,每份对应一个财季

第②步  LEFT JOIN bp_level_lock
       对每一行,去 bp_level_lock 找匹配的 (incentive_id, fiscal_year, fiscal_quarter)
       找到 → 填上 bp_level
       没找到 → bp_level = NULL

第③步  输出 10000 行结果
       每个 BP × 每个请求财季 = 一行

为什么要先 CROSS JOIN 再 LEFT JOIN,而不是直接 LEFT JOIN?

因为需求是"给我 FY2627Q1 和 FY2526Q4 两个财季的数据"------如果只用 LEFT JOIN,

你没有办法在一条 SQL 里同时为每个 BP 生成两行(一行对应 Q1,一行对应 Q4)。

CROSS JOIN 先把财季维度"展开",LEFT JOIN 再把等级"附上去",两步合一。


一张图总结

复制代码
数据集 A = {1, 2, 3}
数据集 B = {2, 3, 4}

INNER JOIN     → {2, 3}           只保留两边都有的
LEFT JOIN      → {1, 2, 3}        A 全要,B 没有的填 NULL
RIGHT JOIN     → {2, 3, 4}        B 全要,A 没有的填 NULL
FULL JOIN      → {1, 2, 3, 4}     两边都全要,缺的填 NULL
CROSS JOIN     → {1×2, 1×3, 1×4,  两两组合,无条件,结果 = 3×3 = 9 行
                  2×2, 2×3, 2×4,
                  3×2, 3×3, 3×4}

实际选择建议

场景 用哪个
只要两边都有的数据 INNER JOIN
左表全量 + 右表补充信息(可为空) LEFT JOIN ✅ 最常用
数据对账,找两边的差异 FULL JOIN
把一个小维度列表展开到大表每一行 CROSS JOIN
右表全量 + 左表补充 RIGHT JOIN(可以改成 LEFT JOIN 替代)
相关推荐
ss2734 小时前
03 -AI时代首先数据库-PostgreSQL发展历史:从伯克利到AI数据库
postgresql
东风微鸣8 小时前
精简 Ubuntu Linux 固件包:每个 N100 节点省下约 450 MB
linux·ubuntu·postgresql
yumgpkpm19 小时前
CDP 7.3.1(Cloudera Runtime 7.3.1)VS Acceldata ODP 3.3.6.4 核心引擎详细版本对比
大数据·hive·hadoop·postgresql·zookeeper·spark·hbase
明日清晨19 小时前
mobaxterm root身份登录Ubuntu
linux·ubuntu·postgresql
有想法的py工程师1 天前
PostgreSQL【内核】 CBO 优化器的“早停下注”与数学模型失效分析
数据库·postgresql
有想法的py工程师1 天前
PostgreSQL 字符集双重转义击穿 磁盘 I/O
数据库·postgresql
l1t1 天前
DeepSeek总结的PostgreSQL因选择而宽松,因偶然而永久
数据库·postgresql
IvorySQL1 天前
PostgreSQL 日报| PostgreSQL JDBC 安全更新(10 月 10 日)
数据库·postgresql·区块链
IvorySQL2 天前
PostgreSQL 日报|修复截断 zstd 备份检测(10 月 9 日)
数据库·postgresql
余槐i2 天前
200万行表加索引锁死写入:PostgreSQL CONCURRENTLY 的三类等待与重建路径
数据库·postgresql·性能优化·索引·explain