MySQL多表查询详解:内连接、外连接、自连接通通搞懂【MySQL系列】

MySQL多表查询详解:内连接、外连接、自连接通通搞懂【MySQL系列】

本文将全面深入讲解 MySQL 多表查询的核心内容,包括 INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN(模拟实现)、自连接、UNION 等,通过大量示例与实战分析,帮助你全面掌握表与表之间的联动查询技巧。

一、前置基础:为什么需要多表查询?1.1 多表数据结构的必要性在数据库设计中,为了避免数据冗余和提升可维护性,通常会采用 三范式设计。这会将重复出现的数据抽离成独立的表,比如:

员工信息在 employees 表中;部门信息在 departments 表中;工资记录在 salaries 表中。这就意味着我们在查询“员工所属部门名”、“员工工资情况”时必须进行多表关联。

1.2 多表查询的场景获取员工及其所属部门名称查询订单及对应的客户信息汇总不同表中的业务指标构建分页列表时拉取扩展字段二、INNER JOIN:内连接查询2.1 基本语法代码语言:javascript复制SELECT A.*, B.*

FROM tableA A

INNER JOIN tableB B

ON A.common_field = B.common_field;关键点:

只返回“两个表中都存在匹配关系”的记录。2.2 示例:查询员工及其部门名称代码语言:javascript复制SELECT e.employee_id, e.first_name, d.department_name

FROM employees e

INNER JOIN departments d

ON e.department_id = d.department_id;2.3 多表内连接代码语言:javascript复制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;三、LEFT JOIN:左外连接3.1 基本语法代码语言:javascript复制SELECT A.*, B.*

FROM tableA A

LEFT JOIN tableB B

ON A.common_field = B.common_field;关键点:

返回左表全部记录;若右表无匹配,则字段为 NULL。3.2 示例:查询所有员工及其部门(包括未分配的)代码语言:javascript复制SELECT e.employee_id, e.first_name, d.department_name

FROM employees e

LEFT JOIN departments d

ON e.department_id = d.department_id;四、RIGHT JOIN:右外连接4.1 基本语法代码语言:javascript复制SELECT A.*, B.*

FROM tableA A

RIGHT JOIN tableB B

ON A.common_field = B.common_field;关键点:

返回右表全部记录;若左表无匹配,则字段为 NULL。4.2 示例:列出所有部门及对应员工(包括空部门)代码语言:javascript复制SELECT e.employee_id, d.department_name

FROM employees e

RIGHT JOIN departments d

ON e.department_id = d.department_id;五、FULL OUTER JOIN:全外连接(MySQL 模拟)MySQL 原生不支持 FULL OUTER JOIN,但我们可以通过 UNION 手动模拟。

5.1 模拟实现代码语言:javascript复制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;5.2 使用场景适用于需要显示所有员工和所有部门,即便他们之间没有关系。

六、自连接(Self Join)自连接是将同一张表看作两张表来使用,通常用于有层级结构的数据。

6.1 典型应用:员工与上级的关系代码语言:javascript复制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 表示其上级经理。6.2 层级结构拓展:部门树结构查询代码语言:javascript复制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:多结果合并当多个查询结果具有相同字段结构,可以用 UNION 或 UNION ALL 合并。

7.1 UNION代码语言:javascript复制SELECT employee_id FROM employees WHERE department_id = 10

UNION

SELECT employee_id FROM employees WHERE department_id = 20;特点:自动去重

7.2 UNION ALL代码语言:javascript复制SELECT employee_id FROM employees WHERE department_id = 10

UNION ALL

SELECT employee_id FROM employees WHERE department_id = 20;特点:不去重,性能更优

八、别名、过滤、排序与分页在多表查询中的运用8.1 使用表别名提升可读性代码语言:javascript复制SELECT e.first_name, d.department_name

FROM employees e

JOIN departments d ON e.department_id = d.department_id;8.2 多表查询中的过滤(WHERE)代码语言:javascript复制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;8.3 排序与分页代码语言:javascript复制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;九、实战案例:解决真实业务问题9.1 案例1:列出每个部门的最新入职员工代码语言:javascript复制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;9.2 案例2:查询没有员工的部门(左连接 + NULL 过滤)代码语言:javascript复制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;9.3 案例3:不同表中相同字段对比(UNION)代码语言:javascript复制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 性能更好

十一、总结:掌握 JOIN,关系型数据库的灵魂INNER JOIN:找出两个表中匹配的部分;LEFT JOIN / RIGHT JOIN:包含一边全部记录;自连接:处理树状结构或自身关系;UNION / UNION ALL:合并多个结果集;多表查询是构建报表、后台管理、业务接口的基础能力;掌握它,意味着你可以从零构建数据查询引擎的核心逻辑!本项目适用于后台管理系统、电商用户中心、SaaS 用户模块等场景,特别适合开发者进行实战演练与面试准备。

一、项目背景与需求概述我们将构建一个基础版的用户管理系统,具备以下业务功能:

用户注册与登录用户角色与权限分配日志记录与用户状态追踪多条件用户查询与分页涉及的核心业务对象包括:用户、角色、权限、日志等。

二、数据库建模与表结构设计2.1 实体关系图(ER图)简要说明一位用户可以拥有多个角色(多对多)一个角色可以拥有多个权限(多对多)用户与登录日志是一对多关系2.2 用户表(users)代码语言:javascript复制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

);2.3 角色表(roles)代码语言:javascript复制CREATE TABLE roles (

id INT PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(50) NOT NULL UNIQUE,

description VARCHAR(255)

);2.4 权限表(permissions)代码语言:javascript复制CREATE TABLE permissions (

id INT PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(50) NOT NULL UNIQUE,

code VARCHAR(50) NOT NULL UNIQUE COMMENT '用于权限标识,如 user:view'

);2.5 用户-角色关联表(user_role)代码语言:javascript复制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)

);2.6 角色-权限关联表(role_permission)代码语言:javascript复制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)

);2.7 登录日志表(login_logs)代码语言:javascript复制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)

);三、数据初始化脚本3.1 插入初始角色与权限代码语言:javascript复制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 仅能查看用户3.2 插入测试用户代码语言:javascript复制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 为普通用户四、典型查询场景实现4.1 查询所有启用用户及其角色代码语言:javascript复制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;4.2 查询某用户拥有的所有权限代码语言:javascript复制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';4.3 查询最近7天登录日志代码语言:javascript复制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;4.4 用户分页查询(带关键字搜索)代码语言:javascript复制SELECT *

FROM users

WHERE username LIKE '%bob%'

ORDER BY created_at DESC

LIMIT 0, 10;五、事务控制与一致性保障在角色授权或用户注册等业务流程中,可以使用事务来确保数据完整性。

5.1 注册用户 + 分配默认角色(事务)代码语言:javascript复制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;5.2 授权失败时回滚代码语言:javascript复制START TRANSACTION;

-- 假设某权限不存在导致失败

INSERT INTO role_permission(role_id, permission_id) VALUES(1, 999);

-- 失败时回滚

ROLLBACK;六、索引优化与执行分析6.1 建议加索引字段users.username:用于登录验证、搜索login_logs.user_id:日志查询user_role.user_id / role_permission.role_id:JOIN 优化代码语言:javascript复制CREATE INDEX idx_username ON users(username);

CREATE INDEX idx_user_log ON login_logs(user_id);6.2 执行计划分析代码语言:javascript复制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 扫描数量等。

相关推荐

热血传奇白日门宝箱刷新时间及打法
365bet线上网址

热血传奇白日门宝箱刷新时间及打法

📅 08-05 👁️ 2669
朱镇模高调“示爱”张俪
365bet线上网址

朱镇模高调“示爱”张俪

📅 07-11 👁️ 8928