MySQL高级特性详解
MySQL高级特性详解
一、自关联查询
概念
自关联查询是指一个表与它自己进行连接的查询。通常用于处理具有层级关系或递归结构的数据。
应用场景
- 员工与上级关系
- 分类的父子关系
- 地区的层级关系
示例
-- 创建员工表
CREATE TABLE employees (emp_id INT PRIMARY KEY,emp_name VARCHAR(50),manager_id INT,FOREIGN KEY (manager_id) REFERENCES employees(emp_id)
);-- 插入示例数据
INSERT INTO employees VALUES
(1, '张总', NULL),
(2, '李经理', 1),
(3, '王主管', 2),
(4, '赵员工', 3),
(5, '钱员工', 3);-- 查询每个员工及其直接上级
SELECT e1.emp_name AS '员工',e2.emp_name AS '上级'
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.emp_id;-- 查询所有下属超过1人的管理者
SELECT e2.emp_name AS '管理者',COUNT(e1.emp_id) AS '下属人数'
FROM employees e1
INNER JOIN employees e2 ON e1.manager_id = e2.emp_id
GROUP BY e2.emp_id, e2.emp_name
HAVING COUNT(e1.emp_id) > 1;
二、子查询操作
概念
子查询是嵌套在其他SQL语句中的SELECT语句,可以出现在WHERE、FROM、SELECT等子句中。
子查询分类
1. 标量子查询(返回单个值)
-- 查询工资高于平均工资的员工
SELECT emp_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
2. 列子查询(返回一列多行)
-- 使用IN操作符
SELECT * FROM employees
WHERE dept_id IN (SELECT dept_id FROM departments WHERE location = '北京');-- 使用ANY/ALL操作符
SELECT * FROM employees
WHERE salary > ANY (SELECT salary FROM employees WHERE dept_id = 10);
3. 行子查询(返回一行多列)
-- 查询与张三相同部门和职位的员工
SELECT * FROM employees
WHERE (dept_id, position) = (SELECT dept_id, position FROM employees WHERE emp_name = '张三'
);
4. 表子查询(返回多行多列)
-- FROM子句中的子查询
SELECT t.dept_name, t.avg_salary
FROM (SELECT d.dept_name, AVG(e.salary) AS avg_salaryFROM employees eJOIN departments d ON e.dept_id = d.dept_idGROUP BY d.dept_name
) t
WHERE t.avg_salary > 10000;
EXISTS子查询
-- 查询有员工的部门
SELECT * FROM departments d
WHERE EXISTS (SELECT 1 FROM employees e WHERE e.dept_id = d.dept_id
);-- 查询没有下属的员工
SELECT * FROM employees e1
WHERE NOT EXISTS (SELECT 1 FROM employees e2 WHERE e2.manager_id = e1.emp_id
);
三、窗口函数
概念
窗口函数在保留原表所有行的基础上,为每一行计算聚合值。与GROUP BY不同,它不会减少返回的行数。
基本语法
函数名() OVER ([PARTITION BY 列名][ORDER BY 列名][窗口子句]
)
常用窗口函数
1. 聚合窗口函数
-- 计算累计销售额
SELECT order_date,amount,SUM(amount) OVER (ORDER BY order_date) AS cumulative_sum,AVG(amount) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM orders;-- 按部门计算工资占比
SELECT emp_name,dept_id,salary,salary / SUM(salary) OVER (PARTITION BY dept_id) * 100 AS salary_percentage
FROM employees;
2. 排名函数
-- ROW_NUMBER(): 生成唯一序号
-- RANK(): 相同值同排名,下一个排名会跳过
-- DENSE_RANK(): 相同值同排名,下一个排名连续SELECT emp_name,dept_id,salary,ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS row_num,RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rank_num,DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dense_rank_num
FROM employees;-- NTILE(): 将数据分成N组
SELECT emp_name,salary,NTILE(4) OVER (ORDER BY salary) AS quartile
FROM employees;
3. 偏移函数
-- LAG/LEAD: 访问前后行数据
SELECT month,sales,LAG(sales, 1) OVER (ORDER BY month) AS prev_month_sales,LEAD(sales, 1) OVER (ORDER BY month) AS next_month_sales,sales - LAG(sales, 1) OVER (ORDER BY month) AS month_over_month_change
FROM monthly_sales;-- FIRST_VALUE/LAST_VALUE: 获取窗口内第一个/最后一个值
SELECT emp_name,dept_id,salary,FIRST_VALUE(emp_name) OVER (PARTITION BY dept_id ORDER BY salary DESC) AS highest_paid,LAST_VALUE(emp_name) OVER (PARTITION BY dept_id ORDER BY salary DESCRANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lowest_paid
FROM employees;
四、MySQL常用内置函数
1. 时间日期函数
-- 获取当前时间
SELECT NOW(), CURDATE(), CURTIME();-- 日期格式化
SELECT DATE_FORMAT(NOW(), '%Y年%m月%d日 %H:%i:%s');-- 日期计算
SELECT DATE_ADD(NOW(), INTERVAL 7 DAY) AS '7天后',DATE_SUB(NOW(), INTERVAL 1 MONTH) AS '1个月前',DATEDIFF('2024-12-31', NOW()) AS '距离年底天数',TIMESTAMPDIFF(YEAR, '1990-01-01', NOW()) AS '年龄';-- 提取日期部分
SELECT YEAR(NOW()) AS '年',MONTH(NOW()) AS '月',DAY(NOW()) AS '日',DAYNAME(NOW()) AS '星期',WEEKDAY(NOW()) AS '星期索引';
2. 字符串函数
-- 字符串连接
SELECT CONCAT('Hello', ' ', 'World');
SELECT CONCAT_WS(',', 'A', 'B', 'C'); -- 使用分隔符-- 字符串长度和位置
SELECT LENGTH('你好'), CHAR_LENGTH('你好'); -- 字节长度vs字符长度
SELECT LOCATE('world', 'hello world'); -- 查找位置-- 字符串截取
SELECT LEFT('abcdefg', 3) AS '左截取',RIGHT('abcdefg', 3) AS '右截取',SUBSTRING('abcdefg', 2, 3) AS '中间截取';-- 字符串转换
SELECT UPPER('hello') AS '大写',LOWER('HELLO') AS '小写',REPLACE('hello world', 'world', 'MySQL') AS '替换',TRIM(' hello ') AS '去空格',REVERSE('hello') AS '反转';
3. 数学函数
-- 基础数学运算
SELECT ABS(-10) AS '绝对值',CEIL(4.3) AS '向上取整',FLOOR(4.7) AS '向下取整',ROUND(4.567, 2) AS '四舍五入',TRUNCATE(4.567, 2) AS '截断';-- 高级数学函数
SELECT POW(2, 3) AS '幂运算',SQRT(16) AS '平方根',MOD(10, 3) AS '取余',RAND() AS '随机数',FLOOR(RAND() * 100) AS '0-99随机整数';
五、CASE WHEN条件判断
简单CASE语法
SELECT emp_name,salary,CASE dept_idWHEN 1 THEN '技术部'WHEN 2 THEN '销售部'WHEN 3 THEN '人事部'ELSE '其他部门'END AS dept_name
FROM employees;
搜索CASE语法
-- 工资等级划分
SELECT emp_name,salary,CASE WHEN salary >= 20000 THEN '高级'WHEN salary >= 10000 THEN '中级'WHEN salary >= 5000 THEN '初级'ELSE '实习'END AS salary_level
FROM employees;-- 条件统计
SELECT dept_id,COUNT(CASE WHEN salary >= 10000 THEN 1 END) AS high_salary_count,COUNT(CASE WHEN salary < 10000 THEN 1 END) AS low_salary_count,SUM(CASE WHEN gender = '男' THEN salary ELSE 0 END) AS male_total_salary,SUM(CASE WHEN gender = '女' THEN salary ELSE 0 END) AS female_total_salary
FROM employees
GROUP BY dept_id;-- 动态排序
SELECT * FROM employees
ORDER BY CASE WHEN @sort_type = 'name' THEN emp_name END,CASE WHEN @sort_type = 'salary' THEN salary END DESC;
六、事务概念及应用
事务的概念
事务是一组不可分割的操作单元,要么全部成功,要么全部失败。
ACID特性
- 原子性(Atomicity):事务是不可分割的最小操作单元
- 一致性(Consistency):事务完成后,数据必须处于一致状态
- 隔离性(Isolation):多个事务之间相互独立
- 持久性(Durability):事务一旦提交,改变是永久的
事务操作
-- 开启事务
START TRANSACTION; -- 或 BEGIN;-- 执行SQL操作
UPDATE accounts SET balance = balance - 1000 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE account_id = 2;-- 提交或回滚
COMMIT; -- 提交事务
ROLLBACK; -- 回滚事务-- 设置保存点
SAVEPOINT point1;
-- 回滚到保存点
ROLLBACK TO point1;
事务隔离级别
-- 查看当前隔离级别
SELECT @@transaction_isolation;-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- 读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 读已提交
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 可重复读(MySQL默认)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- 串行化
应用场景示例
-- 转账操作
DELIMITER $$
CREATE PROCEDURE transfer_money(IN from_account INT,IN to_account INT,IN amount DECIMAL(10,2)
)
BEGINDECLARE EXIT HANDLER FOR SQLEXCEPTIONBEGINROLLBACK;SELECT 'Transaction failed, rolled back' AS message;END;START TRANSACTION;UPDATE accounts SET balance = balance - amount WHERE account_id = from_account AND balance >= amount;IF ROW_COUNT() = 0 THENSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient balance';END IF;UPDATE accounts SET balance = balance + amount WHERE account_id = to_account;COMMIT;SELECT 'Transaction successful' AS message;
END$$
DELIMITER ;
七、索引概念及应用
索引的概念
索引是帮助MySQL高效获取数据的数据结构,类似于书的目录。
索引类型
- 主键索引(PRIMARY KEY):唯一且不为NULL
- 唯一索引(UNIQUE):唯一,可以为NULL
- 普通索引(INDEX):最基本的索引
- 全文索引(FULLTEXT):用于全文搜索
- 组合索引:多列组成的索引
索引操作
-- 创建索引
CREATE INDEX idx_emp_name ON employees(emp_name);
CREATE UNIQUE INDEX idx_emp_email ON employees(email);
CREATE INDEX idx_emp_dept_salary ON employees(dept_id, salary); -- 组合索引-- 查看索引
SHOW INDEX FROM employees;-- 删除索引
DROP INDEX idx_emp_name ON employees;-- 使用EXPLAIN分析查询
EXPLAIN SELECT * FROM employees WHERE emp_name = '张三';
索引使用原则
-- 1. 最左前缀原则(组合索引)
-- 假设有索引 (a, b, c)
SELECT * FROM table WHERE a = 1; -- 使用索引
SELECT * FROM table WHERE a = 1 AND b = 2; -- 使用索引
SELECT * FROM table WHERE b = 2; -- 不使用索引-- 2. 避免索引失效的情况
-- 函数操作
SELECT * FROM employees WHERE YEAR(hire_date) = 2024; -- 索引失效
SELECT * FROM employees WHERE hire_date >= '2024-01-01' AND hire_date < '2025-01-01'; -- 使用索引-- 类型不匹配
SELECT * FROM employees WHERE emp_id = '123'; -- 如果emp_id是整数,可能失效-- LIKE通配符
SELECT * FROM employees WHERE emp_name LIKE '%张%'; -- 索引失效
SELECT * FROM employees WHERE emp_name LIKE '张%'; -- 使用索引
索引优化建议
- 选择性高的列适合建索引
- 频繁作为查询条件的列建索引
- 经常需要排序的列建索引
- 避免过多索引,影响写入性能
- 定期分析和优化索引
八、视图概念及应用
视图的概念
视图是一个虚拟表,其内容由查询定义。视图不存储数据,只存储SQL查询语句。
视图的优点
- 简化复杂查询
- 数据安全性(隐藏敏感列)
- 逻辑数据独立性
视图操作
-- 创建视图
CREATE VIEW v_emp_dept AS
SELECT e.emp_id,e.emp_name,e.salary,d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id;-- 使用视图
SELECT * FROM v_emp_dept WHERE salary > 10000;-- 创建可更新视图
CREATE VIEW v_emp_simple AS
SELECT emp_id, emp_name, salary
FROM employees
WHERE dept_id = 1
WITH CHECK OPTION; -- 确保通过视图的修改符合WHERE条件-- 修改视图
ALTER VIEW v_emp_dept AS
SELECT e.emp_id,e.emp_name,e.salary,e.hire_date,d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id;-- 删除视图
DROP VIEW IF EXISTS v_emp_dept;
视图应用场景
-- 1. 权限控制:为不同用户创建不同视图
CREATE VIEW v_emp_public AS
SELECT emp_id, emp_name, dept_id
FROM employees; -- 隐藏工资信息-- 2. 简化复杂查询
CREATE VIEW v_sales_summary AS
SELECT DATE_FORMAT(order_date, '%Y-%m') AS month,SUM(amount) AS total_sales,COUNT(DISTINCT customer_id) AS customer_count,AVG(amount) AS avg_order_value
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m');-- 3. 计算字段
CREATE VIEW v_emp_annual AS
SELECT emp_id,emp_name,salary,salary * 12 AS annual_salary,salary * 0.1 AS bonus
FROM employees;
九、ER模型和3NF三范式
ER模型(实体-关系模型)
基本概念
- 实体(Entity):现实世界中的对象,如学生、课程
- 属性(Attribute):实体的特征,如学生的姓名、年龄
- 关系(Relationship):实体之间的联系
关系类型
- 一对一(1:1):一个实体对应另一个实体的一个实例
- 一对多(1:N):一个实体对应另一个实体的多个实例
- 多对多(M:N):多个实体对应另一个实体的多个实例
ER图示例
-- 学生与课程的多对多关系
-- 学生表
CREATE TABLE students (student_id INT PRIMARY KEY,student_name VARCHAR(50),age INT
);-- 课程表
CREATE TABLE courses (course_id INT PRIMARY KEY,course_name VARCHAR(100),credits INT
);-- 选课表(关系表)
CREATE TABLE enrollments (student_id INT,course_id INT,grade DECIMAL(3,1),PRIMARY KEY (student_id, course_id),FOREIGN KEY (student_id) REFERENCES students(student_id),FOREIGN KEY (course_id) REFERENCES courses(course_id)
);
三范式(3NF)
第一范式(1NF):原子性
每个字段都是不可分割的原子值。
-- 违反1NF的设计
CREATE TABLE bad_students (student_id INT,student_name VARCHAR(50),phone_numbers VARCHAR(200) -- 存储多个电话号码,如"13812345678,13987654321"
);-- 符合1NF的设计
CREATE TABLE students (student_id INT PRIMARY KEY,student_name VARCHAR(50)
);CREATE TABLE student_phones (student_id INT,phone_number VARCHAR(20),phone_type VARCHAR(20),PRIMARY KEY (student_id, phone_number),FOREIGN KEY (student_id) REFERENCES students(student_id)
);
第二范式(2NF):完全依赖主键
非主键字段必须完全依赖于主键,不能只依赖主键的一部分。
-- 违反2NF的设计
CREATE TABLE bad_order_items (order_id INT,product_id INT,quantity INT,product_name VARCHAR(100), -- 只依赖于product_id,不依赖于order_idproduct_price DECIMAL(10,2), -- 只依赖于product_idPRIMARY KEY (order_id, product_id)
);-- 符合2NF的设计
CREATE TABLE products (product_id INT PRIMARY KEY,product_name VARCHAR(100),product_price DECIMAL(10,2)
);CREATE TABLE order_items (order_id INT,product_id INT,quantity INT,PRIMARY KEY (order_id, product_id),FOREIGN KEY (product_id) REFERENCES products(product_id)
);
第三范式(3NF):消除传递依赖
非主键字段之间不能有依赖关系,都应该直接依赖于主键。
-- 违反3NF的设计
CREATE TABLE bad_employees (emp_id INT PRIMARY KEY,emp_name VARCHAR(50),dept_id INT,dept_name VARCHAR(50), -- 传递依赖:dept_name依赖于dept_id,而非直接依赖于emp_iddept_location VARCHAR(100) -- 传递依赖
);-- 符合3NF的设计
CREATE TABLE departments (dept_id INT PRIMARY KEY,dept_name VARCHAR(50),dept_location VARCHAR(100)
);CREATE TABLE employees (emp_id INT PRIMARY KEY,emp_name VARCHAR(50),dept_id INT,FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);
范式化的优缺点
优点
- 减少数据冗余
- 保证数据一致性
- 节省存储空间
- 更新异常少
缺点
- 查询时需要更多JOIN操作
- 可能影响查询性能
反范式化场景
在某些情况下,为了提高查询性能,可能会适度违反范式:
-- 适度冗余以提高查询性能
CREATE TABLE order_summary (order_id INT PRIMARY KEY,order_date DATE,customer_name VARCHAR(100), -- 冗余字段,避免每次都JOINtotal_amount DECIMAL(10,2), -- 计算字段,避免每次都SUMitem_count INT -- 统计字段
);
实战练习题
综合练习:电商数据分析
-- 创建示例表
CREATE TABLE orders (order_id INT PRIMARY KEY,customer_id INT,order_date DATE,total_amount DECIMAL(10,2)
);-- 1. 使用窗口函数计算客户的购买排名
SELECT customer_id,SUM(total_amount) AS total_purchase,RANK() OVER (ORDER BY SUM(total_amount) DESC) AS customer_rank
FROM orders
GROUP BY customer_id;-- 2. 使用子查询找出消费超过平均值的客户
SELECT DISTINCT customer_id
FROM orders o1
WHERE (SELECT SUM(total_amount) FROM orders o2 WHERE o2.customer_id = o1.customer_id
) > (SELECT AVG(customer_total) FROM (SELECT SUM(total_amount) AS customer_total FROM orders GROUP BY customer_id) t
);-- 3. 使用CASE WHEN进行客户分类
SELECT customer_id,SUM(total_amount) AS total_amount,CASE WHEN SUM(total_amount) >= 10000 THEN 'VIP客户'WHEN SUM(total_amount) >= 5000 THEN '重要客户'WHEN SUM(total_amount) >= 1000 THEN '普通客户'ELSE '潜在客户'END AS customer_level
FROM orders
GROUP BY customer_id;
总结
掌握这些MySQL高级特性对于数据库开发和优化至关重要:
- 自关联查询和子查询提供了灵活的数据查询方式
- 窗口函数让复杂的分析查询变得简单高效
- 内置函数和CASE WHEN增强了SQL的表达能力
- 事务确保了数据的完整性和一致性
- 索引是提升查询性能的关键
- 视图简化了复杂查询并提供了安全性
- ER模型和范式化是良好数据库设计的基础
建议通过大量实践来深入理解这些概念,并在实际项目中灵活运用。