函数
调用函数时,数据库不仅要执行函数体,还要先确定“调用的是哪个函数”。同名函数可能有不同参数类型或不同返回类型,字符串字面量和 NULL 又可能没有明确类型,因此函数调用是类型转换最常见的发生点之一。
本文以通用 SQL 为主,并在需要时标注 PostgreSQL 风格写法。函数名称、重载数量、可用的多态类型和失败处理函数都可能因数据库而异。
函数调用的基本结构
SELECT ROUND(total_amount, 2) AS rounded_amount,
LOWER(customer_name) AS normalized_name
FROM orders;
数据库通常按以下步骤解析:
- 根据函数名和参数个数找候选函数。
- 确定每个参数当前的类型。
- 优先选择参数类型精确匹配的函数。
- 必要时尝试允许的隐式转换或多态匹配。
- 确定函数返回类型,并把它交给外层表达式继续解析。
如果仍有多个候选函数同样匹配,调用会因为歧义失败;如果没有候选函数,通常会看到“函数不存在”或“参数类型不匹配”的错误。
字面量决定不了函数时
列通常有明确类型,字面量却可能需要上下文:
SELECT LENGTH('hello'),
ABS(-10),
COALESCE(NULL, 'unknown');
字符字面量 'hello' 一般可以转换为文本,因此 LENGTH 容易解析;NULL 没有值,COALESCE(NULL, 'unknown') 需要由另一个参数提供类型。只有 NULL 时,数据库可能无法确定应该使用哪一个重载:
-- 某些数据库中会因为类型未知而失败
SELECT some_overloaded_function(NULL);
-- 明确意图
SELECT some_overloaded_function(CAST(NULL AS INTEGER));
接口参数也建议在绑定阶段提供类型,而不是把所有值都作为文本发送。
显式转换函数参数
当函数要求的类型与列类型不同,显式转换能让行为和错误位置更清楚:
SELECT DATE_TRUNC('month', CAST(created_at AS TIMESTAMP))
FROM events;
也可以使用数据库的类型转换语法。PostgreSQL 常见写法是 expression::type:
SELECT raw_id::BIGINT
FROM staging_rows;
:: 简短但不可移植;跨数据库的 SQL 优先使用标准 CAST(expression AS type)。不要为了让函数“能接收”而随意把金额转成浮点或把日期转成文本,这可能改变精度、时区和排序语义。
函数重载与类型优先级
数据库可能同时提供:
format(INTEGER)
format(NUMERIC)
format(TEXT)
调用 format(1) 时,整数精确匹配通常优先;调用 format('1') 时,数据库可能把未知字符串解析为文本,也可能根据其他规则选择数字版本。调用 format(NULL) 则更容易产生歧义。
不要用“当前数据库恰好选中了某个版本”作为契约。对重载函数使用显式转换,尤其是公共视图、存储过程和跨版本迁移脚本:
SELECT format(CAST(:value AS NUMERIC(12, 2)));
添加一个新的重载函数也可能改变旧 SQL 的解析结果,因此扩展数据库功能时应回归测试依赖这些函数的查询。
返回类型会继续参与转换
函数的返回值不是类型转换的终点,它还会作为外层操作符、函数或 CASE 的输入:
SELECT ROUND(price, 2) * quantity AS amount
FROM order_items;
这里 ROUND 的返回类型决定后续乘法采用精确数值还是近似数值。如果需要固定输出类型,可以在最外层显式转换:
SELECT CAST(ROUND(price * quantity, 2) AS DECIMAL(14, 2)) AS amount
FROM order_items;
但要注意:最外层转换无法恢复已经在函数内部丢失的精度。应先选择合适的输入类型,再决定显示格式。
字符串、数字和日期函数
字符串格式化函数通常返回文本,不是“看起来像数字”就仍然是数字:
SELECT TO_CHAR(total_amount, 'FM999999990.00') AS display_amount
FROM orders;
display_amount 适合展示,不应再拿来做加法。需要计算时保留原始数值列,最后一步才格式化。
日期解析也应明确格式和时区:
SELECT CAST(:start_date AS DATE) AS start_date;
如果使用方言格式函数,应确认格式标记、语言环境和会话时区。不要把用户输入的 01/02/2025 当作跨地区稳定的日期格式。
聚合函数与输入类型
聚合函数的结果类型通常与输入类型相关:
SELECT COUNT(*) AS row_count,
SUM(quantity) AS total_quantity,
AVG(unit_price) AS average_price
FROM order_items;
COUNT 常返回整数或大整数,AVG 可能返回精确小数或近似类型,SUM 还可能提升到更宽的数值类型。不要只根据客户端显示的数字判断结果类型;需要稳定接口时,用 CAST 固定返回类型,并测试空表和全 NULL 输入:
SELECT CAST(COALESCE(SUM(amount), 0) AS DECIMAL(14, 2)) AS total_amount
FROM payments;
这里先处理聚合结果为 NULL 的情况,再固定输出类型。COALESCE 中的 0 也应与金额类型兼容。
窗口函数与参数类型
窗口函数保留每一行,但其参数仍会参与类型解析:
SELECT order_id,
SUM(amount) OVER (
PARTITION BY customer_id
ORDER BY created_at
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS seven_row_total
FROM payments;
窗口边界中的数字是语法要求的整数概念,不要把它们当作可随意转换的业务值。日期窗口通常需要明确使用时间间隔类型或数据库方言。
多态函数和“兼容类型”
部分数据库支持多态函数,例如一个函数可以接收任意数组并返回相同元素类型。这类函数会根据调用处的参数推导类型:
SELECT ARRAY[1, 2, 3];
如果数组元素来自不同类型,数据库必须寻找共同类型;无法找到时会失败。显式指定数组类型可以消除歧义:
SELECT ARRAY[CAST(1 AS BIGINT), CAST(2 AS BIGINT)];
多态函数的具体名称和类型约束是方言能力。迁移前应查目标数据库是否支持相同的泛型规则。
安全转换函数
直接 CAST 遇到非法输入通常会使整个语句失败:
SELECT CAST(raw_quantity AS INTEGER)
FROM import_rows;
部分数据库提供失败时返回 NULL 的函数,例如 SQL Server 的 TRY_CAST、BigQuery 的 SAFE_CAST。它们便于隔离坏数据,但不是通用 SQL:
-- 仅在支持 SAFE_CAST 的数据库中使用
SELECT SAFE_CAST(raw_quantity AS INT64) AS quantity
FROM import_rows;
安全转换不会告诉你失败原因,也不会自动把空值和非法值区分开。生产导入应同时记录原始文本、失败原因和数据来源,避免把所有问题静默变成 NULL。
条件函数的共同类型
COALESCE、NULLIF、GREATEST 和 LEAST 都会比较或合并多个参数,参数通常需要转换成共同类型:
SELECT COALESCE(discount, CAST(0 AS DECIMAL(12, 2))) AS discount,
NULLIF(status, '') AS normalized_status
FROM products;
如果分支混合文本和数字,数据库可能选择文本类型,后续数值运算就会失败。把参数先转换到业务类型,比等函数内部自动推断更安全。
函数嵌套与转换位置
转换放在不同层次,结果可能完全不同:
-- 先相加再保留两位小数
SELECT CAST(price * quantity AS DECIMAL(14, 2))
FROM order_items;
-- 先把单价舍入,再相加
SELECT CAST(ROUND(price, 2) * quantity AS DECIMAL(14, 2))
FROM order_items;
金额、税费和比例计算应明确舍入时机。不要把“最后显示两位小数”误认为“整个计算过程都只有两位小数”。
与索引和执行计划的关系
函数套在索引列上,常常会让普通索引无法直接使用:
-- 可能对每一行调用 LOWER
WHERE LOWER(email) = LOWER(:email)
改进方案可能是保存规范化列、使用数据库支持的函数索引,或在参数侧完成规范化:
WHERE email_normalized = :normalized_email
是否真的使用索引,应通过目标数据库的执行计划确认;不要仅凭 SQL 表面形式判断性能。
调试函数类型问题
- 先查看函数签名和参数实际类型。
- 把复杂表达式拆成多列,逐层检查中间结果类型。
- 对
NULL、空字符串、非法文本和边界数值分别测试。 - 对重载函数和日期/数值格式使用显式
CAST。 - 固定对外返回类型,避免客户端根据一次查询结果猜测类型。
小结
- 函数调用首先是“函数解析”,然后才是执行;重载和未知字面量可能造成歧义。
- 输入类型决定函数版本和返回类型,返回值还会继续参与外层表达式的转换。
- 聚合、窗口和条件函数都可能改变类型或传播
NULL,空输入必须单独测试。 - 需要容忍脏数据时使用数据库提供的安全转换,并记录失败,而不是静默吞错。
- 函数套在索引列上可能影响性能;必要时调整数据模型或使用专用索引。