diff --git a/数据库系统原理与实践/平时作业/mypreamble.tex b/数据库系统原理与实践/平时作业/mypreamble.tex index 3f47a61..a8f63fb 100644 --- a/数据库系统原理与实践/平时作业/mypreamble.tex +++ b/数据库系统原理与实践/平时作业/mypreamble.tex @@ -43,8 +43,6 @@ % 将minted的外框修复为framed,修复换行的问题 \setlength{\FrameRule}{0pt} -\BeforeBeginEnvironment{minted}{\begin{framed}} -\AfterEndEnvironment{minted}{\end{framed}} \ExplSyntaxOn diff --git a/数据库系统原理与实践/平时作业/第三次作业.tex b/数据库系统原理与实践/平时作业/第三次作业.tex index fe19d51..bf99b79 100644 --- a/数据库系统原理与实践/平时作业/第三次作业.tex +++ b/数据库系统原理与实践/平时作业/第三次作业.tex @@ -1,6 +1,9 @@ \documentclass[全部作业]{subfiles} \input{mysubpreamble} +\BeforeBeginEnvironment{minted}{\begin{framed}} +\AfterEndEnvironment{minted}{\end{framed}} + \begin{document} \setcounter{chapter}{2} \chapter{第三次作业} diff --git a/数据库系统原理与实践/平时作业/第五次作业.tex b/数据库系统原理与实践/平时作业/第五次作业.tex index ee3c9c3..b9f5b67 100644 --- a/数据库系统原理与实践/平时作业/第五次作业.tex +++ b/数据库系统原理与实践/平时作业/第五次作业.tex @@ -3,7 +3,7 @@ \begin{document} \setcounter{chapter}{4} -\chapter{第四次作业} +\chapter{第五次作业} \begin{enumerate} \questionandanswer[]{ 一、数据库介绍 diff --git a/数据库系统原理与实践/平时作业/第六次作业.tex b/数据库系统原理与实践/平时作业/第六次作业.tex new file mode 100644 index 0000000..eac491f --- /dev/null +++ b/数据库系统原理与实践/平时作业/第六次作业.tex @@ -0,0 +1,527 @@ +\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} \ No newline at end of file