2565 字
13 分钟
MySQL 查询命令速查

一、基础查询#

-- 查询所有列
SELECT * FROM employees;
-- 查询指定列,使用别名
SELECT
emp_id AS "员工ID",
emp_name AS "姓名",
salary AS "薪资"
FROM employees;
-- 去重
SELECT DISTINCT department FROM employees;
-- 排序(默认 ASC)
SELECT * FROM employees ORDER BY salary DESC, hire_date ASC;
-- 分页 (MySQL 语法)
SELECT * FROM employees LIMIT 5 OFFSET 10; -- 跳过前 10 行,取 5 行
SELECT * FROM employees LIMIT 10, 5; -- 等效写法

二、条件过滤#

-- 比较运算符:= <> != > >= < <=
SELECT * FROM employees WHERE salary > 8000;
-- BETWEEN (闭区间)
SELECT * FROM employees WHERE salary BETWEEN 5000 AND 10000;
-- IN / NOT IN
SELECT * FROM employees WHERE department IN ('IT', 'Finance');
-- LIKE 模糊匹配 (% 任意多字符,_ 单个字符)
SELECT * FROM employees WHERE emp_name LIKE '张%'; -- 以"张"开头
SELECT * FROM employees WHERE emp_name LIKE '__'; -- 两个字的名字
-- NULL 判断 (不能用 = NULL)
SELECT * FROM employees WHERE bonus IS NULL;
SELECT * FROM employees WHERE bonus IS NOT NULL;
-- 逻辑运算 AND / OR / NOT
SELECT * FROM employees
WHERE department = 'IT' AND (salary > 10000 OR bonus > 5000);

三、聚合与分组#

-- 聚合函数
SELECT
COUNT(*) AS 总人数, -- 计数(含重复和NULL)
COUNT(emp_id) AS 有ID的人数,
COUNT(DISTINCT dept) AS 部门数,
SUM(salary) AS 工资总和,
AVG(salary) AS 平均工资,
MAX(salary) AS 最高工资,
MIN(salary) AS 最低工资
FROM employees;
-- GROUP BY 分组
SELECT
department,
COUNT(*) AS 人数,
AVG(salary) AS 平均薪资
FROM employees
GROUP BY department;
-- HAVING 过滤分组结果(WHERE 在分组前,HAVING 在分组后)
SELECT
department,
AVG(salary) AS avg_sal
FROM employees
WHERE hire_date > '2020-01-01' -- 先过滤行
GROUP BY department
HAVING AVG(salary) > 8000 -- 再过滤分组
ORDER BY avg_sal DESC;
-- SQL 执行顺序:
-- FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

四、连接 (JOIN)#

假设有两张表:employees (员工) 和 departments (部门),通过 dept_id 关联。

-- INNER JOIN(交集):两表都匹配的行
SELECT e.emp_name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id;
-- LEFT JOIN:左表全部保留,右表无匹配填 NULL
SELECT e.emp_name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id;
-- RIGHT JOIN:右表全部保留
SELECT e.emp_name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
-- CROSS JOIN(笛卡尔积):慎用
SELECT * FROM employees CROSS JOIN departments;
-- 多表连接
SELECT e.emp_name, d.dept_name, p.project_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
LEFT JOIN projects p ON e.emp_id = p.emp_id;
-- 自连接:同一张表自己做连接
-- 例:查询每个员工的上级姓名
SELECT e1.emp_name AS 员工, e2.emp_name AS 上级
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.emp_id;
连接类型结果
INNER JOIN两表都匹配的行
LEFT JOIN左表全保留,右表不匹配填 NULL
RIGHT JOIN右表全保留,左表不匹配填 NULL
FULL JOIN两表全保留(MySQL 不支持,用 UNION 模拟)

五、子查询#

-- 标量子查询(返回单个值)
-- 查询薪资高于平均薪资的员工
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- 多值子查询 IN / ANY / ALL
-- 查询在"有奖金记录的部门"中工作的员工
SELECT * FROM employees
WHERE dept_id IN (
SELECT DISTINCT dept_id FROM employees WHERE bonus IS NOT NULL
);
-- ANY:大于子查询结果中的任意一个即可
SELECT * FROM employees
WHERE salary > ANY (SELECT salary FROM employees WHERE dept_id = 1);
-- ALL:大于子查询结果中的所有值
SELECT * FROM employees
WHERE salary > ALL (SELECT salary FROM employees WHERE dept_id = 1);
-- FROM 子查询(派生表):子查询结果当作一张临时表
SELECT dept_id, avg_sal
FROM (
SELECT dept_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id
) AS dept_avg
WHERE avg_sal > 8000;
-- EXISTS / NOT EXISTS(相关子查询)
-- 查询"有下属"的员工(至少有一个员工的 manager_id 指向他)
SELECT * FROM employees e1
WHERE EXISTS (
SELECT 1 FROM employees e2 WHERE e2.manager_id = e1.emp_id
);
-- 查询"没有下属"的员工
SELECT * FROM employees e1
WHERE NOT EXISTS (
SELECT 1 FROM employees e2 WHERE e2.manager_id = e1.emp_id
);

六、窗口函数#

函数名([参数]) OVER (
[PARTITION BY 列,...] -- 分组
[ORDER BY 列 [ASC|DESC],...] -- 排序
[ROWS|RANGE 帧定义] -- 窗口范围(可选)
)

6.1 排名函数#

-- ROW_NUMBER:连续排名,无并列(1,2,3,4,...)
SELECT
emp_name, department, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num
FROM employees;
-- RANK:有并列,跳号(1,1,3,4,...)
SELECT
emp_name, department, salary,
RANK() OVER (ORDER BY salary DESC) AS rk
FROM employees;
-- DENSE_RANK:有并列,不跳号(1,1,2,3,...)
SELECT
emp_name, department, salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS dr
FROM employees;
-- 分组排名:每个部门内按薪资排名
SELECT
emp_name, department, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;
函数并列处理后续编号
ROW_NUMBER不并列,严格递增1, 2, 3, 4
RANK值相同则并列1, 1, 3, 4
DENSE_RANK值相同则并列1, 1, 2, 3

6.2 偏移函数#

-- LAG:向上偏移(取前一行)
SELECT
order_date,
amount,
LAG(amount, 1) OVER (ORDER BY order_date) AS prev_amount, -- 前一天
LAG(amount, 2, 0) OVER (ORDER BY order_date) AS two_days_ago -- 前两天,默认 0
FROM orders;
-- LEAD:向下偏移(取后一行)
SELECT
order_date,
amount,
LEAD(amount, 1) OVER (ORDER BY order_date) AS next_amount
FROM orders;

6.3 聚合窗口函数#

-- 累计求和 (Running Total)
SELECT
order_date,
amount,
SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders;
-- 分组累计求和
SELECT
order_date,
department,
amount,
SUM(amount) OVER (PARTITION BY department ORDER BY order_date) AS dept_running_total
FROM orders;
-- 移动平均(前 2 行 + 当前行 + 后 2 行,共 5 行)
SELECT
order_date,
amount,
AVG(amount) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS moving_avg_5
FROM orders;
-- 每组内的聚合值
SELECT
emp_name, department, salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg,
MAX(salary) OVER (PARTITION BY department) AS dept_max,
salary - AVG(salary) OVER (PARTITION BY department) AS diff_from_avg
FROM employees;

6.4 窗口帧定义#

-- 累积帧(从分区起点到当前行,默认行为)
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
-- 滑动窗口(前3行 + 当前行)
ROWS BETWEEN 3 PRECEDING AND CURRENT ROW
-- 前后各1行
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
-- 到分区末尾
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING

七、索引#

-- 创建索引(加速查询的核心手段)
CREATE INDEX idx_name ON employees(emp_name);
CREATE INDEX idx_dept_salary ON employees(dept_id, salary); -- 复合索引
-- 查看索引
SHOW INDEX FROM employees;
-- 删除索引
DROP INDEX idx_name ON employees;
-- 用 EXPLAIN 分析查询是否走了索引
EXPLAIN SELECT * FROM employees WHERE emp_name = '张三';
-- type=ALL 是全表扫描(不好)
-- type=ref 或 type=const 是走了索引(好)

八、常用函数#

字符串函数#

-- 拼接
SELECT CONCAT(last_name, ' ', first_name) AS full_name FROM employees;
SELECT CONCAT_WS('-', '2024', '01', '15'); -- '2024-01-15'
-- 子串
SELECT SUBSTRING('Hello World', 1, 5); -- 'Hello'
SELECT LEFT('Hello World', 5); -- 'Hello'
SELECT RIGHT('Hello World', 5); -- 'World'
-- 替换
SELECT REPLACE('Hello World', 'World', 'MySQL'); -- 'Hello MySQL'
-- 长度
SELECT LENGTH('Hello'); -- 5(字节)
SELECT CHAR_LENGTH('你好'); -- 2(字符数)
-- 大小写与去空格
SELECT UPPER('hello'), LOWER('HELLO'), TRIM(' text ');

日期函数#

-- 当前时间
SELECT NOW(), CURDATE(), CURTIME();
-- 提取日期部分
SELECT
YEAR('2024-06-15') AS yr,
MONTH('2024-06-15') AS mon,
DAY('2024-06-15') AS day,
DAYOFWEEK('2024-06-15') AS weekday; -- 1=Sunday
-- 日期格式化
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'); -- '2026-05-30 14:30:00'
SELECT DATE_FORMAT(NOW(), '%Y年%m月%d日'); -- '2026年05月30日'
-- 日期计算
SELECT DATEDIFF('2024-12-31', '2024-01-01'); -- 364(日期差)
SELECT DATE_ADD('2024-01-01', INTERVAL 7 DAY); -- '2024-01-08'
SELECT DATE_SUB('2024-01-01', INTERVAL 1 MONTH); -- '2023-12-01'
SELECT TIMESTAMPDIFF(MONTH, '2023-01-01', '2024-06-01'); -- 17(月差)
-- 日期截断
SELECT DATE('2024-06-15 14:30:00'); -- '2024-06-15'

数学与类型转换#

-- 四舍五入 / 上下取整
SELECT ROUND(3.14159, 2); -- 3.14
SELECT CEIL(3.1), FLOOR(3.9); -- 4, 3
SELECT ABS(-5), MOD(10, 3); -- 5, 1
-- 类型转换
SELECT CAST('123' AS SIGNED);
SELECT CAST('2024-01-01' AS DATE);
SELECT CONVERT(150, CHAR); -- 等效 CAST(150 AS CHAR)
-- IFNULL / COALESCE:空值处理
SELECT IFNULL(bonus, 0) FROM employees; -- bonus 为 NULL 时返回 0
SELECT COALESCE(phone, email, '无联系方式') FROM employees; -- 返回第一个非 NULL

条件与流程控制#

-- CASE WHEN:条件分支
SELECT
emp_name,
salary,
CASE
WHEN salary < 5000 THEN '低薪'
WHEN salary < 10000 THEN '中薪'
ELSE '高薪'
END AS salary_level
FROM employees;
-- CASE WHEN 配合聚合做交叉/透视
SELECT department,
SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END) AS 男性,
SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END) AS 女性
FROM employees
GROUP BY department;
-- IF 函数(三元表达式)
SELECT IF(salary > 8000, '高', '低') FROM employees;

九、进阶特性#

9.1 UNION / UNION ALL#

-- UNION:合并结果集并去重
SELECT emp_name FROM employees_2023
UNION
SELECT emp_name FROM employees_2024;
-- UNION ALL:合并结果集不去重(更快)
SELECT emp_name FROM employees_2023
UNION ALL
SELECT emp_name FROM employees_2024;

9.2 CTE(公用表表达式)#

-- 普通 CTE:把子查询抽出来,可读性更好
WITH dept_avg AS (
SELECT dept_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id
)
SELECT e.emp_name, e.salary, d.avg_sal
FROM employees e
JOIN dept_avg d ON e.dept_id = d.dept_id
WHERE e.salary > d.avg_sal;
-- 多个 CTE
WITH
dept_avg AS (
SELECT dept_id, AVG(salary) AS avg_sal
FROM employees GROUP BY dept_id
),
high_paid AS (
SELECT * FROM employees WHERE salary > 10000
)
SELECT * FROM high_paid;
-- 递归 CTE(生成连续日期、组织结构树等)
WITH RECURSIVE dates AS (
SELECT '2024-01-01' AS dt
UNION ALL
SELECT DATE_ADD(dt, INTERVAL 1 DAY) FROM dates WHERE dt < '2024-01-31'
)
SELECT * FROM dates;

9.3 DDL 与 DML 速览#

-- 建表
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100),
age INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 修改表结构
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users MODIFY COLUMN age TINYINT;
ALTER TABLE users DROP COLUMN phone;
ALTER TABLE users ADD INDEX idx_email(email);
-- 插入
INSERT INTO users (username, email) VALUES ('zhangsan', 'zhangsan@example.com');
INSERT INTO users (username, email) VALUES
('lisi', 'lisi@example.com'),
('wangwu', 'wangwu@example.com');
-- 更新 / 删除(务必带 WHERE)
UPDATE users SET age = 25 WHERE username = 'zhangsan';
DELETE FROM users WHERE id = 1;

十、经典题目#

10.1 查询每个部门薪资最高的员工#

-- 解法1:子查询 + 窗口函数(推荐)
SELECT emp_name, department, salary
FROM (
SELECT *,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rk
FROM employees
) t
WHERE rk = 1;
-- 解法2:相关子查询
SELECT emp_name, department, salary
FROM employees e1
WHERE salary = (
SELECT MAX(salary)
FROM employees e2
WHERE e2.department = e1.department
);

10.2 计算次日留存率#

login_loguser_id, login_date

WITH first_login AS (
-- 每个用户的首次登录日期
SELECT user_id, MIN(login_date) AS first_date
FROM login_log
GROUP BY user_id
),
retention AS (
-- 次日是否还登录了
SELECT
f.first_date,
COUNT(DISTINCT f.user_id) AS total_users,
COUNT(DISTINCT l.user_id) AS retained_users
FROM first_login f
LEFT JOIN login_log l
ON f.user_id = l.user_id
AND l.login_date = DATE_ADD(f.first_date, INTERVAL 1 DAY)
GROUP BY f.first_date
)
SELECT
first_date,
total_users,
retained_users,
ROUND(retained_users / total_users * 100, 2) AS retention_pct
FROM retention
ORDER BY first_date;

10.3 连续登录 N 天#

-- 用 SQL 查询连续登录 3 天及以上的用户
WITH ranked AS (
SELECT
user_id,
login_date,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn
FROM login_log
GROUP BY user_id, login_date -- 一天多次登录去重
),
diff_group AS (
SELECT
user_id,
login_date,
DATE_SUB(login_date, INTERVAL rn DAY) AS grp -- 连续日期的分组标志
FROM ranked
)
SELECT user_id, COUNT(*) AS consecutive_days
FROM diff_group
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;

10.4 累计求和#

-- 方案1:窗口函数(简单直接)
SELECT
order_date,
amount,
SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders;
-- 方案2:自连接(理解原理)
SELECT
o1.order_date,
o1.amount,
SUM(o2.amount) AS running_total
FROM orders o1
JOIN orders o2 ON o2.order_date <= o1.order_date
GROUP BY o1.order_date, o1.amount
ORDER BY o1.order_date;

10.5 求中位数#

-- 窗口函数方法
WITH numbered AS (
SELECT
salary,
ROW_NUMBER() OVER (ORDER BY salary) AS rn,
COUNT(*) OVER () AS total
FROM employees
)
SELECT AVG(salary) AS median
FROM numbered
WHERE rn IN (FLOOR((total + 1) / 2), CEIL((total + 1) / 2));

十一、参考链接#

MySQL 查询命令速查
https://laplacan.icu/posts/mysql/
作者
LapLacan
发布于
2026-05-30
许可协议
CC BY-NC-SA 4.0