高级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;
当函数中只有一条语句时,可以省略begin
和end
,下面来改造刚刚的代码。
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触发器
何时不用触发器
不用触发器就能实现的功能就不用触发器
因为触发器有点危险,可能造成某条语句执行失败,还可能造成链式触发器调用,循环触发器调用。