-- 相同分数名次也不同 SELECT class, student, score, ROW_NUMBER() OVER (PARTITIONBY class ORDERBY score DESC) AS row_num FROM scores ORDERBY class, row_num;
-- 结果: -- | class | student | score | row_num | -- |-------|---------|-------|---------| -- | A | Bob | 90 | 1 | -- | A | Alice | 85 | 2 | -- | A | Charlie | 85 | 3 | -- | A | Ali | 82 | 4 | -- | B | David | 92 | 1 | -- | B | Frank | 90 | 2 | -- | B | Eve | 88 | 3 |
RANK() 实现排名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
-- 相同分数名次相同,但是后面会占用一个名次 SELECT class, student, score, RANK() OVER (PARTITIONBY class ORDERBY score DESC) AS rank_num FROM scores ORDERBY class, rank_num;
-- 结果: -- | class | student | score | rank_num | -- |-------|---------|-------|----------| -- | A | Bob | 90 | 1 | -- | A | Alice | 85 | 2 | Alice 和 Charlie 并列第2,后面没有第3名,会直接跳到了4 -- | A | Charlie | 85 | 2 | -- | A | Ali | 82 | 4 | Ali 直接是第4名 -- | B | David | 92 | 1 | -- | B | Frank | 90 | 2 | -- | B | Eve | 88 | 3 |
DENSE_RANK() 实现排名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
-- 相同分数名次相同,后面的名次正常 SELECT class, student, score, DENSE_RANK() OVER (PARTITIONBY class ORDERBY score DESC) AS dense_rank_num FROM scores ORDERBY class, dense_rank_num;
-- 结果: -- | class | student | score | rank_num | -- |-------|---------|-------|----------| -- | A | Bob | 90 | 1 | -- | A | Alice | 85 | 2 | Alice 和 Charlie 并列第2,后面就是第3名 -- | A | Charlie | 85 | 2 | -- | A | Ali | 82 | 3 | -- | B | David | 92 | 1 | -- | B | Frank | 90 | 2 | -- | B | Eve | 88 | 3 |
-- 计算每一天与前一天的销售额差异 SELECT sale_date, amount, LAG(amount, 1) OVER (ORDERBY sale_date) AS prev_amount, amount -LAG(amount, 1) OVER (ORDERBY sale_date) AS daily_diff FROM sales;
-- 计算每一天与后一天的销售额差异 SELECT sale_date, amount, LEAD(amount, 1) OVER (ORDERBY sale_date) AS next_amount, LEAD(amount, 1) OVER (ORDERBY sale_date) - amount AS next_diff FROM sales;
-- 查看每一天的销售额与该月最后一天销售额的比较 SELECT sale_date, amount, -- LAST_VALUE 默认是到当前行为止的窗口,需要使用 `ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING` 来获取到窗口末尾的值 LAST_VALUE( amount ) OVER ( ORDERBY sale_date ROWSBETWEENCURRENTROWAND UNBOUNDED FOLLOWING ) AS last_amount_of_month, LAST_VALUE( amount ) OVER ( ORDERBY sale_date ROWSBETWEENCURRENTROWAND UNBOUNDED FOLLOWING ) - amount AS last_diff FROM sales;
-- cte_name: 别名 -- initial_query: 返回递归的起始行,不引用 cte_name 本身。 -- UNION ALL: 通常使用 UNION ALL,因为递归需要合并所有层级的结果。UNION 会去重,可能导致性能问题或意外结果。 -- recursive_query: 引用 cte_name,定义如何从当前行找到下一级的行。
场景:组织架构中查找某员工的所有上级。其中每个员工只有一个上级
1 2 3 4 5 6 7 8
-- 递归查询“小明”的所有上级(包括间接上级),manager_id为上级ID WITHRECURSIVE manager_chain AS ( SELECT id, name, manager_id FROM org_chart WHERE name ='小明' UNIONALL SELECT oc.id, oc.name, oc.manager_id FROM org_chart oc INNERJOIN manager_chain mc ON oc.id = mc.manager_id ) SELECT name FROM manager_chain WHERE name !='小明';
场景:查找某员工及其所有下级员工。
1 2 3 4 5 6 7 8 9 10 11 12 13
-- 查找 'CTO' (id=2) 及其所有下级员工 WITHRECURSIVE subordinates AS ( -- 锚点:从 CTO 开始 SELECT id, name, manager_id, 0AS level FROM employees WHERE id =2-- 起始点
UNIONALL
-- 递归:找到 CTO 的直接下属,然后是下属的下属... SELECT e.id, e.name, e.manager_id, s.level +1FROM employees e INNERJOIN subordinates s ON e.manager_id = s.id ) SELECT id, name, level FROM subordinates ORDERBY level, id;
场景:生成一个从 1 到 10 的数字列表。
1 2 3 4 5 6 7 8 9 10 11 12
WITHRECURSIVE number_series AS ( -- 锚点:从 1 开始 SELECT1AS n
UNIONALL
-- 递归:n 递增 SELECT n +1 FROM number_series WHERE n <10-- 递归终止条件:当 n >= 10 时停止 ) SELECT n FROM number_series;
非递归查询
基础语法:
1 2 3 4 5 6 7 8
WITH cte_name [(column_list)] AS ( query_definition ) SELECT ... FROM cte_name ...;
WITH -- CTE 1: 计算公司整体平均工资 company_avg AS ( SELECTAVG(salary) AS avg_salary FROM employees ), -- CTE 2: 计算每个部门的平均工资 dept_avg AS ( SELECT department, AVG(salary) AS avg_dept_salary FROM employees GROUPBY department ) -- 主查询:连接两个 CTE 的结果 SELECT dept_avg.department, dept_avg.avg_dept_salary FROM dept_avg CROSSJOIN company_avg -- 交叉连接,将公司平均工资与每个部门平均工资关联 WHERE dept_avg.avg_dept_salary > company_avg.avg_salary;
WITH ranked_employees AS ( SELECT name, department, salary, ROW_NUMBER() OVER (PARTITIONBY department ORDERBY salary DESC) AS rn, MAX(salary) OVER (PARTITIONBY department) AS max_dept_salary -- 窗口函数计算部门最高薪资 FROM employees ) SELECT name, department, salary, max_dept_salary FROM ranked_employees WHERE rn =1; -- 只取每个部门排序第一(薪资最高)的员工
-- 结果: -- | name | department | salary | max_dept_salary | -- |-------|-------------|---------|-----------------| -- | Bob | Engineering | 9000.00 | 9000.00 | -- | David | Sales | 7000.00 | 7000.00 | -- | Eve | HR | 5500.00 | 5500.00 |
索引优化
联合索引
索引跳跃扫描( index skip scan) 是 MySQL 8.0 的重要优化特性。它允许在跳过复合索引最左列的情况下仍能使用索引,前提是前导列取值少(低基数),能显著提升特定查询的性能,避免不必要的全表扫描。
会用到ISS的sql: # 查询中包含phone, gender的任意一列 explain select phone, gender from app_user where phone = '18103430049'
不会用到ISS的sql: # 除了phone, gender的任意一列的其他列 explain select name from app_user where phone = '18103430049' explain select * from app_user where phone = '18103430049'
# 表示优化器是否使用不可见索引,默认为off不使用 set session optimizer_switch="use_invisible_indexes=on"; # 查看查询优化器的开关和其他开关 select @@optimizer_switch # 创建隐藏索引,在其后添加 关键字 invisible create indextest_idx on employees(j) invisible;
# 将字段j上的隐藏索引j_idx设置为可见 alter table employees alter index idx_dept visible; # 将字段j上的隐藏索引j_idx设置为不可见 alter table employees alter index idx_dept invisible;
-- 查看索引状态 SHOW INDEX FROM employees WHERE Key_name ='idx_dept';
降序索引
只有InnoDB存储引擎支持降序索引,只支持BTREE降序索引。
不再对GROUP BY操作进行隐式排序
1 2 3 4 5 6 7 8
# 创建表并指定排序索引 idx1,包含排序关键字 # 使用c1字段降序,c2字段升序排序。在8.0中使用索引,之前版本不使用索引 create table if notexists t2(c1 int, c2 int, index idx1(c1 asc, c2 desc));
# 8.0 之前c2字段上会进行升序排序操作,之后版本并没有排序 selectcount(*), c2 from t2 groupby c2; # 在MySQL 8.x中如果需要对c2字段进行排序,则需要使用orderby语句明确指定排序规则 selectcount(*), c2 from t2 groupby c2 orderby c2;
-- 更新 price 字段 UPDATE products SET data = JSON_SET(data, '$.price', 2799.00) WHERE id =1; -- 删除某个颜色选项 UPDATE products SET data = JSON_REMOVE(data, '$.spec.color[1]') WHERE id =1; -- 替换整个子对象 UPDATE products SET data = JSON_REPLACE(data, '$.spec.model', 'X2 Pro') WHERE id =1;