# 存储函数

在本教程中，您将学习如何使用`CREATE FUNCTION`语句创建存储的函数。

存储的函数是返回单个值的特殊类型的存储程序。您使用存储的函数来封装在SQL语句或存储的程序中可重用的常用公式或业务规则。

与[存储过程](http://www.yiibai.com/mysql/stored-procedure.html)不同，您可以在SQL语句中使用存储的函数，也可以在表达式中使用。 这有助于提高程序代码的可读性和可维护性。

## MySQL存储函数语法

以下说明了创建新存储函数的最简单语法：

```sql
CREATE FUNCTION function_name(param1,param2,…)
    RETURNS datatype
   [NOT] DETERMINISTIC
 statements
```

_首先_，在`CREATE FUNCTION`子句之后指定存储函数的名称。 _其次_，列出括号内存储函数的所有[参数](http://www.yiibai.com/mysql/stored-procedures-parameters.html)。 默认情况下，所有参数均为`IN`参数。不能为参数指定`IN`，`OUT`或`INOUT`修饰符。 _第三_，必须在`RETURNS`语句中指定返回值的数据类型。它可以是任何有效的[MySQL数据类型](http://www.yiibai.com/mysql/data-types.html)。 _第四_，对于相同的输入参数，如果存储的函数返回相同的结果，这样则被认为是确定性的，否则存储的函数不是确定性的。必须决定一个存储函数是否是确定性的。 如果您声明不正确，则存储的函数可能会产生意想不到的结果，或者不使用可用的优化，从而降低性能。 _第五_，将代码写入存储函数的主体中。 它可以是单个语句或复合语句。 在主体部分中，必须至少指定一个`RETURN`语句。`RETURN`语句用于返回一个值给调用者。 每当到达`RETURN`语句时，存储的函数的执行将立即终止。

## MySQL存储函数示例

我们来看一下使用存储函数的例子，这里将使用[示例数据库(yiibaidb)](http://www.yiibai.com/mysql/sample-database.html)中的`customers`表进行演示。

以下示例是根据信用额度返回客户级别的功能。 我们使用[IF语句](http://www.yiibai.com/mysql/if-statement.html)来确定信用额度。

```sql
DELIMITER $$

CREATE FUNCTION CustomerLevel(p_creditLimit double) RETURNS VARCHAR(10)
    DETERMINISTIC
BEGIN
    DECLARE lvl varchar(10);

    IF p_creditLimit > 50000 THEN
 SET lvl = 'PLATINUM';
    ELSEIF (p_creditLimit <= 50000 AND p_creditLimit >= 10000) THEN
        SET lvl = 'GOLD';
    ELSEIF p_creditLimit < 10000 THEN
        SET lvl = 'SILVER';
    END IF;

 RETURN (lvl);
END $$
DELIMITER ;
```

现在，我们在[SELECT语句](http://www.yiibai.com/mysql/select-statement-query-data.html)中调用`CustomerLevel()`存储函数，如下所示：

```sql
SELECT 
    customerName, CustomerLevel(creditLimit)
FROM
    customers
ORDER BY customerName;
```

执行上面查询语句，得到以下结果 -

```sql
+------------------------------------+----------------------------+
| customerName                       | CustomerLevel(creditLimit) |
+------------------------------------+----------------------------+
| Alpha Cognac                       | PLATINUM                   |
| American Souvenirs Inc             | SILVER                     |
| Amica Models & Co.                 | PLATINUM                   |
| ANG Resellers                      | SILVER                     |
| Anna's Decorations, Ltd            | PLATINUM                   |
| Anton Designs, Ltd.                | SILVER                     |
| Asian Shopping Network, Co         | SILVER                     |
| Asian Treasures, Inc.              | SILVER                     |
| Atelier graphique                  | GOLD                       |
| Australian Collectables, Ltd       | PLATINUM                   |
| Australian Collectors, Co.         | PLATINUM                   |
|************** 此处省略了一大波数据 *********************************|
| Vitachrome Inc.                    | PLATINUM                   |
| Volvo Model Replicas, Co           | PLATINUM                   |
| Warburg Exchange                   | SILVER                     |
| West Coast Collectables Co.        | PLATINUM                   |
+------------------------------------+----------------------------+
122 rows in set
```

下面，来重写在[MySQL IF语句教程](http://www.yiibai.com/mysql/if-statement.html)中开发的`GetCustomerLevel()`存储过程，如下所示：

```sql
DELIMITER $$

CREATE PROCEDURE GetCustomerLevel(
    IN  p_customerNumber INT(11),
    OUT p_customerLevel  varchar(10)
)
BEGIN
    DECLARE creditlim DOUBLE;

    SELECT creditlimit INTO creditlim
    FROM customers
    WHERE customerNumber = p_customerNumber;

    SELECT CUSTOMERLEVEL(creditlim) 
    INTO p_customerLevel;
END $$
DELIMITER ;
```

如您所见，`GetCustomerLevel()`存储过程在使用`CustomerLevel()`存储函数时可读性更高。

请注意，存储函数仅返回单个值。 如果没有包含`INTO`子句的`SELECT`语句，则将会收到错误。

另外，如果存储的函数包含SQL语句，则不应在其他SQL语句中使用它; 否则，存储的函数将减慢查询的速度。
