📊 SQL 入门 Day 6:多表查询 — JOIN 的四种姿势

📌 一句话总结

JOIN 是 SQL 跨表查数据的核心机制,通过关联条件把多张表的行拼成结果集。掌握 INNER/LEFT/RIGHT/FULL JOIN 四种类型,覆盖 95% 的多表查询需求。


1. 四类 JOIN 速览

sql 复制代码
-- 假设有两张表:
-- employees: id, name, dept_id
-- departments: id, dept_name

-- INNER JOIN:两表都有才返回(交集)
SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;

-- LEFT JOIN:左表全保留,右表没有就 NULL
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;

-- RIGHT JOIN:右表全保留,左表没有就 NULL(很少用,LEFT 反过来就好)
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.id;

-- FULL JOIN:两表都保留,没有就 NULL(PG/MySQL 8+ 支持,SQLite 不支持)
SELECT e.name, d.dept_name
FROM employees e
FULL JOIN departments d ON e.dept_id = d.id;
JOIN 类型 左表有 右表有 结果
INNER ✓ 返回
INNER
INNER
LEFT ✓ 右表为 NULL
LEFT
RIGHT ✓ 左表为 NULL
FULL ✓ 右表为 NULL
FULL ✓ 左表为 NULL

2. INNER JOIN --- 最常用

sql 复制代码
-- 查员工及其所属部门名
SELECT
    e.employee_id,
    e.name,
    d.department_name,
    d.location
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;

-- 三表 JOIN:员工 → 部门 → 项目
SELECT
    e.name,
    d.department_name,
    p.project_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
INNER JOIN projects p ON e.employee_id = p.lead_id;

ON vs USING

如果关联的列名相同,可以用 USING 简化:

sql 复制代码
-- 列名相同(都是 department_id)
SELECT e.name, d.department_name
FROM employees e
INNER JOIN departments d USING (department_id);

-- 等价写法
SELECT e.name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;

⚠️ USING 会把关联列合并为一个,ON 则保留两个列。


3. LEFT JOIN --- 查"有/无"

sql 复制代码
-- 查所有员工及其部门(没有部门的员工也显示)
SELECT e.name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;

-- 技巧:找"没有部门"的员工(左表有,右表没有)
SELECT e.name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
WHERE d.department_id IS NULL;

-- 找"没有员工"的部门
SELECT d.department_name
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
WHERE e.employee_id IS NULL;

LEFT JOIN ... IS NULL 是非常经典的"找缺失"模式,等价于 NOT EXISTS


4. RIGHT JOIN --- 对称写法

sql 复制代码
-- RIGHT JOIN 只是 LEFT 的反向,通常直接用 LEFT 就行
-- 下面两个等价:
SELECT e.name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id;

-- 等价 LEFT 写法(推荐)
SELECT e.name, d.department_name
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id;

💡 习惯: 只用 LEFT JOIN 就够了,把主表放左边,副表放右边。RIGHT JOIN 除了在特定自动生成的 SQL 中外很少主动使用。


5. FULL JOIN --- 全保留

sql 复制代码
-- PostgreSQL / MySQL 8+
SELECT
    COALESCE(e.name, '无员工') AS employee,
    COALESCE(d.department_name, '无部门') AS department
FROM employees e
FULL JOIN departments d ON e.department_id = d.department_id;

-- SQLite 模拟 FULL JOIN(不支持)
SELECT e.name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
UNION
SELECT e.name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id;

6. 自连接 --- 同一张表的 JOIN

sql 复制代码
-- 查员工及其上级
SELECT
    e.name AS employee,
    m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;

-- 查同部门的同事对
SELECT
    a.name AS emp1,
    b.name AS emp2,
    a.department_id
FROM employees a
INNER JOIN employees b
    ON a.department_id = b.department_id
   AND a.employee_id < b.employee_id;  -- 去重:只保留 (A,B) 不保留 (B,A)

自连接的核心要点:必须用别名,否则无法区分。


7. 多表 JOIN 的顺序与性能

sql 复制代码
-- 4 表 JOIN
SELECT
    o.order_id,
    c.customer_name,
    e.name AS sales_rep,
    p.product_name,
    oi.quantity
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN employees e ON o.sales_rep_id = e.employee_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id;

性能要点

原则 说明
小表驱动大表 JOIN 的顺序影响性能(优化器会重排,但建议手动优先小表)
关联列建索引 JOIN 的 ON 条件列必须有索引
避免 JOIN 太多表 超过 5 个 JOIN 可读性和性能都会变差,考虑反范式或视图
先过滤再 JOIN 用子查询或 CTE 先缩小结果再 JOIN
sql 复制代码
-- 先过滤再 JOIN 的优化写法
SELECT e.name, d.department_name
FROM employees e
INNER JOIN (
    SELECT department_id, department_name
    FROM departments
    WHERE status = 'active'
) d ON e.department_id = d.department_id
WHERE e.status = '在职';

8. 实战:多表查询完整场景

sql 复制代码
-- 场景:生成 2024 年销售报表
SELECT
    c.customer_name,
    COUNT(o.order_id) AS order_count,
    SUM(o.total_amount) AS total_spent,
    MAX(o.order_date) AS last_order
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2024-01-01'
  AND o.status = '已完成'
GROUP BY c.customer_id, c.customer_name
HAVING COUNT(o.order_id) >= 3
ORDER BY total_spent DESC
LIMIT 10;

📝 要点总结

JOIN 类型 场景 记忆口诀
INNER JOIN 两表都有才要 交集
LEFT JOIN 主表全保留 左表为王
RIGHT JOIN 副表全保留 尽量换 LEFT
FULL JOIN 两表都保留 全集
自连接 同一张表内关联 必须用别名
ON 关联条件 列名不同时用
USING 关联列同名 列名相同时用
LEFT JOIN + IS NULL 找缺失数据 经典"不存在"查询
相关推荐
2601_9620738110 小时前
Spring全面详解(基础版)
java·后端·spring
IT_陈寒10 小时前
Vite打包时的静态资源坑,我帮你踩过了
前端·人工智能·后端
考虑考虑10 小时前
kubectl命令
运维·后端·自动化运维
第五页的你11 小时前
ElasticSearch结合本地消息表实现搜索功能
后端
谢亮_vipxieliang11 小时前
ValidX分组验证详解:不同场景使用不同验证规则
java·spring boot·后端·spring cloud·eclipse·hibernate
超兔一体云11 小时前
MySQL索引优化实战——从慢查询到索引调优
后端·mysql
. . . . .12 小时前
服务端监控
后端
凤山老林12 小时前
Spring Boot 集成 Spring Vault:集中式密钥管理与动态凭证轮换
spring boot·后端·spring·vault·集中式秘钥管理
雨夜之寂12 小时前
雨夜-现在有办法识别是不是ai文章么
后端·面试
2601_9620711712 小时前
八. Spring Boot2 整合连接 Redis(超详细剖析)
spring boot·redis·后端