527 lines
17 KiB
TeX
527 lines
17 KiB
TeX
|
\documentclass[全部作业]{subfiles}
|
|||
|
\input{mysubpreamble}
|
|||
|
|
|||
|
% 不知道为什么这里又变成不用framed才能不出现换页问题了
|
|||
|
% \BeforeBeginEnvironment{minted}{}
|
|||
|
% \AfterEndEnvironment{minted}{}
|
|||
|
% 这样好像不能替换掉原来的,那就只能把framed移动到需要的部分里了
|
|||
|
|
|||
|
\begin{document}
|
|||
|
\setcounter{chapter}{5}
|
|||
|
\chapter{第六次作业}
|
|||
|
\begin{enumerate}
|
|||
|
\item \choice[1]{关于游标,选项中说法错误的是()
|
|||
|
A
|
|||
|
游标可以定在查询结果集的特定行,也可以从结果集的当前行检索一行或多行
|
|||
|
|
|||
|
B
|
|||
|
通常我们并不使用游标,但是需要逐条处理数据的时候,游标显得十分重要
|
|||
|
|
|||
|
C
|
|||
|
游标使用时必须在完成后关闭,以释放资源
|
|||
|
|
|||
|
D
|
|||
|
游标的 SELECT 语句中可以使用 INTO 子句来创建新表
|
|||
|
|
|||
|
}{4}
|
|||
|
\item \choice[1]{下列关于触发器的描述正确的是()
|
|||
|
A
|
|||
|
MySql的触发器只支持行级出发,不支持语句级触发
|
|||
|
|
|||
|
B
|
|||
|
触发器可以调用将数据返回客户端的存储程序
|
|||
|
|
|||
|
C
|
|||
|
在触发器中可以使用显式或者隐式方式开始或结束事务的语句
|
|||
|
|
|||
|
D
|
|||
|
在MySql中,不可使用new和old引用触发器中发生的记录内容
|
|||
|
}{1}
|
|||
|
\item \choice[1]{关于before和after触发器的说法中,哪一项是正确的?()
|
|||
|
A
|
|||
|
在 BEFORE 触发器中,可以对 INSERT 和 UPDATE 的 NEW 值进行修改;而在 AFTER 触发器中,无法修改 NEW 值
|
|||
|
|
|||
|
B
|
|||
|
在 AFTER 触发器中,可以修改 NEW 值,但在 BEFORE 触发器中不能修改
|
|||
|
|
|||
|
C
|
|||
|
BEFORE 触发器不能用于 UPDATE 操作
|
|||
|
|
|||
|
D
|
|||
|
AFTER 触发器可以对 INSERT 和 UPDATE 的 NEW 值进行修改
|
|||
|
|
|||
|
|
|||
|
}{1}
|
|||
|
\item \choice[1]{关于 MySQL 触发器中 SELECT 语句的使用,以下哪种说法是正确的?()
|
|||
|
A
|
|||
|
触发器中可以包含 SELECT 语句,并且可以返回结果集
|
|||
|
|
|||
|
B
|
|||
|
触发器中可以包含 SELECT 语句,但不能返回结果集
|
|||
|
|
|||
|
C
|
|||
|
触发器中必须包含 SELECT 语句,以便执行其他操作
|
|||
|
|
|||
|
D
|
|||
|
触发器中可以使用 SELECT 语句来更新表的记录
|
|||
|
|
|||
|
}{2}
|
|||
|
\item \choice[1]{关于存储过程的说法中,哪一项是正确的?()
|
|||
|
A
|
|||
|
存储过程可以通过 SELECT 语句直接返回结果集给调用者
|
|||
|
|
|||
|
B
|
|||
|
存储过程只能接受一个输入参数,并且不能返回任何值
|
|||
|
|
|||
|
C
|
|||
|
存储过程可以使用 BEGIN ... END 块来定义多个 SQL 语句的执行逻辑
|
|||
|
|
|||
|
D
|
|||
|
存储过程的定义必须包含 CREATE FUNCTION 关键字
|
|||
|
|
|||
|
}{3}
|
|||
|
\questionandanswer[]{
|
|||
|
创建一个名为 check_student_age_trigger 的触发器,该触发器用于在 students 表中插入新记录之前进行年龄检查。要求如下:
|
|||
|
|
|||
|
触发器在执行 INSERT 操作之前触发。
|
|||
|
|
|||
|
如果新插入的学生年龄(age 列)小于 18 岁,触发器应该将 age 设置为 18。
|
|||
|
|
|||
|
如果新插入的学生年龄大于 120 岁,触发器应该将 age 设置为 120。
|
|||
|
|
|||
|
触发器应该记录所有插入操作的原始年龄值到一个名为 age_log 的表中,表结构如下:
|
|||
|
|
|||
|
log_id: INT, 主键,自增
|
|||
|
|
|||
|
student_id: INT, 学生学号
|
|||
|
|
|||
|
original_age: INT,原始年龄值
|
|||
|
|
|||
|
log_time: DATETIME,记录插入的时间
|
|||
|
|
|||
|
要求:
|
|||
|
|
|||
|
(1)编写 SQL 语句创建 age_log 表。
|
|||
|
|
|||
|
(2)编写 SQL 语句创建 check_student_age_trigger 触发器。
|
|||
|
|
|||
|
提示:
|
|||
|
|
|||
|
使用 NEW 关键字访问将要插入的记录中的字段。
|
|||
|
|
|||
|
使用 INSERT INTO 语句将原始年龄记录到 age_log 表中。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
示例:
|
|||
|
|
|||
|
如果插入一条记录:INSERT INTO students (name, age) VALUES ('Alice', 16);
|
|||
|
|
|||
|
触发器将插入 age 为 18,并在 age_log 表中记录 original_age 为 16。
|
|||
|
}{}
|
|||
|
{\kaishu
|
|||
|
\begin{minted}{SQL}
|
|||
|
create table age_log
|
|||
|
(
|
|||
|
log_id int primary key auto_increment,
|
|||
|
student_id int comment '学生学号',
|
|||
|
original_age int comment '原始年龄值',
|
|||
|
log_time datetime comment '记录插入的时间'
|
|||
|
);
|
|||
|
|
|||
|
create trigger check_student_age_trigger
|
|||
|
before insert
|
|||
|
on students
|
|||
|
for each row
|
|||
|
begin
|
|||
|
insert into age_log(student_id, original_age, log_time) value (new.id, new.age, now());
|
|||
|
case
|
|||
|
when (new.age < 18) then set new.age = 18;
|
|||
|
when (new.age > 120) then set new.age = 120;
|
|||
|
end case;
|
|||
|
end;
|
|||
|
\end{minted}
|
|||
|
}
|
|||
|
\begin{verification}
|
|||
|
{\small
|
|||
|
\begin{minted}{SQL}
|
|||
|
drop table if exists students;
|
|||
|
drop table if exists age_log;
|
|||
|
|
|||
|
create table students
|
|||
|
(
|
|||
|
id int primary key,
|
|||
|
name varchar(100) comment '姓名',
|
|||
|
age int comment '年龄'
|
|||
|
);
|
|||
|
|
|||
|
create table age_log
|
|||
|
(
|
|||
|
log_id int primary key auto_increment,
|
|||
|
student_id int comment '学生学号',
|
|||
|
original_age int comment '原始年龄值',
|
|||
|
log_time datetime comment '记录插入的时间'
|
|||
|
);
|
|||
|
|
|||
|
create trigger check_student_age_trigger
|
|||
|
before insert
|
|||
|
on students
|
|||
|
for each row
|
|||
|
begin
|
|||
|
insert into age_log(student_id, original_age, log_time) value (new.id, new.age, now());
|
|||
|
case
|
|||
|
when (new.age < 18) then set new.age = 18;
|
|||
|
when (new.age > 120) then set new.age = 120;
|
|||
|
end case;
|
|||
|
end;
|
|||
|
|
|||
|
insert into students(id, name, age)
|
|||
|
values (10001, 'Alice', 16),
|
|||
|
(10002, 'Bob', 150);
|
|||
|
\end{minted}
|
|||
|
}
|
|||
|
|
|||
|
students:
|
|||
|
\begin{csv}
|
|||
|
,id,name,age
|
|||
|
1,10001,Alice,18
|
|||
|
2,10002,Bob,120
|
|||
|
\end{csv}
|
|||
|
|
|||
|
age_log:
|
|||
|
\begin{csv}
|
|||
|
,log_id,student_id,original_age,log_time
|
|||
|
1,1,10001,16,2024-11-01 15:39:52
|
|||
|
2,2,10002,150,2024-11-01 15:39:52
|
|||
|
\end{csv}
|
|||
|
\end{verification}
|
|||
|
\questionandanswer[]{
|
|||
|
假设有以下两个表:
|
|||
|
|
|||
|
\noindent\includegraphics[width=1\linewidth]{imgs/2024-11-01-16-47-16.png}
|
|||
|
|
|||
|
创建一个名为 withdraw_funds 的存储过程,用于从银行账户中提款。要求如下:
|
|||
|
|
|||
|
存储过程接受两个参数:
|
|||
|
|
|||
|
account_id: INT, 要提款的账户的 ID
|
|||
|
|
|||
|
amount: DECIMAL(10, 2), 要提款的金额
|
|||
|
|
|||
|
在提款前检查该账户的余额是否足够支付提款金额。
|
|||
|
|
|||
|
如果余额不足,存储过程应返回一个错误消息。
|
|||
|
|
|||
|
如果提款成功,更新账户余额,并在 transactions 表中记录提款信息(转出账户为提款账户,转入账户为 NULL,金额为提款金额)。
|
|||
|
|
|||
|
}{}
|
|||
|
{\kaishu
|
|||
|
\begin{minted}{SQL}
|
|||
|
create procedure withdraw_funds(in _account_id int, in amount decimal(10, 2))
|
|||
|
begin
|
|||
|
start transaction;
|
|||
|
-- 如果账户不存在呢?好像没有交代应该怎么处理
|
|||
|
if (select accounts.balance from accounts where accounts.account_id = _account_id) < amount then
|
|||
|
rollback ;
|
|||
|
signal sqlstate '45000' set message_text = '余额不足';
|
|||
|
end if;
|
|||
|
update accounts set balance = balance - amount where accounts.account_id = _account_id;
|
|||
|
insert into transactions(from_account_id, to_account_id, amount) value (_account_id, null, amount);
|
|||
|
commit;
|
|||
|
end;
|
|||
|
\end{minted}
|
|||
|
}
|
|||
|
\begin{verification}
|
|||
|
{\small
|
|||
|
\begin{minted}{SQL}
|
|||
|
drop table if exists accounts;
|
|||
|
drop table if exists transactions;
|
|||
|
|
|||
|
create table accounts
|
|||
|
(
|
|||
|
-- 自增好像是从1开始的
|
|||
|
account_id int primary key auto_increment comment '账户唯一标识符',
|
|||
|
account_holder varchar(100) comment '账户持有者的姓名',
|
|||
|
balance decimal(10, 2) comment '账户余额'
|
|||
|
);
|
|||
|
|
|||
|
create table transactions
|
|||
|
(
|
|||
|
transaction_id int primary key auto_increment comment '交易的唯一标识符',
|
|||
|
from_account_id int comment '转出账户的ID',
|
|||
|
to_account_id int comment '转入账户的ID',
|
|||
|
amount decimal(10, 2) comment '转账金额',
|
|||
|
transaction_time datetime default current_timestamp comment '转账时间'
|
|||
|
);
|
|||
|
|
|||
|
insert into accounts(account_holder, balance)
|
|||
|
values ('a', 10),
|
|||
|
('b', 100);
|
|||
|
|
|||
|
drop procedure if exists withdraw_funds;
|
|||
|
create procedure withdraw_funds(in _account_id int, in amount decimal(10, 2))
|
|||
|
begin
|
|||
|
start transaction;
|
|||
|
-- 如果账户不存在呢?好像没有交代应该怎么处理
|
|||
|
if (select accounts.balance from accounts where accounts.account_id = _account_id) < amount then
|
|||
|
rollback ;
|
|||
|
signal sqlstate '45000' set message_text = '余额不足';
|
|||
|
end if;
|
|||
|
update accounts set balance = balance - amount where accounts.account_id = _account_id;
|
|||
|
insert into transactions(from_account_id, to_account_id, amount) value (_account_id, null, amount);
|
|||
|
commit;
|
|||
|
end;
|
|||
|
|
|||
|
call withdraw_funds(2, 10);
|
|||
|
call withdraw_funds(1, 10);
|
|||
|
call withdraw_funds(1, 10);
|
|||
|
\end{minted}
|
|||
|
}
|
|||
|
|
|||
|
\begin{minted}{text}
|
|||
|
[2024-11-01 16:35:33] [45000][1644] 余额不足
|
|||
|
\end{minted}
|
|||
|
|
|||
|
accounts:
|
|||
|
\begin{csv}
|
|||
|
,account_id,account_holder,balance
|
|||
|
1,1,a,0.00
|
|||
|
2,2,b,90.00
|
|||
|
\end{csv}
|
|||
|
\vspace{1em}
|
|||
|
|
|||
|
transactions:\vspace{1em}\\
|
|||
|
\small\begin{csv}
|
|||
|
,transaction_id,from_account_id,to_account_id,amount,transaction_time
|
|||
|
1,1,2,,10.00,2024-11-01 16:35:33
|
|||
|
2,2,1,,10.00,2024-11-01 16:35:34
|
|||
|
\end{csv}
|
|||
|
\end{verification}
|
|||
|
|
|||
|
\questionandanswer[]{
|
|||
|
请根据下图所示的银行数据库,编写一个触发器来执行下列操作:在删除一个账户时,检查该账户的拥有者是否还有其他账户,如果没有,则将其从depositor关系中删除。
|
|||
|
\includegraphics[width=1\linewidth]{imgs/2024-11-01-21-57-55.png}
|
|||
|
}{}
|
|||
|
{\kaishu
|
|||
|
这里为什么要检测是否有其他账户,删除一个账户时不管这个人有没有其他账户都需要从depositor里删除account_number对应的行吧,那删除最后一个账户时depositor里自然也删完了。
|
|||
|
\begin{minted}{SQL}
|
|||
|
create trigger delete_account
|
|||
|
after delete
|
|||
|
on account
|
|||
|
for each row
|
|||
|
begin
|
|||
|
delete from depositor where account_number = old.account_number;
|
|||
|
end;
|
|||
|
\end{minted}
|
|||
|
}
|
|||
|
\begin{verification}
|
|||
|
\begin{minted}{SQL}
|
|||
|
drop table if exists account;
|
|||
|
drop table if exists depositor;
|
|||
|
|
|||
|
create table account
|
|||
|
(
|
|||
|
account_number int primary key,
|
|||
|
branch_name varchar(100),
|
|||
|
balance int
|
|||
|
);
|
|||
|
|
|||
|
create table depositor
|
|||
|
(
|
|||
|
customer_name varchar(100),
|
|||
|
account_number int references account.account_number
|
|||
|
);
|
|||
|
|
|||
|
insert into account
|
|||
|
values (1, 'a', 100),
|
|||
|
(2, 'b', 200);
|
|||
|
|
|||
|
insert into depositor
|
|||
|
values ('c', 1),
|
|||
|
('c', 2);
|
|||
|
|
|||
|
create trigger delete_account
|
|||
|
after delete
|
|||
|
on account
|
|||
|
for each row
|
|||
|
begin
|
|||
|
delete from depositor where account_number = old.account_number;
|
|||
|
end;
|
|||
|
|
|||
|
delete from account where account_number = 1;
|
|||
|
select * from depositor;
|
|||
|
\end{minted}
|
|||
|
\begin{csv}
|
|||
|
,customer_name,account_number
|
|||
|
1,c,2
|
|||
|
\end{csv}
|
|||
|
\begin{minted}{SQL}
|
|||
|
delete from account where account_number = 2;
|
|||
|
select * from depositor;
|
|||
|
\end{minted}
|
|||
|
\begin{csv}
|
|||
|
,customer_name,account_number
|
|||
|
,,
|
|||
|
\end{csv}
|
|||
|
\end{verification}
|
|||
|
|
|||
|
\questionandanswer[]{
|
|||
|
假设有一个名为 employees 的员工表和一个名为 departments 的部门表,结构如下:
|
|||
|
\includegraphics[width=1\linewidth]{imgs/2024-11-02-11-15-08.png}
|
|||
|
(1)存储过程:名为 increase_salary,用于根据部门名称增加员工工资。要求:
|
|||
|
|
|||
|
接受两个参数:dept_name(部门名称)和 percentage(增加的百分比)。
|
|||
|
|
|||
|
如果部门不存在,返回错误消息。
|
|||
|
|
|||
|
如果部门存在,更新该部门所有员工的工资,并返回更新的员工数量。
|
|||
|
|
|||
|
}{}
|
|||
|
{\kaishu
|
|||
|
\begin{minted}{SQL}
|
|||
|
create procedure increase_salary(in dept_name varchar(100), in percentage int)
|
|||
|
begin
|
|||
|
if (select count(*) from departments where dept_name = departments.department_name) <= 0 then
|
|||
|
signal sqlstate '45000' set message_text = '部门不存在';
|
|||
|
end if;
|
|||
|
update employees join departments using (department_id)
|
|||
|
set salary = salary * (1 + percentage / 100)
|
|||
|
where department_name = dept_name;
|
|||
|
-- 存储过程怎么返回更新的员工数量
|
|||
|
-- 30 ms 中有 2 行受到影响 返回的影响行数应该就能反映更新的员工数量了
|
|||
|
end;
|
|||
|
\end{minted}
|
|||
|
}
|
|||
|
\questionandanswer[]{
|
|||
|
(2)函数:名为 get_average_salary,用于获取指定部门的平均工资。要求:
|
|||
|
|
|||
|
接受一个参数:dept_id(部门 ID)。
|
|||
|
|
|||
|
返回该部门员工的平均工资,如果没有员工,则返回 0。
|
|||
|
}{}
|
|||
|
{\kaishu
|
|||
|
\begin{minted}{SQL}
|
|||
|
create function get_average_salary(dept_id int) returns decimal(10, 2)
|
|||
|
begin
|
|||
|
declare result decimal(10, 2);
|
|||
|
if (select count(*) from employees where department_id = dept_id) <= 0 then
|
|||
|
set result = 0;
|
|||
|
else
|
|||
|
select avg(employees.salary) into result from employees where department_id = dept_id;
|
|||
|
end if;
|
|||
|
return result;
|
|||
|
end;
|
|||
|
\end{minted}
|
|||
|
}
|
|||
|
\begin{verification}
|
|||
|
\begin{minted}{SQL}
|
|||
|
drop table if exists employees;
|
|||
|
drop table if exists departments;
|
|||
|
drop procedure if exists increase_salary;
|
|||
|
drop function if exists get_average_salary;
|
|||
|
|
|||
|
create table employees
|
|||
|
(
|
|||
|
employee_id int primary key auto_increment comment '员工唯一标识符',
|
|||
|
employee_name varchar(100) comment '员工姓名',
|
|||
|
department_id int comment '部门ID',
|
|||
|
salary decimal(10, 2) comment '员工工资'
|
|||
|
);
|
|||
|
create table departments
|
|||
|
(
|
|||
|
department_id int primary key auto_increment comment '部门唯一标识符',
|
|||
|
department_name varchar(100) comment '部门名称'
|
|||
|
);
|
|||
|
\end{minted}
|
|||
|
\quad
|
|||
|
% 大概知道了,只要在前一页结束前的位置加一个minted分段,并且在这个分段前面或后面存在一个字符,即使是空白的\quad 也可以,就不会出现分页异常的问题了
|
|||
|
\begin{minted}{SQL}
|
|||
|
create procedure increase_salary(in dept_name varchar(100), in percentage int)
|
|||
|
begin
|
|||
|
if (select count(*) from departments where dept_name = departments.department_name) <= 0 then
|
|||
|
signal sqlstate '45000' set message_text = '部门不存在';
|
|||
|
end if;
|
|||
|
update employees join departments using (department_id)
|
|||
|
set salary = salary * (1 + percentage / 100)
|
|||
|
where department_name = dept_name;
|
|||
|
-- 存储过程怎么返回更新的员工数量
|
|||
|
-- 30 ms 中有 2 行受到影响 返回的影响行数应该就能反映更新的员工数量了
|
|||
|
end;
|
|||
|
|
|||
|
create function get_average_salary(dept_id int) returns decimal(10, 2)
|
|||
|
begin
|
|||
|
declare result decimal(10, 2);
|
|||
|
if (select count(*) from employees where department_id = dept_id) <= 0 then
|
|||
|
set result = 0;
|
|||
|
else
|
|||
|
select avg(employees.salary) into result from employees where department_id = dept_id;
|
|||
|
end if;
|
|||
|
return result;
|
|||
|
end;
|
|||
|
|
|||
|
insert into departments
|
|||
|
values (101, 'aaa'),
|
|||
|
(102, 'bbb');
|
|||
|
|
|||
|
insert into employees(employee_name, department_id, salary)
|
|||
|
values ('a', 101, 10),
|
|||
|
('b', 101, 100),
|
|||
|
('c', 102, 1000);
|
|||
|
\end{minted}
|
|||
|
\begin{minted}{SQL}
|
|||
|
select get_average_salary(101);
|
|||
|
\end{minted}
|
|||
|
\begin{csv}
|
|||
|
,get_average_salary(101)
|
|||
|
1,55.00
|
|||
|
\end{csv}
|
|||
|
\begin{minted}{SQL}
|
|||
|
select get_average_salary(103);
|
|||
|
\end{minted}
|
|||
|
\begin{csv}
|
|||
|
,get_average_salary(103)
|
|||
|
1,0.00
|
|||
|
\end{csv}
|
|||
|
\begin{minted}{SQL}
|
|||
|
call increase_salary('aaa', 50);
|
|||
|
\end{minted}
|
|||
|
\begin{minted}{text}
|
|||
|
[2024-11-02 11:13:13] 30 ms 中有 2 行受到影响
|
|||
|
\end{minted}
|
|||
|
\begin{minted}{SQL}
|
|||
|
select * from employees;
|
|||
|
\end{minted}
|
|||
|
\begin{csv}
|
|||
|
,employee_id,employee_name,department_id,salary
|
|||
|
1,1,a,101,15.00
|
|||
|
2,2,b,101,150.00
|
|||
|
3,3,c,102,1000.00
|
|||
|
\end{csv}
|
|||
|
\begin{minted}{SQL}
|
|||
|
call increase_salary('ccc', 100);
|
|||
|
\end{minted}
|
|||
|
\begin{minted}{text}
|
|||
|
[2024-11-02 11:22:51] [45000][1644] 部门不存在
|
|||
|
\end{minted}
|
|||
|
\end{verification}
|
|||
|
|
|||
|
\questionandanswer[]{
|
|||
|
存储过程、函数和触发器的区别?
|
|||
|
}{
|
|||
|
存储过程不能使用return语句返回结果,只能通过传入参数设置为out来返回结果;而函数可以直接使用return语句返回结果。
|
|||
|
触发器没有传入参数也不能返回结果,只能通过old和new获取更新前后的值。
|
|||
|
}
|
|||
|
|
|||
|
\questionandanswer[]{
|
|||
|
触发器的作用?Mysql表中允许有多少个触发器?
|
|||
|
}{
|
|||
|
触发器定义了一系列操作,这一系列操作称为触发程序,当触发事件发生时,触发程序会自动运行。
|
|||
|
触发器主要用于监视某个表的插入(insert)、更新(update)和删除(delete)等更新操作,这些操作可以分别激活该表的insert、update和delete类型的触发程序运行,从而实现数据的自动维护。
|
|||
|
|
|||
|
在5.7.2版本以前,同一个表不能创建两个相同触发时间、触发事件的触发程序,那么就是6个触发器:
|
|||
|
before insert、after insert、before update、after update、before delete、after delete。
|
|||
|
|
|||
|
在5.7.2版本之后没有此限制,那么对触发器的数量就没有限制了。
|
|||
|
}
|
|||
|
\end{enumerate}
|
|||
|
\end{document}
|