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 INSELECT * 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 / NOTSELECT * FROM employeesWHERE 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 employeesGROUP BY department;
-- HAVING 过滤分组结果(WHERE 在分组前,HAVING 在分组后)SELECT department, AVG(salary) AS avg_salFROM employeesWHERE hire_date > '2020-01-01' -- 先过滤行GROUP BY departmentHAVING 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_nameFROM employees eINNER JOIN departments d ON e.dept_id = d.dept_id;
-- LEFT JOIN:左表全部保留,右表无匹配填 NULLSELECT e.emp_name, d.dept_nameFROM employees eLEFT JOIN departments d ON e.dept_id = d.dept_id;
-- RIGHT JOIN:右表全部保留SELECT e.emp_name, d.dept_nameFROM employees eRIGHT 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_nameFROM employees eJOIN departments d ON e.dept_id = d.dept_idLEFT JOIN projects p ON e.emp_id = p.emp_id;
-- 自连接:同一张表自己做连接-- 例:查询每个员工的上级姓名SELECT e1.emp_name AS 员工, e2.emp_name AS 上级FROM employees e1LEFT JOIN employees e2 ON e1.manager_id = e2.emp_id;| 连接类型 | 结果 |
|---|---|
| INNER JOIN | 两表都匹配的行 |
| LEFT JOIN | 左表全保留,右表不匹配填 NULL |
| RIGHT JOIN | 右表全保留,左表不匹配填 NULL |
| FULL JOIN | 两表全保留(MySQL 不支持,用 UNION 模拟) |
五、子查询
-- 标量子查询(返回单个值)-- 查询薪资高于平均薪资的员工SELECT * FROM employeesWHERE salary > (SELECT AVG(salary) FROM employees);
-- 多值子查询 IN / ANY / ALL-- 查询在"有奖金记录的部门"中工作的员工SELECT * FROM employeesWHERE dept_id IN ( SELECT DISTINCT dept_id FROM employees WHERE bonus IS NOT NULL);
-- ANY:大于子查询结果中的任意一个即可SELECT * FROM employeesWHERE salary > ANY (SELECT salary FROM employees WHERE dept_id = 1);
-- ALL:大于子查询结果中的所有值SELECT * FROM employeesWHERE salary > ALL (SELECT salary FROM employees WHERE dept_id = 1);
-- FROM 子查询(派生表):子查询结果当作一张临时表SELECT dept_id, avg_salFROM ( SELECT dept_id, AVG(salary) AS avg_sal FROM employees GROUP BY dept_id) AS dept_avgWHERE avg_sal > 8000;
-- EXISTS / NOT EXISTS(相关子查询)-- 查询"有下属"的员工(至少有一个员工的 manager_id 指向他)SELECT * FROM employees e1WHERE EXISTS ( SELECT 1 FROM employees e2 WHERE e2.manager_id = e1.emp_id);
-- 查询"没有下属"的员工SELECT * FROM employees e1WHERE 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_numFROM employees;
-- RANK:有并列,跳号(1,1,3,4,...)SELECT emp_name, department, salary, RANK() OVER (ORDER BY salary DESC) AS rkFROM employees;
-- DENSE_RANK:有并列,不跳号(1,1,2,3,...)SELECT emp_name, department, salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS drFROM employees;
-- 分组排名:每个部门内按薪资排名SELECT emp_name, department, salary, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rankFROM 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 -- 前两天,默认 0FROM orders;
-- LEAD:向下偏移(取后一行)SELECT order_date, amount, LEAD(amount, 1) OVER (ORDER BY order_date) AS next_amountFROM orders;6.3 聚合窗口函数
-- 累计求和 (Running Total)SELECT order_date, amount, SUM(amount) OVER (ORDER BY order_date) AS running_totalFROM orders;
-- 分组累计求和SELECT order_date, department, amount, SUM(amount) OVER (PARTITION BY department ORDER BY order_date) AS dept_running_totalFROM 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_5FROM 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_avgFROM 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.14SELECT CEIL(3.1), FLOOR(3.9); -- 4, 3SELECT 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 时返回 0SELECT 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_levelFROM 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 employeesGROUP BY department;
-- IF 函数(三元表达式)SELECT IF(salary > 8000, '高', '低') FROM employees;九、进阶特性
9.1 UNION / UNION ALL
-- UNION:合并结果集并去重SELECT emp_name FROM employees_2023UNIONSELECT emp_name FROM employees_2024;
-- UNION ALL:合并结果集不去重(更快)SELECT emp_name FROM employees_2023UNION ALLSELECT 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_salFROM employees eJOIN dept_avg d ON e.dept_id = d.dept_idWHERE e.salary > d.avg_sal;
-- 多个 CTEWITH 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, salaryFROM ( SELECT *, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rk FROM employees) tWHERE rk = 1;
-- 解法2:相关子查询SELECT emp_name, department, salaryFROM employees e1WHERE salary = ( SELECT MAX(salary) FROM employees e2 WHERE e2.department = e1.department);10.2 计算次日留存率
表 login_log:user_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_pctFROM retentionORDER 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_daysFROM diff_groupGROUP BY user_id, grpHAVING COUNT(*) >= 3;10.4 累计求和
-- 方案1:窗口函数(简单直接)SELECT order_date, amount, SUM(amount) OVER (ORDER BY order_date) AS running_totalFROM orders;
-- 方案2:自连接(理解原理)SELECT o1.order_date, o1.amount, SUM(o2.amount) AS running_totalFROM orders o1JOIN orders o2 ON o2.order_date <= o1.order_dateGROUP BY o1.order_date, o1.amountORDER 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 medianFROM numberedWHERE rn IN (FLOOR((total + 1) / 2), CEIL((total + 1) / 2));