以
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 替代) |