SQL 函数从入门到精通:原理、类型、窗口函数与实战指南
面向初学者的由浅入深教程:带你理解 SQL 函数的本质、分类(标量/聚合/窗口)、内置与自定义函数的用法差异,配合 MySQL 与 PostgreSQL 的完整示例与避坑建议。
目录
- [SQL 函数从入门到精通:原理、类型、窗口函数与实战指南](#SQL 函数从入门到精通:原理、类型、窗口函数与实战指南)
-
- [一、什么是 SQL 函数?](#一、什么是 SQL 函数?)
- 二、函数类型与使用场景
- [三、内置函数速览(以 MySQL/PG 为例)](#三、内置函数速览(以 MySQL/PG 为例))
- [四、自定义函数:MySQL 与 PostgreSQL 对比](#四、自定义函数:MySQL 与 PostgreSQL 对比)
-
- [1)MySQL 自定义函数(Stored Function)](#1)MySQL 自定义函数(Stored Function))
- [2)PostgreSQL 自定义函数(plpgsql)](#2)PostgreSQL 自定义函数(plpgsql))
- 五、聚合函数与自定义聚合(PG)
- 六、窗口函数实战
- 七、性能与可维护性
- 八、安全与规范
- 九、常见坑与避坑
- 十、实践清单(建议)
- [附:MySQL/PG 对照要点](#附:MySQL/PG 对照要点)
- 彩蛋:梗版总结(轻松读法)
一、什么是 SQL 函数?
- 概念:SQL 函数是在查询或写入过程中被调用的可复用计算单元,输入参数,返回计算结果。
- 价值:解耦复杂计算、提升可读性、复用公共逻辑、在数据库端高效执行(减少应用端搬运与二次计算)。
- 与存储过程的区别:函数偏"返回值计算",适合在
SELECT
、WHERE
、ORDER BY
、CHECK
等处嵌入;过程偏"执行动作",更多用于批量更新、事务流程。
二、函数类型与使用场景
- 标量函数(Scalar):输入若干值,返回单个标量值。适合清洗、格式化、业务规则判断。
- 聚合函数(Aggregate):对多行汇总,返回单值,如
SUM/AVG/COUNT/MAX/MIN
,也可自定义复杂聚合。 - 窗口函数(Window):在不折叠行的前提下,基于窗口分区进行排名、累计、移动统计,如
ROW_NUMBER()
、LAG/LEAD
、SUM() OVER(...)
。
常见场景:
- 统一数据清洗(手机号脱敏、文本规范化)。
- 指标计算(转化率、留存、同比环比)。
- 排名、TopN、滑动窗口统计。
三、内置函数速览(以 MySQL/PG 为例)
- 字符:
LOWER/UPPER/TRIM/REPLACE/SUBSTRING/REGEXP_REPLACE
(PG) - 数值:
ROUND/CEIL/FLOOR/RANDOM
(PGrandom()
,MySQLRAND()
) - 日期:
NOW()/CURRENT_DATE/DATE_TRUNC()
(PG)/DATE_FORMAT()
(MySQL) - 条件:
COALESCE/NULLIF/CASE WHEN
- JSON:
JSON_EXTRACT()
(MySQL)/jsonb_extract_path_text()
(PG)
四、自定义函数:MySQL 与 PostgreSQL 对比
1)MySQL 自定义函数(Stored Function)
sql
DELIMITER //
CREATE FUNCTION fn_mask_phone(p_phone VARCHAR(20))
RETURNS VARCHAR(20)
DETERMINISTIC
BEGIN
IF p_phone IS NULL OR LENGTH(p_phone) < 7 THEN
RETURN p_phone;
END IF;
RETURN CONCAT(LEFT(p_phone, 3), '****', RIGHT(p_phone, 4));
END //
DELIMITER ;
-- 使用
SELECT fn_mask_phone('13812345678');
要点:
RETURNS
指定返回类型;DETERMINISTIC
表示相同输入恒定输出,利于优化。- 存储函数应避免修改数据(以免副作用),适合作为计算函数在查询中使用。
2)PostgreSQL 自定义函数(plpgsql)
sql
CREATE OR REPLACE FUNCTION fn_mask_phone(p_phone TEXT)
RETURNS TEXT
LANGUAGE plpgsql AS $$
BEGIN
IF p_phone IS NULL OR length(p_phone) < 7 THEN
RETURN p_phone;
END IF;
RETURN left(p_phone, 3) || '****' || right(p_phone, 4);
END;
$$;
-- 使用
SELECT fn_mask_phone('13812345678');
PG 还支持 LANGUAGE sql
、LANGUAGE plpythonu
等多语言,常用 plpgsql
与 sql
。
五、聚合函数与自定义聚合(PG)
内置聚合:SUM/AVG/COUNT/MAX/MIN/STRING_AGG
(PG)/GROUP_CONCAT
(MySQL)。
在 PostgreSQL 中可以自定义聚合(MySQL 8.0 也引入了自定义聚合的扩展方式但较少使用):
sql
-- 将文本拼接为逗号分隔
SELECT STRING_AGG(name, ',') FROM users; -- PG
SELECT GROUP_CONCAT(name) FROM users; -- MySQL
PG 自定义聚合通常需要定义状态转换函数与最终函数,初学可先掌握内置聚合与窗口函数。
六、窗口函数实战
窗口函数不折叠行,适合排名、累计与环比。
sql
-- 1) 分区排名
SELECT user_id,
amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rn
FROM orders;
-- 2) 累计/移动平均
SELECT stat_date,
amount,
SUM(amount) OVER (ORDER BY stat_date) AS cum_amount,
AVG(amount) OVER (ORDER BY stat_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_7
FROM daily_revenue;
-- 3) 同比/环比(LAG/LEAD)
SELECT stat_date,
amount,
LAG(amount, 1) OVER (ORDER BY stat_date) AS prev,
amount - LAG(amount, 1) OVER (ORDER BY stat_date) AS diff
FROM daily_revenue;
MySQL 与 PG 均支持窗口函数(MySQL 8.0+)。
七、性能与可维护性
- 避免在可索引列上包裹函数,防止失去索引(非 SARGable)。
- 将高开销函数计算前置到
WITH
子句或临时表,复用结果。 - 小心非确定性函数(如随机、当前时间)对缓存与可重复性的影响。
- 对自定义函数标注
IMMUTABLE/STABLE/VOLATILE
(PG)或DETERMINISTIC
(MySQL)以利优化器。
八、安全与规范
- 函数应无副作用,避免在函数内执行 DML 以免难以推导。
- 最小权限:仅授予调用所需的
EXECUTE
;敏感表走视图或安全函数封装。 - 严格参数化与输入校验,杜绝动态 SQL 注入风险(PG 可用
format()
搭配quote_ident/quote_literal
)。
九、常见坑与避坑
- 在
WHERE
对列使用函数导致全表扫描。 - 忘记标注函数稳定性,导致计划无法重用或错误优化。
- 自定义函数里抛异常未捕获,影响批量任务;必要时在上层流程处理。
- 混淆窗口与聚合:窗口不折叠行,聚合折叠行。
十、实践清单(建议)
- 写一个手机号脱敏函数,并在用户列表中批量展示。
- 用窗口函数计算过去 7 天的移动平均 GMV 与日环比。
- 将复杂 CASE 逻辑提取为自定义函数,统一在多查询中使用。
附:MySQL/PG 对照要点
- 非确定性与稳定性标注:MySQL
DETERMINISTIC
;PGIMMUTABLE/STABLE/VOLATILE
。 - 字符函数命名差异:
SUBSTRING
参数写法不同、正则函数差异。 - JSON 操作函数不同:MySQL
JSON_EXTRACT
/->>
,PGjsonb
运算符与函数族。 - 窗口函数两者均支持,但语法细节与函数名存在差异。
如果你需要,我可以为这篇文章生成配套的 SQL 脚本与示例数据集,便于本地一键上手演示。
彩蛋:梗版总结(轻松读法)
- 把函数想成"即食小零食",随手一丢就有味(随处可用,返回一个值);
- 聚合函数是"榨汁机",把一堆水果打成一杯果汁(多行变一行);
- 窗口函数是"吃着不减的自助餐",既吃到单份菜又能看全场排名(不折叠行还排名统计);
- 非 SARGable 就像"给跑鞋绑铅块",索引跑不动;
IMMUTABLE/DETERMINISTIC
像给函数贴"保质期",输入一样味道不变,厨子(优化器)才敢囤货(缓存计划)。