子查询的概念与应用
子查询(即一个查询嵌套在另一个查询中)为 SQL 提供了更高的灵活性和强大的数据处理能力。
例如,我们可以在一个 SELECT 语句的 WHERE 子句中嵌套另一个 SELECT 语句。
示例 1:查询最高分学生的姓名和班级
sql
SELECT name, class FROM student WHERE score = (SELECT MAX(score) FROM student);
示例 2:查询成绩高于平均成绩的学生
sql
SELECT * FROM student WHERE score > (SELECT AVG(score) FROM student);
EXISTS 和 NOT EXISTS 子句
EXISTS 和 NOT EXISTS 是特殊的 SQL 操作符,用于测试子查询是否返回结果。
示例 1:查询有员工的部门
sql
SELECT name FROM department WHERE EXISTS (SELECT * FROM employee WHERE department.id = employee.department_id);
示例 2:查询没有员工的部门
sql
SELECT name FROM department WHERE NOT EXISTS (SELECT * FROM employee WHERE department.id = employee.department_id);
子查询在 DML 操作中的应用
子查询不仅可以在 SELECT 语句中使用,还可以在数据修改语句(如 INSERT、UPDATE、DELETE)中使用。
示例 1:插入平均价格数据
sql
-- 向 avg_price_by_category 表插入数据
INSERT INTO avg_price_by_category (category, avg_price)
-- 从 product 表中选择 category 和平均 price
SELECT category, AVG(price)
-- 从 product 表中获取数据
FROM product
-- 按照 category 字段分组
GROUP BY category;
示例 2:更新技术部员工的名称
sql
-- 更新 employee 表中的数据
UPDATE employee
-- 设置 name 字段的值,将 '技术-' 添加到原有 name 前
SET name = CONCAT('技术-', name)
-- 指定更新条件:部门必须是 '技术部'
WHERE department_id = (SELECT id FROM department WHERE name = '技术部');
示例 3:删除技术部所有员工
sql
DELETE FROM employee WHERE department_id = (SELECT id FROM department WHERE name = '技术部');
事务的基本概念与操作
事务 是一组原子性的 SQL 命令集合,要么全部执行,要么全部不执行。
当更新多个相关表时,使用事务可以保证整体数据的一致性。
基本语法
- 开启事务: START TRANSACTION;
- 提交事务: COMMIT; 数据永久保存,不可回滚。
- 回滚事务: ROLLBACK; 数据回退到事务开始前的状态。
示例场景
假设需要更新订单详情表(order_items)和订单总金额表(orders)。
如果只更新了订单详情而未成功更新订单总金额,数据将不一致。此时,事务的应用非常关键。
sql
START TRANSACTION;
UPDATE order_items SET quantity=1 WHERE order_id=3;
UPDATE orders SET total_amount=200 WHERE id=3;
COMMIT;
如果中途发现错误或执行失败,可以使用 ROLLBACK; 撤销所有更改。
事务的保存点(Savepoint)
在复杂的事务中,可能需要在特定步骤设置回滚点,以便在错误发生时只回滚到某个特定点:
设置保存点: SAVEPOINT savepoint_name;
回滚到保存点: ROLLBACK TO savepoint_name;
sql
START TRANSACTION;
SAVEPOINT sp1;
UPDATE order_items SET quantity=1 WHERE order_id=3;
SAVEPOINT sp2;
UPDATE orders SET total_amount=200 WHERE id=3;
ROLLBACK TO sp2; -- 回滚到 sp2 保存点
COMMIT;
事务的隔离级别
我们先来看看脏读、不可重复读和幻读,这三种通常在多个事务并发执行时发生,分别对应不同的隔离问题。
脏读(Dirty Read)
脏读发生在一个事务读取了另一个事务未提交的数据。如果这个未提交的事务最终被回滚(取消),那么第一个事务读到的数据就是"脏"的,因为这些数据从未被真正地保存过。
示例:
- 事务 A 修改一条记录,然后暂停。
- 事务 B 读取了事务 A 修改的同一条记录。
- 如果事务 A 回滚更改,事务 B 读到的数据实际上是不存在的。
不可重复读(Non-repeatable Read)
不可重复读是指在同一事务中,多次读取同一数据集合时,由于其他事务的修改操作,导致后续的读取结果与初次读取不一致。
示例:
- 事务 A 读取了一条记录。
- 事务 B 更新了事务 A 刚刚读取的记录,然后提交。
- 事务 A 再次读取同一记录时,发现数据已经改变。
不可重复读的问题主要由于更新(UPDATE)操作引起。
幻读(Phantom Read)
幻读问题涉及到在同一事务中执行相同的查询两次,但由于其他事务插入或删除了符合查询条件的记录,导致两次查询结果的行数不一致。
示例:
- 事务 A 根据某个条件查询表中的多行数据。
- 事务 B 在事务 A 的查询条件中插入新的行,然后提交。
- 事务 A 再次使用相同的条件查询数据时,发现多出了一些行。
幻读主要由插入(INSERT)或删除(DELETE)操作引起,与不可重复读类似,但更关注于整个数据集的一致性,而不仅仅是单条记录的一致性。
事务隔离级别
事务隔离级别定义了一个事务可能受其他并发事务影响的程度。
MySQL 支持四种隔离级别:
- READ UNCOMMITTED(未提交读): 可以读取未提交的数据,存在脏读和不可重复读问题。
- READ COMMITTED(提交读): 只能读取已提交的数据,避免脏读,但不可重复读和幻读问题仍然存在。
- REPEATABLE READ(可重复读): 保证在同一事务中多次读取的数据一致,MySQL 默认级别,解决了不可重复读,但可能有幻读。
- SERIALIZABLE(可串行化): 最高隔离级别,事务串行执行,避免了所有读取问题,但并发性能较差。
查询当前事务隔离级别:
sql
SELECT @@transaction_isolation;
设置事务隔离级别:
sql
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
在 MySQL 数据库系统中,连接到服务器后,可以访问其管理的多个数据库。
每个数据库可能包括以下组成部分:
- 表:用于存储数据的基本结构。
- 视图:基于表的查询结果集,用于简化和定制数据访问。
- 存储过程:用于执行一系列 SQL 语句的预编译代码块,可以接受参数并在数据库服务器上执行。
- 函数:执行运算并返回一个值的 SQL 语句集。
视图的创建与应用
视图是基于 SQL 查询创建的虚拟表,主要用于简化复杂的 SQL 查询。
例如,使用以下 SQL 语句基于 customers 和 orders 表创建一个视图 customer_orders:
sql
-- 创建一个名为customer_orders的视图
CREATE VIEW customer_orders AS
-- 选择以下列来定义视图的内容
SELECT
c.name AS customer_name, -- 从customers表中选择name列,并将其重命名为customer_name
o.id AS order_id, -- 从orders表中选择id列,并将其重命名为order_id
o.order_date, -- 选择orders表中的order_date列
o.total_amount -- 选择orders表中的total_amount列
FROM customers c -- 指定从customers表中选择数据,给这个表指定一个别名c
JOIN orders o -- 与orders表进行连接操作,给这个表指定一个别名o
ON c.id = o.customer_id; -- 定义连接条件,即customers表的id列与orders表的customer_id列相匹配
优点:
- 简化查询:用户可以通过简单的 SELECT * FROM customer_orders 查询复杂的数据集。
- 权限控制:通过视图可以限制对基础数据的访问,只展示必要的数据。
限制:
- 视图主要用于数据查询,对于数据的增删改操作有限制,通常只适用于包含单一数据表的视图。
存储过程的创建与调用
存储过程是一种在数据库中保存的程序,可以优化常用的或复杂的操作。
下面是一个名为 get_customer_orders 的存储过程,用于查询特定客户的所有订单:
sql
-- 设置SQL语句的分隔符为美元符号$
DELIMITER $
-- 创建一个名为get_customer_orders的存储过程
CREATE PROCEDURE get_customer_orders(IN customer_id INT)
BEGIN
-- 存储过程的内容是执行一个SELECT查询
SELECT o.id AS order_id, -- 选择orders表的id列,并将其重命名为order_id
o.order_date, -- 选择orders表中的order_date列
o.total_amount -- 选择orders表中的total_amount列
FROM orders o -- 指定从orders表中选择数据,并给这个表指定一个别名o
WHERE o.customer_id = customer_id; -- 查询条件是orders表中的customer_id列等于传入的customer_id参数
END $
-- 恢复SQL语句的分隔符为分号;
DELIMITER ;
使用方法:
- 存储过程通过 CALL get_customer_orders(5); 调用,其中 5 是客户 ID。
函数的创建与应用
函数是可以返回单个值的 SQL 程序。例如,创建一个计算平方的函数 square:
sql
DELIMITER $
-- 设置语句的分隔符为$,这是为了将整个CREATE FUNCTION语句作为一个整体执行,特别是当函数体内有多条语句时。
CREATE FUNCTION square(x INT) RETURNS INT
-- 创建一个名为square的函数,这个函数接收一个整型参数x,并返回一个整型值。这个返回值是参数x的平方。
BEGIN
-- BEGIN和END之间是函数的主体部分。
DECLARE result INT;
-- 声明一个名为result的整型变量,用于存储计算结果。
SET result = x * x;
-- 计算参数x的平方,并将结果赋值给变量result。
RETURN result;
-- 返回变量result的值,即x的平方。
END $
-- 函数主体结束。
DELIMITER ;
-- 将语句的分隔符重置为默认的分号(;)。
在 MySQL 中,默认情况下不允许创建函数,需要先设置:
sql
SET GLOBAL log_bin_trust_function_creators = 1;
实际应用:
- 函数可以用于查询,例如 SELECT product_name, square(price) FROM order_items_view;。
- 创建复杂函数,如 get_order_total,用于计算订单的总金额:
sql
-- 设置语句结束符为 $,这是为了在创建函数时,可以把整个函数体看作是一个语句
DELIMITER $
-- 创建一个名为 get_order_total 的函数,这个函数接收一个整型参数 order_id
CREATE FUNCTION get_order_total(order_id INT) RETURNS DECIMAL(10,2)
BEGIN
-- 声明一个名为 total 的变量,用于存储计算出的订单总额,数据类型为 DECIMAL(10,2),即最多10位数,其中小数点后2位
DECLARE total DECIMAL(10,2);
-- 从 order_items 表中选择与传入的 order_id 相匹配的所有条目,计算它们的 quantity(数量)与 price(价格)之积的总和
-- 并将这个总和赋值给之前声明的 total 变量
SELECT SUM(quantity * price) INTO total FROM order_items WHERE order_id = order_id;
-- 返回计算出的订单总额
RETURN total;
END $
-- 恢复语句结束符为 ;,即将 DELIMITER 设置回默认的 ;
DELIMITER ;
调用方法:
- 通过 SELECT id, get_order_total(id) FROM orders; 来获取订单的总金额。
存储过程和函数的区别
目的和用途
- 存储过程:主要用于执行动作,如数据插入、更新、删除等。它可以执行一系列复杂的 SQL 语句,并进行事务管理。
- 函数:主要用于计算并返回一个值。函数通常用于在 SQL 语句中进行计算,如转换数据、计算数值等。
返回值
- 存储过程:可以返回零个、一个或多个值(通常通过输出参数或结果集),也可以不返回任何值。
- 函数:必须返回一个值,这是其定义的一部分。
调用方式
- 存储过程:使用 CALL 语句调用。
- 函数:可以直接在 SQL 语句中调用,如在 SELECT、WHERE 或 CASE 等语句中。
使用场景
- 存储过程:适用于需要执行多个步骤的业务逻辑,或当需要使用循环、条件语句等复杂控制结构时。
- 函数:适用于需要在查询中重复使用的计算。由于函数必须是确定性的或不依赖于外部状态,它们在数据处理中非常有用。
事务处理
- 存储过程:可以控制事务(即事务的开始和结束)。这使得存储过程适合处理复杂的业务逻辑,其中可能需要回滚操作或提交多个步骤作为一个整体。
- 函数:通常不处理事务。在函数中进行事务控制可能会导致不可预测的结果。