MySQL8.0与SQL server 2017都是关系型数据库,两者在原理与技术方面有极高的相似度。本文主要简述笔者在学习过程中发现的两者有区别的部分,如果读者知道还有其他区别的点,欢迎进行评论补充,大家一起交流学习,共同成长!
1.在 MySQL 中,服务器处理 SQL 语句默认是以分号作为语句结束标志的。然而,在创建存储过程时,存储过程体可能包含有多条 SQL 语句,这些 SQL 语句如果仍以分号作为语句结束符,那么 MySQL 服务器在处理时会以遇到的第一条 SQL 语句结尾处的分号作为整个程序的结束符,而不再去处理存储过程体中后面的 SQL 语句。为解决这个问题,通常可使用DELIMITER 语句将存储过程结束符号修改为其他字符,笔者统一将其设置为$$。
存储过程可以没有参数(此时存储过程的名称后仍需加上一对括号),也可以有一个或多个参数。MySQL 存储过程支持三种类型的参数,即输入参数、输出参数和输入/输出参数,分别用 IN、OUT 和 INOUT 三个关键字标识。其中,输入参数可以传递给一个存储过程,输出参数用于存储过程返回一个操作结果,而输入/输出参数既可以充当输入参数也可以充当输出参数。需要注意的是,参数的名称不要与数据表的列名相同,否则尽管不会返回出错信息,但是存储过程的 SQL 语句会将参数名看作列名,从而引发不可预知的结果。为此,笔者统一将存储过程中的变量以$为前缀命名。
存储过程的主体部分包含在过程调用时需要执行的 SQL 语句,以关键字 BEGIN 开始,以关键字 END 结束。
drop procedure if exists sp1;
delimiter $$
--注意:MySQL语法规定自定义符合$$和delimiter之间必须至少有一个空格
--另外,不能直接在delimiter $$ 所在行的后面添加注释,否则无法创建存储过程。真有意思。。
create procedure sp1( in $a varchar(100)) --输入参数中的 IN 可以省略。
begin
declare $n,$m int;
set $n=1,$m=2;
if ($n>$m and $a='just a demo') then SELECT 11111;
else SELECT 22222;
end if;
end $$ --注意:MySQL语法规定自定义符合$$和end之间可以没有一个空格
delimiter ; --注意:MySQL语法规定分号和delimiter之间必须至少有一个空格
call sp1('just a demo'); --调用存储过程
/*在以下存储过程中,输入年份、客户名称和商品名称这三个参数,返回这个年度这个客户购买这个商品的
销售量、销售额与购买次数,如果这个客户没有购买这个商品,那么销售量、销售额和购买次数均返回 0。
这里,三个输出参数定义中的 OUT 不能省略,在调用存储过程时需要使用用户变量(@Amount, @qty, @n)
把三个输出变量返回的值接收回来。*/
drop procedure if exists sp2;
delimiter $$
create procedure sp2(
$year int,
$cname varchar(255),
$pname varchar(255),
OUT $qty int,
OUT $amt decimal(12,2),
OUT $xcount int
)
begin
SELECT sum(Amount),sum(Quantity), count(*) into $amt, $qty, $xcount FROM Orderitems a
join Orders b using(OrderID)
join Customers c using(CustomerID)
join Products d using(ProductID)
where year(Orderdate)=$year and companyname=$cname and Productname=$pname;
if ($amt is null) then --这里必须用第一个被赋值的变量,否则set语句执行的结果有问题(写代码要多调试哦)
set $amt=0, $qty=0, $xcount=0;
end if;
end$$
delimiter ;
call sp7(2028,'恒康天诚贸易商行','百威啤酒', @qty,@Amount,@n);
SELECT @Amount, @qty, @n;
而在SQL Server中,与创建视图的规矩一样, CREATE PROCEDURE和CREATE FUNCTION 语句之前必须使用 GO 语句,除非它是批处理中的第一条语句(批处理是指由一个执行指令发出,多条独立的SQL语句一起被执行)。也与创建视图的规矩一样,存储过程的主体定义在 AS 与下一个 GO 语句之间。但是,用户定义函数在AS前面必须要增加一个RETURNS 子句。 存储过程可以没有参数,也可以有一个或多个参数,存储过程的名称后没有括号。SQL Server存储过程支持两种类型的参数,即输入参数和输出参数,输出参数要用output关键字标识。其中,输入参数可以把外部的值传递给存储过程,输出参数用于存储过程返回一个操作结果。
IF (OBJECT_ID ( 'myproc', 'P' ) IS NOT NULL) DROP PROCEDURE myproc
GO
CREATE PROCEDURE myproc @lastname varchar(40), @FirstName varchar(20)
AS
SELECT LastName, FirstName, b.OrderID, b.OrderDate, d.ProductName, c.UnitPrice,
c.Quantity, c.Amount FROM Employees a
INNER JOIN Orders b ON a.EmployeeID=b.EmployeeID
INNER JOIN OrderItems c ON b.OrderID=c.OrderID
INNER JOIN Products d ON c.ProductID=d.ProductID
WHERE LastName=@lastname AND FirstName=@FirstName
GO
/* 执行存储过程,不显式指定输入参数。*/
EXEC myproc 'Davolio', 'Nancy'
/* 执行存储过程,显式指定输入参数及其对应的值。*/
EXECUTE myproc @lastname = 'Davolio', @FirstName = 'Nancy'
/* 执行存储过程,显式指定输入参数,但次序与存储过程定义时不同。*/
myproc @FirstName = 'Nancy', @lastname = 'Davolio'
/*存储过程可以同时包含输入参数、输出参数和返回值。 OUTPUT 参数允许把信息返回给存储过程的调用者。
在调用存储过程时需要定义局部变量来保存 OUTPUT 的值。使用OUTPUT 返回存储过程执行后的值,
其在参数的个数和数据类型上几乎没有限制,而不像RETURN 只能返回一个值,而且是整型的值。*/
/*以下实例创建一个存储过程,输入一个产品类别的名称,通过两个 OUTPUT 变量返回这类产品
单价的最大值和最小值,使用 RETURN 语句返回这类产品的个数。*/
IF (OBJECT_ID('myproc') IS NOT NULL) DROP PROCEDURE myproc
GO
CREATE PROCEDURE myproc
@category varchar(40),
@maxprice real OUTPUT,
@minprice real OUTPUT
AS
DECLARE @count int
IF (SELECT COUNT(*) FROM Products a JOIN Categories b ON b.CategoryID = a.CategoryID
and CategoryName=@category)>0
SELECT @maxprice=MAX(a.UnitPrice), @minprice=MIN(a.UnitPrice), @count=COUNT(*) FROM
Products a JOIN Categories b ON b.CategoryID = a.CategoryID WHERE Categoryname=@category
ELSE
SELECT @maxprice=0, @minprice=0, @count=0
RETURN(@count)
GO
/* 定义 3 个变量以接收存储过程返回的值。参数名与变量名不一定要相同,但数据类型必须匹配。*/
DECLARE @minp real, @maxp real, @num int
EXECUTE @num= myProc 'Confections',@maxp OUTPUT, @minp OUTPUT
/* 可以在执行存储过程中显式指定输出参数及其对应的值。*/
EXECUTE @num= myProc 'Confections',@maxprice=@maxp OUTPUT, @minprice=@minp OUTPUT
SELECT @maxp, @minp, @num
2.MySQL8.0提供的用户定义函数(有时候又称用户自定义函数为“存储函数”)为标量函数,目前还没有提供表值函数,也就是自定义函数 RETURNS 子句指定的数据类型为一种标量数据类型(如 STRING、INTEGER、REAL、DECIMAL 等),而不能类似于一个表结构的数据类型。自定义函数应用非常广泛,可以在MySQL 语句中允许使用表达式的任何位置调用返回自定义函数。 在创建自定义函数时,也需要像存储过程一样使用 DELIMITER 语句将自定义函数的结束符号修改为其他字符,个人建议统一将其设置为$$。在创建自定义函数时,一般需要将 MySQL 参数GLOBAL log_bin_trust_function_creators设置为 1(或 true),该参数控制是否可以信任存储函数创建者,如果该变量设置为 0(默认值),用户就不可以创建或修改自定义函数,除非用户具有除 CREATE ROUTINE 或 ALTER ROUTINE 特权之外的 SUPER 权限。设置为1之后,MySQL 就不会对创建存储函数实施这些限制。 自定义函数体必须包含一个 RETURN 语句,用于指定自定义函数的返回值。当 RETURN 语句中包含 SELECT 语句时,SELECT 语句的返回结果只能是一行一列的值,即只能返回单个数据值。自定义函数函数不能返回一个多行多列的结果集,在使用 SELECT 语句时只能把列值赋值到变量,而且必须使用 into 赋值变量,而不能使用$d:=columnname 在列的右边赋值变量这种形式。
/*输入一个月份(含年份)和客户编码,返回这个客户该月份的销售额合计值,没有销售记录的客户其销售额返回为 0。*/
SET GLOBAL log_bin_trust_function_creators = 1; --一次MySQL会话连接只需要设置一次。
drop function if exists fn1;
delimiter $$
create function fn1( $year int, $month int, $customerid varchar(10) )
returns decimal(12,2) DETERMINISTIC
begin
declare $amt decimal(12,2);
SELECT sum(Amount) into $amt FROM Orderitems where OrderID in (
SELECT Orderid FROM Orders
where year(Orderdate)=$year and month(Orderdate)=$month and CustomerID=$CustomerID);
if ($amt is null) then set $amt=0; end if;
return $amt;
end$$
delimiter ;
/* 调用自定义函数,检索某一个客户的销售额。*/
select fn1(2018, 10, 'AHDTSM') as Amount;
/* 调用自定义函数,检索所有客户的销售额。*/
SELECT CustomerID, Companyname, fn1(2018, 10, customerid) as Amount10 FROM Customers
order by Amount10 desc;
而在SQL Server中, 用户定义函数分为标量值函数和表值函数。如果函数中 RETURNS 子句指定一种标量数据类型,则函数为标量值函数。如果 RETURNS 子句指定 TABLE 类型,则函数为表值函数。根据函数主体的定义方式,表值函数又分为内嵌表值函数和多语句表值函数。如果RETURNS 子句指定的 TABLE 类型不附带列的列表,则该函数为内嵌表值函数,如果 RETURNS 子句指定的 TABLE 类型带有列及其数据类型,在函数体内可以使用多条T-SQL 语句,则该函数为多语句表值函数。在调用用户定义函数时,需要提供数据库名和函数名,或者在函数名之前添加一个前缀 dbo ,即为 数据库名.dbo.函数名 或 dbo.函数名 。另外,用户定义函数的函数主体中,也都必须有RETURN语句,返回语句一般是最后一条语句。由于篇幅限制,这里不写demo了。