本文將深入探討 MySQL 高級查詢技巧,重點講解 GROUP BY
、HAVING
、各種聚合函數、子查詢以及分頁查詢(LIMIT
語法)的使用。文章內容涵蓋實際應用中最常見的報表需求和分頁實現技巧,適合有一定 SQL 基礎的開發者進一步提升技能。
一、前置知識回顧
在進入高級部分之前,我們先簡要回顧一些 SQL 查詢的基本組成部分,便于后續內容的理解:
SELECT [字段列表]
FROM [表名]
WHERE [條件]
GROUP BY [分組字段]
HAVING [聚合條件]
ORDER BY [排序字段]
LIMIT [偏移量, 行數]
二、GROUP BY 分組查詢
2.1 基本語法
GROUP BY
用于將查詢結果按某個或某些字段進行分組。配合聚合函數(如 COUNT()
、SUM()
、AVG()
等)使用,可以實現對每個分組的統計。
示例:統計每個部門的員工數量
SELECT department_id, COUNT(*) AS employee_count
FROM employees
GROUP BY department_id;
2.2 多字段分組
示例:統計每個部門中每個職位的員工數量
SELECT department_id, job_id, COUNT(*) AS employee_count
FROM employees
GROUP BY department_id, job_id;
多字段分組適用于需要“交叉”維度分析的場景,比如不同區域+不同產品的銷售統計。
三、聚合函數詳解
聚合函數用于對一組數據進行計算,常用于 GROUP BY
分組后。
函數 | 說明 |
---|---|
COUNT() | 統計數量 |
SUM() | 求和 |
AVG() | 平均值 |
MAX() | 最大值 |
MIN() | 最小值 |
GROUP_CONCAT() | 將組內字段連接為字符串 |
3.1 COUNT()
SELECT department_id, COUNT(*) AS total_employees
FROM employees
GROUP BY department_id;
3.2 SUM()
SELECT department_id, SUM(salary) AS total_salary
FROM employees
GROUP BY department_id;
3.3 GROUP_CONCAT()
SELECT department_id, GROUP_CONCAT(first_name) AS employee_names
FROM employees
GROUP BY department_id;
GROUP_CONCAT()
在報表中經常用于“拼接多個名稱為一列”,如列出參與某個項目的所有人名。
四、HAVING:對分組后的結果進行過濾
4.1 區別 WHERE 與 HAVING
WHERE
是對 原始數據 進行篩選HAVING
是對 分組后的結果 進行篩選
4.2 示例:只顯示員工數大于5的部門
SELECT department_id, COUNT(*) AS employee_count
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 5;
4.3 使用別名
雖然在 SELECT
中定義了別名 employee_count
,但在 HAVING
中引用聚合函數更安全。
HAVING COUNT(*) > 5 -- 推薦
-- HAVING employee_count > 5 -- 有些版本不支持
五、子查詢的多種用法
子查詢是指嵌套在主查詢內部的 SELECT 查詢。可以出現在 SELECT、FROM、WHERE 等多個位置。
5.1 SELECT 中的子查詢
示例:查詢每位員工的平均工資差值
SELECT employee_id, salary,salary - (SELECT AVG(salary) FROM employees) AS diff_from_avg
FROM employees;
5.2 WHERE 中的子查詢
示例:查詢工資高于公司平均值的員工
SELECT *
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
5.3 FROM 中的子查詢(內聯視圖)
用于將子查詢臨時當作一個“表”來使用。
SELECT department_id, avg_salary
FROM (SELECT department_id, AVG(salary) AS avg_salaryFROM employeesGROUP BY department_id
) AS dept_avg
WHERE avg_salary > 10000;
5.4 IN/NOT IN 子查詢
示例:查找至少有一位員工的部門
SELECT department_id, department_name
FROM departments
WHERE department_id IN (SELECT DISTINCT department_id FROM employees
);
六、分頁查詢(LIMIT)詳解
在構建分頁接口或展示數據列表時,LIMIT
是非常關鍵的 SQL 工具。
6.1 LIMIT 基本用法
SELECT * FROM employees
LIMIT 10; -- 取前10條
6.2 LIMIT + OFFSET 用法
SELECT * FROM employees
LIMIT 10 OFFSET 20; -- 從第21條開始,取10條
等價寫法:
SELECT * FROM employees
LIMIT 20, 10;
6.3 用于分頁接口的實現
-- page = 3, pageSize = 10
SELECT * FROM employees
ORDER BY hire_date DESC
LIMIT 20, 10;
分頁核心邏輯:LIMIT (page - 1) * pageSize, pageSize
七、常見報表需求實踐
以下為結合 GROUP BY
、聚合函數、子查詢與分頁的常見報表查詢場景。
7.1 部門月度工資支出報表
SELECT department_id, DATE_FORMAT(hire_date, '%Y-%m') AS month,SUM(salary) AS total_salary
FROM employees
GROUP BY department_id, month
ORDER BY department_id, month;
7.2 Top N 查詢(工資最高的前3名員工)
SELECT employee_id, first_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;
7.3 每個部門工資最高的員工(相關子查詢)
SELECT *
FROM employees e
WHERE salary = (SELECT MAX(salary)FROM employeesWHERE department_id = e.department_id
);
八、分頁優化技巧
分頁性能在大數據量下成為瓶頸,以下是常見優化方法。
8.1 使用覆蓋索引加速分頁
-- 僅查詢主鍵或索引字段
SELECT employee_id
FROM employees
ORDER BY hire_date DESC
LIMIT 100000, 10;
8.2 延遲關聯分頁
-- 第一步:查主鍵
SELECT employee_id
FROM employees
ORDER BY hire_date DESC
LIMIT 100000, 10;-- 第二步:再查詳情
SELECT *
FROM employees
WHERE employee_id IN (…);
8.3 使用 ID 游標分頁(適合不斷增長的主鍵)
-- 假設上次最后一條ID是 120
SELECT * FROM employees
WHERE employee_id > 120
ORDER BY employee_id
LIMIT 10;
九、總結
技巧 | 應用場景 |
---|---|
GROUP BY | 數據分組統計、分類匯總 |
聚合函數 | 報表、指標計算(如總數、平均值等) |
HAVING | 分組結果過濾 |
子查詢 | 復雜條件過濾、嵌套數據分析 |
LIMIT | 分頁列表、Top N 取值 |
分頁優化 | 大數據分頁響應慢時的優化方案 |
實戰建議:
- 分組前過濾用 WHERE,分組后過濾用 HAVING
- 復雜統計盡量使用子查詢或視圖,保持主查詢簡潔
- 分頁查詢在大數據場景下需優化 LIMIT 的性能
- GROUP_CONCAT 適合小量數據展示,不宜用于大表
本項目適用于后臺管理系統、電商用戶中心、SaaS 用戶模塊等場景,特別適合開發者進行實戰演練與面試準備。
一、項目背景與需求概述
我們將構建一個基礎版的用戶管理系統,具備以下業務功能:
- 用戶注冊與登錄
- 用戶角色與權限分配
- 日志記錄與用戶狀態追蹤
- 多條件用戶查詢與分頁
涉及的核心業務對象包括:用戶、角色、權限、日志等。
二、數據庫建模與表結構設計
2.1 實體關系圖(ER圖)簡要說明
- 一位用戶可以擁有多個角色(多對多)
- 一個角色可以擁有多個權限(多對多)
- 用戶與登錄日志是一對多關系
2.2 用戶表(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
);
2.3 角色表(roles
)
CREATE TABLE roles (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50) NOT NULL UNIQUE,description VARCHAR(255)
);
2.4 權限表(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'
);
2.5 用戶-角色關聯表(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)
);
2.6 角色-權限關聯表(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)
);
2.7 登錄日志表(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)
);
三、數據初始化腳本
3.1 插入初始角色與權限
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 插入測試用戶
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 查詢所有啟用用戶及其角色
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 查詢某用戶擁有的所有權限
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天登錄日志
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 用戶分頁查詢(帶關鍵字搜索)
SELECT *
FROM users
WHERE username LIKE '%bob%'
ORDER BY created_at DESC
LIMIT 0, 10;
五、事務控制與一致性保障
在角色授權或用戶注冊等業務流程中,可以使用事務來確保數據完整性。
5.1 注冊用戶 + 分配默認角色(事務)
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 授權失敗時回滾
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 優化
CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_user_log ON login_logs(user_id);
6.2 執行計劃分析
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 掃描數量等。
更多推薦【MySQL完整系列】:MySQL數據庫從0到拿捏系列
-
MySQL數據庫零基礎入門教程:從安裝配置到數據查詢全掌握
關鍵詞:安裝、登錄、客戶端、庫表基礎、簡單查詢 -
MySQL數據表操作全指南:建表、修改、刪除一步到位
關鍵詞:DDL語句、字段類型、主鍵/外鍵、約束、規范設計
聚焦表結構的創建和維護,配合真實業務建表案例(如用戶表、訂單表)。 -
MySQL增刪改查基礎教程:熟練掌握DML語句操作
關鍵詞:INSERT、UPDATE、DELETE、SELECT、WHERE、ORDER BY
實戰演練日常的數據庫操作命令,重點講解查詢語句的條件與排序。 -
MySQL高級查詢技巧:分組、聚合、子查詢與分頁
關鍵詞:GROUP BY、HAVING、聚合函數、LIMIT、子查詢
向中級進階,涵蓋常見報表需求與分頁列表的查詢實現。 -
MySQL多表查詢詳解:內連接、外連接、自連接通通搞懂
關鍵詞:JOIN、INNER JOIN、LEFT JOIN、UNION、自連接
深度講解表與表之間如何通過字段建立關聯并進行數據整合。 -
MySQL索引與性能優化入門:讓查詢提速的秘密武器
關鍵詞:索引原理、EXPLAIN、慢查詢、查詢優化
開啟性能優化之路,適合準備應對數據量增長或面試的人。 -
MySQL事務與鎖機制詳解:確保數據一致性的關鍵
關鍵詞:事務四大特性、鎖類型、死鎖案例、隔離級別
涉及電商、支付系統等對數據一致性要求高的業務場景。 -
MySQL項目實戰演練:搭建用戶管理系統的完整數據庫結構
關鍵詞:業務建模、表關系設計、數據初始化、查詢場景
以實戰帶動知識回顧,模擬真實業務項目,整合前面所學內容。