在数据库开发中,经常会遇到一些重复性的计算逻辑,比如根据身份证号提取出生日期、根据订单金额计算阶梯折扣、把多个字段拼接成特定格式等。如果每次都在SQL里写一大段嵌套表达式,不仅容易出错,后期维护也很麻烦。DB2提供了自定义函数(User Defined Function)的能力,可以把这些逻辑封装成一个函数,在SQL语句中像调用内置函数一样直接使用,代码可读性和复用性都会大幅提升。本文将从函数类型、创建语法、实际示例到部署调试,完整讲解DB2自定义函数的开发过程。

一、DB2自定义函数的两种主要类型
DB2的函数按照返回值形式可以分为标量函数(Scalar Function)和表函数(Table Function),两者的使用场景完全不同,开发前必须先想清楚自己需要哪一种。
标量函数每次调用返回一个单一的值,比如返回一个数字、一个字符串或一个日期。它最适合用在SELECT列表、WHERE条件、GROUP BY等需要单值的地方。例如写一个函数根据客户等级和消费金额计算折扣率,返回一个DECIMAL值,就可以直接写在SELECT语句里。
表函数则返回一张完整的结果集,可以出现在FROM子句中,像查询普通表一样查询函数的返回结果。典型的应用场景是把非关系型的数据源(比如解析一段JSON或XML)转换成行列表输出,或者把复杂的业务查询逻辑封装起来供多处调用。
除了按返回值分类,还可以按实现方式分类:SQL函数用纯SQL或SQL PL编写,部署简单;外部函数则调用C、Java等语言编写的程序,性能好但开发和部署成本高。日常业务开发中,SQL PL函数是最常用的选择,本文也以此为主。
二、CREATE FUNCTION核心语法详解
创建标量函数的基本语法如下,先看一个完整的框架:
CREATE FUNCTION get_discount(p_level VARCHAR(10), p_amount DECIMAL(12,2))
RETURNS DECIMAL(5,2)
LANGUAGE SQL
BEGIN
DECLARE v_rate DECIMAL(5,2);
IF p_level = 'VIP' THEN
IF p_amount >= 10000 THEN
SET v_rate = 0.70;
ELSE
SET v_rate = 0.85;
END IF;
ELSE
SET v_rate = 1.00;
END IF;
RETURN v_rate;
END
@
这条语句有几个关键部分需要注意。首先是函数名和参数列表,参数必须明确指定类型,DB2是强类型数据库,类型不匹配会直接报错。RETURNS子句声明返回值的类型,标量函数只能返回一个值。
LANGUAGE SQL表明函数体用SQL PL编写。结尾的@符号是语句终止符,因为函数体内包含分号,如果用默认的分号作为终止符,DB2会在遇到第一个分号时就认为语句结束了,从而报语法错误。所以在命令行执行前,通常先用db2 -td@指定@作为终止符,或者在数据工具中设置对应的分隔符。
如果函数体逻辑较复杂,建议加上SPECIFIC关键字给函数指定一个特定名称,方便后续DROP时引用。还可以用PARAMETER STYLE SQL声明参数风格,用DETERMINISTIC标记函数是确定性的(同样的输入一定产生同样的输出),加上这个标记有助于优化器生成更好的执行计划。
表函数的语法略有不同,RETURNS后面要跟一张表的定义,并且需要声明RETURN语句返回一个游标:
CREATE FUNCTION get_order_summary(p_cust_id INTEGER)
RETURNS TABLE (order_month VARCHAR(7), order_count INTEGER, total_amt DECIMAL(14,2))
LANGUAGE SQL
BEGIN
RETURN SELECT SUBSTR(CHAR(order_date),1,7) AS order_month,
COUNT(*) AS order_count,
SUM(amount) AS total_amt
FROM orders
WHERE cust_id = p_cust_id
GROUP BY SUBSTR(CHAR(order_date),1,7);
END
@
调用表函数时把它放在FROM子句里,并给一个表别名,例如SELECT * FROM TABLE(get_order_summary(1001)) AS t。这种写法比直接写视图更灵活,因为可以传参数。
三、开发中的实用技巧与常见坑
第一个常见的坑是错误处理。函数体内如果发生异常,默认会直接抛出错误中断执行。对于一些可预见的异常,比如除零错误,可以声明条件处理器来兜底:
CREATE FUNCTION safe_div(p_a DECIMAL(12,2), p_b DECIMAL(12,2))
RETURNS DECIMAL(18,6)
LANGUAGE SQL
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
RETURN NULL;
END;
RETURN p_a / NULLIF(p_b, 0);
END
@
第二个要注意的点是函数内不允许包含某些操作。SQL PL函数默认不能执行INSERT、UPDATE、DELETE这类修改数据的语句(修改函数自身所属数据库中数据的操作被禁止),如果确实需要读写外部数据,要考虑改用存储过程或者使用MODIFIES SQL DATA属性的外部函数。设计函数时应保持其纯粹性,只做计算和查询,这样也更容易被优化器处理。
第三个技巧是善用DECLARE声明的局部变量和中间结果。复杂的逻辑拆成多个步骤,每一步赋值给一个变量,比写一大段嵌套表达式清晰得多。变量声明必须放在函数体的最前面,在SQL PL中声明的位置是有严格顺序要求的:DECLARE语句必须出现在其他语句之前,先声明变量,再声明游标,最后声明条件处理器。
调试方面,DB2的函数不像存储过程那样可以单独CALL执行,标量函数必须借助一个SQL来测试,最简单的方式是VALUES get_discount('VIP', 20000)或者SELECT get_discount('VIP', 20000) FROM SYSIBM.SYSDUMMY1。如果怀疑函数执行出错,可以查看诊断日志,或者把中间变量的值RETURN出来逐步排查。
最后是权限管理。函数创建后默认只有创建者能使用,如果要给其他用户授权,执行GRANT EXECUTE ON FUNCTION get_discount(VARCHAR(10), DECIMAL(12,2)) TO USER appuser即可,注意授权时必须写上完整的参数类型签名,因为DB2允许同名函数重载,不同参数类型会被视为不同的函数对象。修改函数只能先DROP再重建,重建前如果有其他对象依赖它,需要先处理依赖关系,这也是函数版本管理时需要提前规划的地方。
四、一个完整的业务示例
下面通过一个字符串处理函数把前面的知识点串起来。假设系统里存的手机号格式混乱,有的带区号前缀,有的中间有横线,需要一个函数统一提取纯数字的11位手机号:
CREATE FUNCTION normalize_phone(p_input VARCHAR(50))
RETURNS VARCHAR(11)
LANGUAGE SQL
DETERMINISTIC
BEGIN
DECLARE v_result VARCHAR(50);
DECLARE i INTEGER DEFAULT 1;
DECLARE ch CHAR(1);
SET v_result = '';
WHILE i <= LENGTH(p_input) DO
SET ch = SUBSTR(p_input, i, 1);
IF ch BETWEEN '0' AND '9' THEN
SET v_result = v_result || ch;
END IF;
SET i = i + 1;
END WHILE;
IF LENGTH(v_result) > 11 THEN
SET v_result = RIGHT(v_result, 11);
END IF;
RETURN v_result;
END
@
这个函数体现了标量函数的典型写法:逐字符遍历输入串,只保留数字字符,最后截取末尾11位。DETERMINISTIC标记表明同样的输入必然得到同样的输出,这个属性在函数被大量调用的场景下能帮助优化器缓存或简化计算。
使用时直接嵌入SQL即可,比如SELECT normalize_phone(contact_tel) FROM customers WHERE contact_tel IS NOT NULL,比写一堆REPLACE和TRANSLATE嵌套可读性要好得多。整个清洗逻辑收敛在函数内部,将来规则变化,比如手机号升级成12位,只需要改函数一处,所有调用它的SQL自动生效,这就是函数封装带来的维护优势。
总结一下,DB2自定义函数的开发核心在于三点:选对函数类型、写规范CREATE FUNCTION语句、处理好错误和权限。把常用业务逻辑沉淀成函数库,长期来看对团队的开发效率和代码质量都有明显帮助。
DB2自定义函数DB2 FUNCTIONSQL PL修改时间:2026-09-07 17:54:44