SQL 函数从入门到精通:原理、类型、窗口函数与实战指南

SQL 函数从入门到精通:原理、类型、窗口函数与实战指南

面向初学者的由浅入深教程:带你理解 SQL 函数的本质、分类(标量/聚合/窗口)、内置与自定义函数的用法差异,配合 MySQL 与 PostgreSQL 的完整示例与避坑建议。


目录

一、什么是 SQL 函数?

  • 概念:SQL 函数是在查询或写入过程中被调用的可复用计算单元,输入参数,返回计算结果。
  • 价值:解耦复杂计算、提升可读性、复用公共逻辑、在数据库端高效执行(减少应用端搬运与二次计算)。
  • 与存储过程的区别:函数偏"返回值计算",适合在 SELECTWHEREORDER BYCHECK 等处嵌入;过程偏"执行动作",更多用于批量更新、事务流程。

二、函数类型与使用场景

  • 标量函数(Scalar):输入若干值,返回单个标量值。适合清洗、格式化、业务规则判断。
  • 聚合函数(Aggregate):对多行汇总,返回单值,如 SUM/AVG/COUNT/MAX/MIN,也可自定义复杂聚合。
  • 窗口函数(Window):在不折叠行的前提下,基于窗口分区进行排名、累计、移动统计,如 ROW_NUMBER()LAG/LEADSUM() OVER(...)

常见场景:

  • 统一数据清洗(手机号脱敏、文本规范化)。
  • 指标计算(转化率、留存、同比环比)。
  • 排名、TopN、滑动窗口统计。

三、内置函数速览(以 MySQL/PG 为例)

  • 字符:LOWER/UPPER/TRIM/REPLACE/SUBSTRING/REGEXP_REPLACE(PG)
  • 数值:ROUND/CEIL/FLOOR/RANDOM(PG random(),MySQL RAND()
  • 日期: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 sqlLANGUAGE plpythonu 等多语言,常用 plpgsqlsql


五、聚合函数与自定义聚合(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;PG IMMUTABLE/STABLE/VOLATILE
  • 字符函数命名差异:SUBSTRING 参数写法不同、正则函数差异。
  • JSON 操作函数不同:MySQL JSON_EXTRACT/->>,PG jsonb 运算符与函数族。
  • 窗口函数两者均支持,但语法细节与函数名存在差异。

如果你需要,我可以为这篇文章生成配套的 SQL 脚本与示例数据集,便于本地一键上手演示。


彩蛋:梗版总结(轻松读法)

  • 把函数想成"即食小零食",随手一丢就有味(随处可用,返回一个值);
  • 聚合函数是"榨汁机",把一堆水果打成一杯果汁(多行变一行);
  • 窗口函数是"吃着不减的自助餐",既吃到单份菜又能看全场排名(不折叠行还排名统计);
  • 非 SARGable 就像"给跑鞋绑铅块",索引跑不动;
  • IMMUTABLE/DETERMINISTIC 像给函数贴"保质期",输入一样味道不变,厨子(优化器)才敢囤货(缓存计划)。
相关推荐
Kt&Rs6 小时前
MySQL复制技术的发展历程
数据库·mysql
小小菜鸡ing6 小时前
pymysql
java·服务器·数据库
手握风云-7 小时前
MySQL数据库精研之旅第十六期:深度拆解事务核心(上)
数据库·mysql
boonya7 小时前
Redis核心原理与面试问题解析
数据库·redis·面试
沙二原住民8 小时前
提升数据库性能的秘密武器:深入解析慢查询、连接池与Druid监控
java·数据库·oracle
三毛20048 小时前
玳瑁的嵌入式日记D33-0908(SQL数据库)
jvm·数据库·sql
叫我龙翔8 小时前
【MySQL】从零开始了解数据库开发 --- 库的操作
数据库·mysql·数据库开发
没有bug.的程序员8 小时前
Redis Stream:轻量级消息队列深度解析
java·数据库·chrome·redis·消息队列
GottdesKrieges9 小时前
OceanBase容量统计:租户、数据库、表大小
数据库·oceanbase