本文将全面深入讲解 MySQL 多表查询的核心内容,包括 INNER JOIN
、LEFT JOIN
、RIGHT JOIN
、FULL OUTER JOIN
(模拟实现)、自连接、UNION
等,通过大量示例与实战分析,帮助你全面掌握表与表之间的联动查询技巧。
在数据库设计中,为了避免数据冗余和提升可维护性,通常会采用 三范式设计。这会将重复出现的数据抽离成独立的表,比如:
employees
表中;departments
表中;salaries
表中。这就意味着我们在查询“员工所属部门名”、“员工工资情况”时必须进行多表关联。
SELECT A.*, B.*
FROM tableA A
INNER JOIN tableB B
ON A.common_field = B.common_field;
关键点:
SELECT e.employee_id, e.first_name, d.department_name
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id;
SELECT e.employee_id, e.first_name, d.department_name, j.job_title
FROM employees e
JOIN departments d ON e.department_id = d.department_id
JOIN jobs j ON e.job_id = j.job_id;
SELECT A.*, B.*
FROM tableA A
LEFT JOIN tableB B
ON A.common_field = B.common_field;
关键点:
NULL
。SELECT e.employee_id, e.first_name, d.department_name
FROM employees e
LEFT JOIN departments d
ON e.department_id = d.department_id;
SELECT A.*, B.*
FROM tableA A
RIGHT JOIN tableB B
ON A.common_field = B.common_field;
关键点:
NULL
。SELECT e.employee_id, d.department_name
FROM employees e
RIGHT JOIN departments d
ON e.department_id = d.department_id;
MySQL 原生不支持 FULL OUTER JOIN
,但我们可以通过 UNION
手动模拟。
SELECT e.employee_id, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
UNION
SELECT e.employee_id, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id;
适用于需要显示所有员工和所有部门,即便他们之间没有关系。
自连接是将同一张表看作两张表来使用,通常用于有层级结构的数据。
SELECT e.employee_id, e.first_name, m.first_name AS manager_name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.employee_id;
e
表示普通员工;m
表示其上级经理。SELECT a.department_name AS child, b.department_name AS parent
FROM departments a
LEFT JOIN departments b
ON a.parent_id = b.department_id;
当多个查询结果具有相同字段结构,可以用 UNION
或 UNION ALL
合并。
SELECT employee_id FROM employees WHERE department_id = 10
UNION
SELECT employee_id FROM employees WHERE department_id = 20;
特点:自动去重
SELECT employee_id FROM employees WHERE department_id = 10
UNION ALL
SELECT employee_id FROM employees WHERE department_id = 20;
特点:不去重,性能更优
SELECT e.first_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;
SELECT e.first_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE d.location_id = 1000;
SELECT e.employee_id, e.first_name, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
ORDER BY e.hire_date DESC
LIMIT 10 OFFSET 20;
SELECT e.*
FROM employees e
JOIN (
SELECT department_id, MAX(hire_date) AS latest_hire
FROM employees
GROUP BY department_id
) t
ON e.department_id = t.department_id AND e.hire_date = t.latest_hire;
SELECT d.department_id, d.department_name
FROM departments d
LEFT JOIN employees e
ON d.department_id = e.department_id
WHERE e.employee_id IS NULL;
SELECT name, 'customer' AS source FROM customers
UNION
SELECT name, 'supplier' AS source FROM suppliers;
技巧 | 建议或说明 |
---|---|
使用 EXPLAIN 分析查询计划 | 查看 JOIN 顺序、索引使用情况 |
建立适当索引 | 特别是在 JOIN 条件字段、过滤字段、排序字段上 |
尽量避免 JOIN N 多表 | 超过 5 表连接需评估性能与复杂度 |
使用 LIMIT 分页时避免全表扫描 | 可用延迟关联或游标分页方式加速 |
避免 SELECT * | 精准列出所需字段,提高性能 |
使用 UNION ALL 替代 UNION | 如果业务不要求去重,UNION ALL 性能更好 |
INNER JOIN
:找出两个表中匹配的部分;LEFT JOIN
/ RIGHT JOIN
:包含一边全部记录;自连接
:处理树状结构或自身关系;UNION
/ UNION ALL
:合并多个结果集;本项目适用于后台管理系统、电商用户中心、SaaS 用户模块等场景,特别适合开发者进行实战演练与面试准备。
我们将构建一个基础版的用户管理系统,具备以下业务功能:
涉及的核心业务对象包括:用户、角色、权限、日志等。
users
)CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
status TINYINT DEFAULT 1 COMMENT '0:禁用, 1:启用',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
roles
)CREATE TABLE roles (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(255)
);
permissions
)CREATE TABLE permissions (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL UNIQUE,
code VARCHAR(50) NOT NULL UNIQUE COMMENT '用于权限标识,如 user:view'
);
user_role
)CREATE TABLE user_role (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);
role_permission
)CREATE TABLE role_permission (
role_id INT,
permission_id INT,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id),
FOREIGN KEY (permission_id) REFERENCES permissions(id)
);
login_logs
)CREATE TABLE login_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
ip_address VARCHAR(45),
login_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
INSERT INTO roles(name, description) VALUES ('admin', '系统管理员'), ('user', '普通用户');
INSERT INTO permissions(name, code) VALUES
('查看用户', 'user:view'),
('新增用户', 'user:create'),
('删除用户', 'user:delete');
-- 分配权限给角色
INSERT INTO role_permission(role_id, permission_id) VALUES
(1, 1), (1, 2), (1, 3), -- admin 拥有全部权限
(2, 1); -- user 仅能查看用户
INSERT INTO users(username, password, email) VALUES
('alice', 'hashed_pwd1', 'alice@example.com'),
('bob', 'hashed_pwd2', 'bob@example.com');
-- 分配角色
INSERT INTO user_role(user_id, role_id) VALUES
(1, 1), -- alice 为管理员
(2, 2); -- bob 为普通用户
SELECT u.id, u.username, r.name AS role
FROM users u
JOIN user_role ur ON u.id = ur.user_id
JOIN roles r ON ur.role_id = r.id
WHERE u.status = 1;
SELECT p.name, p.code
FROM users u
JOIN user_role ur ON u.id = ur.user_id
JOIN role_permission rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE u.username = 'alice';
SELECT u.username, l.ip_address, l.login_time
FROM login_logs l
JOIN users u ON l.user_id = u.id
WHERE l.login_time >= NOW() - INTERVAL 7 DAY
ORDER BY l.login_time DESC;
SELECT *
FROM users
WHERE username LIKE '%bob%'
ORDER BY created_at DESC
LIMIT 0, 10;
在角色授权或用户注册等业务流程中,可以使用事务来确保数据完整性。
START TRANSACTION;
INSERT INTO users(username, password, email) VALUES('charlie', 'hashed_pwd3', 'charlie@example.com');
SET @uid = LAST_INSERT_ID();
INSERT INTO user_role(user_id, role_id) VALUES(@uid, 2); -- 默认赋普通角色
COMMIT;
START TRANSACTION;
-- 假设某权限不存在导致失败
INSERT INTO role_permission(role_id, permission_id) VALUES(1, 999);
-- 失败时回滚
ROLLBACK;
users.username
:用于登录验证、搜索login_logs.user_id
:日志查询user_role.user_id
/ role_permission.role_id
:JOIN 优化CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_user_log ON login_logs(user_id);
EXPLAIN SELECT u.username, r.name FROM users u JOIN user_role ur ON u.id = ur.user_id JOIN roles r ON ur.role_id = r.id;
可查看索引是否使用、JOIN 类型、Rows 扫描数量等。