Loading

高级SQL

函数和过程

虽然函数的功能在应用层面也能够解决,而且可能比数据库语言更加强大,但是这会徒增网络流量成本并且在连接该数据库的每一个应用中,都要编写一套相同函数。在数据库中编写,只需要一次。

声明函数

下面的代码声明了一个函数,这个函数接收一个系名作为参数,返回该系的人数。

create
  function dept_count(dept_name varchar(20))
  returns int
begin
  declare d_count int;
    select count(*) into d_count
    from instructor
    where instructor.dept_name = dept_name;
  return d_count;
end;

语法不多说了。下面看下MYSQL中怎么写。

DELIMITER //
create
  function dept_count(dept_name varchar(20))
  returns int
BEGIN
  declare d_count int;
    select count(*) into d_count
    from instructor
    where instructor.dept_name = dept_name;
  return d_count;
END//
DELIMITER ;

由于MySQL中一条语句的结束标志是;,函数里也可能有;,MySQL没那么智能,在函数中遇到;它也会认为是一条语句编写完毕,然后立即去执行这条语句。所以我们要用DELIMITER来将结束标志暂时设为//,保证我们在代码中正常使用;而不会被MySQL误解。

END//,使用了刚刚定义的暂时结束标志//,表明现在我的语句输入完了,MySQL这才去执行这个函数定义。别忘了执行完要把结束符改回来,所以有了DELIMITER ;

下面可以使用这个进行查询,比如下面就是查询教员数大于1个的系的名称和预算。

select dept_name, budget
from department
where dept_count(dept_name) > 1;

当函数中只有一条语句时,可以省略beginend,下面来改造刚刚的代码。

create
  function dept_count_1_line(dept_name varchar(20))
  returns int

  return (
    select count(*) from instructor
    where instructor.dept_name = dept_name
  );

返回关系

SQL标准支持在函数中返回关系。

create
  function instructor_of(dept_name varchar(20))
  returns table(
    ID varchar(5),
    name varchar(20),
    dept_name varchar(20),
    salary numeric(8,2)
  )

  return table(
    select * from instructor
    where instructor.dept_name = dept_name
  );

然后可以把它在查询语句中应用

select *
  from table (instructor_of('Finance'));

MYSQL不支持在函数中返回关系,如果需要对应的功能,则需要定义过程。

下面是MYSQL创建函数语法的定义,只允许返回{STRING|INTEGER|REAL|DECIMAL}

CREATE [AGGREGATE] FUNCTION function_name RETURNS {STRING|INTEGER|REAL|DECIMAL}
    SONAME shared_library_name

声明过程

DELIMITER //
create procedure dept_count_proc(in dept_name varchar(20), out d_count int)
  begin
    select count(*) into d_count
    from instructor
    where instructor.dept_name = dept_name;
  end//
DELIMITER ;
call dept_count_proc('Comp. Sci.');
select @d_count;

如上是MYSQL中的使用,正常的使用应该是

declare dept_count int;
call dept_count_proc('Comp. Sci.', dept_count);
select dept_count;

MySQL看起来确实简洁了不少,但是语法有点让我懵逼。

SQL允许多个过程同名,只要参数不同。

函数和过程的区别

从定义上来说,函数(Function)是数学中的概念,代表一个集合到另一个集合的映射关系,所以它有输入,有返回值,而过程(Procedure)是计算机领域独有的说法,它只代表一个任务的执行,不构成一个集合到另一个集合的映射关系。过程的其它名字还有:例程(routine)、子程序(subprogram)、子例程(subroutine)等。

所以刚刚的create function是通过returns语句明确标注返回值的,而procedure则没有返回值,而是它去执行一些固定任务,然后以参数的形式把任务的结果设置给你。

在计算机领域里过程和函数的区分和界限好像有点模糊...

由于函数只能代表一个集合到另一个集合的映射,所以它没法具有多返回值,尽管很多现代编程语言都使用了类似解构的方法还有各种语法糖来实现多返回值,但本质上函数还是只能有一个返回值,但存储过程不一样,它本没有函数的限制,存储过程可以主动的设置你传递进去的out参数,所以你可以用它处理多返回值的情况,也就是一个集合到多个集合的映射。

其实存储过程很像你在C/C++中使用指针作为参数传递进函数,然后这块内存的操作权就完全归调用的函数所有了。但是用惯了现代语言的我很讨厌这种把控制权完全交给一个函数的感觉。。。感觉我被绑架了那种。。。

其他语法

-- 将其中包含的语句作为一个事务处理
begin atomic 
  ... 
end
-- 循环
while <condition> do 
  ... 
end while
-- 循环
repeat
  ...
until <condition>
end repeat
-- 循环,每次从关系中获取每一个元组,存入变量`<tuple>`,进行循环。
for <tuple> as <relation> do
 ...
end for

-- example
for t as 
  (select * from instructor)
  do
 ...
end for
-- 循环分支语句
if <condition> then 
  ... 
elseif <condition> then
  ...
else 
  ... 
end if

触发器

触发器用于指定一个对数据库进行修改的语句被执行时所采取的操作。之前参照完整性所不能完全覆盖的功能,可以使用触发器进行编写。

下面先使用标准SQL语法来学习触发器。

create trigger time_slot_check1 after insert on section
  referencing new row as nrow
  for each row
  when (nrow.time_slot_id not in (
    select time_slot_id
    from time_slot
  ))
  begin 
    rollback
  end;

上面的语句使用create trigger定义了一个触发器,after insert on section指定了当section表发生插入时,触发器被触发。

for each row就是对于每一个插入的行都执行,因为可能插入多行。

referencing new row as nrow语句将新插入的行命名为一个名字为nrow的变量,以便一会儿访问它。

when指定了一个条件,当新行的time_slot_id属性不在time_slot表中存在的话,那么就执行begin...end中的操作,也就是当插入一个不存在的时间片段时回滚刚刚的插入语句。保证了参照完整性。

create trigger time_slot_check2 after delete on time_slot
  referencing old row as orow
  for each row
  when (orow.time_slot_id not in (
    select time_slot_id from time_slot
  ) and orow.time_slot_id in (
    select time_slot_id from section
  ))
  begin
    rollback
  end;

上面的语句差不多,是在time_slot表中删除时检测刚刚删除的字段在section表中是否还有引用,如果有就不允许删除。

对于update语句,我们可以监测到字段级别,如下代码只会在takes的grade字段被更改后触发。

after update of takes on (grade)

触发器也可以提前触发(使用before),比如当一个时间片段不存在时,主动插入一个时间片段。

MYSQL触发器

Create Trigger in MySQL

何时不用触发器

不用触发器就能实现的功能就不用触发器

因为触发器有点危险,可能造成某条语句执行失败,还可能造成链式触发器调用,循环触发器调用。

posted @ 2021-10-19 10:04  yudoge  阅读(96)  评论(0编辑  收藏  举报