1. 达梦SQL函数基础与应用场景
达梦数据库的SQL函数体系相当完善,涵盖了数据处理所需的各类操作。在实际项目中,我经常用这些函数来解决数据清洗、转换和计算问题。举个例子,在员工管理系统中,我们经常需要处理这样的场景:计算员工工龄、格式化姓名、统计部门薪资等。
字符串处理是最常见的需求之一。比如员工姓名可能存在前后空格或不规范的大小写,这时候就可以用**TRIM()和INITCAP()**函数组合处理:
-- 去除空格并规范姓名格式 SELECT INITCAP(TRIM(ename)) AS formatted_name FROM employee;日期函数在考勤统计中特别实用。计算员工工龄可以用MONTHS_BETWEEN()配合FLOOR():
-- 计算精确到月份的工龄 SELECT ename, FLOOR(MONTHS_BETWEEN(SYSDATE, hire_date)/12) AS work_years FROM employee;数值函数在薪资计算时大显身手。比如计算部门平均薪资并保留两位小数:
-- 部门薪资统计 SELECT dept_no, ROUND(AVG(salary), 2) AS avg_salary, SUM(salary) AS total_salary FROM employee GROUP BY dept_no;提示:达梦的ROUND函数与Oracle行为一致,但和MySQL有细微差异,第二个参数为负数时表示对整数部分进行四舍五入。
2. 内置函数深度解析与实战技巧
2.1 字符串函数高级用法
字符串处理是数据库操作的基石。达梦的REGEXP系列函数支持POSIX标准正则表达式,比基础的通配符更强大。最近在处理用户地址数据时,我就用到了这个功能:
-- 提取地址中的邮编(6位数字) SELECT address, REGEXP_SUBSTR(address, '[0-9]{6}') AS postal_code FROM customer WHERE REGEXP_LIKE(address, '[0-9]{6}');LISTAGG函数是行列转换的利器。在生成部门员工名单时:
-- 将部门员工姓名合并为逗号分隔的字符串 SELECT dept_no, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS members FROM employee GROUP BY dept_no;2.2 日期函数实战案例
达梦的日期函数非常丰富,特别是LAST_DAY和NEXT_DAY这类特殊日期计算函数。在做月度报表时,我经常这样获取当月最后一天:
-- 获取当月最后一天23:59:59 SELECT LAST_DAY(SYSDATE) + INTERVAL '23:59:59' HOUR TO SECOND FROM dual;时间间隔计算也有多种方案。计算两个日期之间的工作日(排除周末):
-- 计算工作日函数 CREATE OR REPLACE FUNCTION work_days(start_date DATE, end_date DATE) RETURN NUMBER AS days NUMBER := 0; curr DATE := start_date; BEGIN WHILE curr <= end_date LOOP IF TO_CHAR(curr, 'DY') NOT IN ('SAT', 'SUN') THEN days := days + 1; END IF; curr := curr + 1; END LOOP; RETURN days; END;3. 自定义函数开发全流程
3.1 函数创建语法详解
创建自定义函数的完整语法结构如下:
CREATE [OR REPLACE] FUNCTION 函数名 ( [参数1 [IN|OUT|IN OUT] 数据类型 [DEFAULT 默认值], 参数2 [IN|OUT|IN OUT] 数据类型...] ) RETURN 返回值类型 [AUTHID DEFINER|CURRENT_USER] [DETERMINISTIC] [PARALLEL_ENABLE] [PIPELINED] IS|AS [声明部分:变量、常量、游标等] BEGIN 执行体 [EXCEPTION 异常处理] END;我开发过一个薪资计算函数,包含绩效系数和考勤扣减:
CREATE OR REPLACE FUNCTION calc_salary( base_salary NUMBER, performance_ratio NUMBER DEFAULT 1.0, absent_days NUMBER DEFAULT 0 ) RETURN NUMBER AS daily_salary NUMBER; deduction NUMBER; BEGIN daily_salary := base_salary / 21.75; -- 月计薪天数 deduction := daily_salary * absent_days; RETURN ROUND(base_salary * performance_ratio - deduction, 2); END;3.2 复杂业务逻辑实现
对于需要访问数据库的自定义函数,游标是常用工具。比如这个部门薪资分析函数:
CREATE OR REPLACE FUNCTION dept_salary_report( p_dept_no VARCHAR2 ) RETURN CLOB AS v_result CLOB := EMPTY_CLOB(); CURSOR c_emp IS SELECT ename, salary FROM employee WHERE dept_no = p_dept_no ORDER BY salary DESC; BEGIN DBMS_LOB.CREATETEMPORARY(v_result, TRUE); FOR r IN c_emp LOOP v_result := v_result || r.ename || ': ' || r.salary || CHR(10); END LOOP; RETURN v_result; END;注意:使用CLOB大对象时一定要记得释放资源,这里用DBMS_LOB.CREATETEMPORARY创建的临时LOB会在会话结束时自动释放。
4. 函数性能优化与调试
4.1 执行计划分析技巧
函数性能问题经常出现在SQL调优中。达梦的EXPLAIN命令可以分析函数调用情况:
-- 查看包含函数调用的SQL执行计划 EXPLAIN SELECT ename, calc_salary(salary, performance) FROM employee WHERE dept_no = 'D001';常见优化手段包括:
- 避免在WHERE条件中使用函数
- 对函数内访问的表建立合适索引
- 使用DETERMINISTIC标记纯函数
4.2 调试与错误处理
完善的异常处理能让函数更健壮。我通常这样记录错误信息:
CREATE OR REPLACE FUNCTION safe_divide( a NUMBER, b NUMBER ) RETURN NUMBER AS BEGIN IF b = 0 THEN INSERT INTO error_log VALUES( 'DIVIDE_BY_ZERO', SYSDATE, 'safe_divide: divisor is zero' ); RETURN NULL; END IF; RETURN a / b; EXCEPTION WHEN OTHERS THEN INSERT INTO error_log VALUES( SQLCODE, SYSDATE, 'safe_divide: ' || SQLERRM ); RETURN NULL; END;5. 跨数据库函数迁移指南
5.1 与Oracle函数差异
达梦与Oracle函数兼容性较高,但仍有需要注意的区别:
| 功能需求 | Oracle实现 | 达梦实现 |
|---|---|---|
| 字符串连接 | CONCAT或` | |
| 空值处理 | NVL | NVL或ISNULL |
| 正则表达式 | REGEXP_系列 | REGEXP_系列 |
| 分页查询 | ROWNUM | ROWNUM或LIMIT/OFFSET |
| 序列生成 | SEQUENCE.NEXTVAL | 相同 |
5.2 与MySQL函数转换
MySQL迁移到达梦时,这些函数需要特别注意:
-- MySQL写法 SELECT IFNULL(salary, 0), DATE_FORMAT(hire_date, '%Y-%m'), GROUP_CONCAT(ename) FROM employee; -- 达梦等价写法 SELECT NVL(salary, 0), TO_CHAR(hire_date, 'yyyy-mm'), LISTAGG(ename, ',') FROM employee;时间计算函数的转换示例:
-- MySQL日期加减 SELECT DATE_ADD(hire_date, INTERVAL 1 MONTH); -- 达梦等价实现 SELECT ADD_MONTHS(hire_date, 1);在实际迁移过程中,我发现达梦的WM_CONCAT与MySQL的GROUP_CONCAT在排序行为上略有不同,需要额外测试验证结果是否符合预期。