参数说明
| 参数 | 说明 |
|---|---|
| RETURNS | 指定函数可以返回的数据类型 |
| RETURN | RETURN 语法后的实际变量或值,将返回到函数被调用的地方 |
第 30.1 节:带 IN、OUT、INOUT 参数的存储过程
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_nested_loop$$
CREATE PROCEDURE sp_nested_loop(IN i INT, IN j INT, OUT x INT, OUT y INT, INOUT z INT)
BEGIN
DECLARE a INTEGER DEFAULT 0;
DECLARE b INTEGER DEFAULT 0;
DECLARE c INTEGER DEFAULT 0;
WHILE a < i DO
WHILE b < j DO
SET c = c + 1;
SET b = b + 1;
END WHILE;
SET a = a + 1;
SET b = 0;
END WHILE;
SET x = a, y = c;
SET z = x + y + z;
END$$
DELIMITER ;
调用存储过程:
SET @z = 30;
CALL sp_nested_loop(10, 20, @x, @y, @z);
SELECT @x, @y, @z;
结果:
[图片占位符]
IN参数将一个值传递给存储过程。该过程可能会修改该值,但修改在过程返回时对调用者不可见。OUT参数将值从过程传回调用者。在过程中,其初始值为NULL,当过程返回时,其值对调用者可见。INOUT参数由调用者初始化,可以被过程修改,并且过程所做的任何更改在过程返回时对调用者可见。
参考:http://dev.mysql.com/doc/refman/5.7/en/create-procedure.html
第 30.2 节:创建函数
以下简单的函数示例只返回常量整数值 12。
DELIMITER ||
CREATE FUNCTION functionname() RETURNS INT
BEGIN
RETURN 12;
END;
||
DELIMITER ;
第一行定义了分隔符要改为 ||,在创建函数之前需要设置它,否则如果保留默认的 ;,函数体中遇到的第一个 ; 将被视为 CREATE 语句的结束,这通常不是期望的结果。
在 CREATE FUNCTION 运行后,应该将分隔符设置回默认的 ;,如上面函数代码后所示(DELIMITER ;)。
执行此函数:
SELECT functionname();
结果:
+----------------+
| functionname() |
+----------------+
| 12 |
+----------------+
一个稍微复杂(但仍然简单)的例子接受一个参数并向其添加一个常量:
DELIMITER $$
CREATE FUNCTION add_2 (my_arg INT ) RETURNS INT
BEGIN
RETURN (my_arg + 2);
END;
$$
DELIMITER ;
SELECT add_2(12);
结果:
+-----------+
| add_2(12) |
+-----------+
| 14 |
+-----------+
注意使用了不同的 DELIMITER 参数。实际上,你可以使用任何在 CREATE 语句体中没有出现的字符序列,但通常的做法是使用双倍的非字母数字字符,如 \\、|| 或 $$。
在创建或更新函数、存储过程或触发器之前和之后更改分隔符是一种好习惯,因为某些 GUI 不需要更改分隔符,而通过命令行运行查询总是需要设置分隔符。
第 30.3 节:游标
游标使你能够逐行遍历查询结果。DECLARE 命令用于初始化游标并将其与特定的 SQL 查询关联:
DECLARE student CURSOR FOR SELECT name FROM student;
假设我们销售若干类型的产品。我们想要计算每种类型的产品有多少个。
我们的数据:
CREATE TABLE product (
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
type VARCHAR(50) NOT NULL,
name VARCHAR(255) NOT NULL
);
CREATE TABLE product_type (
name VARCHAR(50) NOT NULL PRIMARY KEY
);
CREATE TABLE product_type_count (
type VARCHAR(50) NOT NULL PRIMARY KEY,
count INT(10) UNSIGNED NOT NULL DEFAULT 0
);
INSERT INTO product_type (name) VALUES ('dress'), ('food');
INSERT INTO product (type, name) VALUES
('dress', 'T-shirt'),
('dress', 'Trousers'),
('food', 'Apple'),
('food', 'Tomatoes'),
('food', 'Meat');
我们可以通过使用游标的存储过程来实现目标:
DELIMITER //
DROP PROCEDURE IF EXISTS product_count;
CREATE PROCEDURE product_count()
BEGIN
DECLARE p_type VARCHAR(255);
DECLARE p_count INT(10) UNSIGNED;
DECLARE done INT DEFAULT 0;
DECLARE product CURSOR FOR SELECT type, COUNT(*) FROM product GROUP BY type;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
TRUNCATE product_type_count;
OPEN product;
REPEAT
FETCH product INTO p_type, p_count;
IF NOT done THEN
INSERT INTO product_type_count SET type = p_type, count = p_count;
END IF;
UNTIL done END REPEAT;
CLOSE product;
END //
DELIMITER ;
然后你可以调用该过程:
CALL product_count();
结果将显示在 product_type_count 表中:
| type | count |
|---|---|
| dress | 2 |
| food | 3 |
虽然这是一个很好的游标示例,但请注意,整个过程体可以简单地替换为:
INSERT INTO product_type_count (type, count)
SELECT type, COUNT(*) FROM product GROUP BY TYPE;
这将运行得快得多。
第 30.4 节:多个结果集
与 SELECT 语句不同,存储过程返回多个结果集。这需要在 Perl、PHP 等语言中使用不同的代码来收集 CALL 的结果。
(此处需要具体代码示例,可参考其他文档。)
第 30.5 节:创建函数(再述)
DELIMITER $$
CREATE DEFINER = 'db_username'@'hostname_or_IP'
FUNCTION `function_name` (optional_param data_type(length_if_applicable))
RETURNS data_type
BEGIN
/* SQL 语句写在这里 */
END$$
RETURNS data_type 可以是任何 MySQL 数据类型。