函数重载与可变参数
PostgreSQL 允许同名函数共存,只要参数类型不同——这叫函数重载(Function Overloading)。它带来灵活性,也带来「调用时到底命中哪个」的解析难题。本节讲清重载解析规则、VARIADIC 变长参数、以及 CREATE OR REPLACE 在重载场景下的陷阱。
与「函数参数与返回类型」的分工
函数参数与返回类型 讲过单个函数的参数模式与返回形态;本节讲多个同名函数之间如何共存与解析,以及 VARIADIC 这一个特殊参数模式的细节。
前置知识
阅读本节前,建议先了解:函数参数与返回类型、SQL 函数 vs PL/pgSQL 函数
基础概念
函数签名(Signature)是「函数名 + IN/INOUT 参数类型列表」。PostgreSQL 的立场是:函数名不必唯一,签名必须唯一。
| 概念 | 英文 | 说明 |
|---|---|---|
| 函数重载 | Function Overloading | 同名、参数类型不同的一组函数 |
| 签名 | Signature | 函数名 + 参数类型列表(OUT 不计入) |
| 重载解析 | Overload Resolution | 调用时决定命中哪个重载的过程 |
| 精确匹配 | Exact Match | 参数类型完全相同,优先级最高 |
| 隐式转换 | Implicit Cast | 类型可自动转换(如 int → bigint) |
| 歧义 | Ambiguous | 多个候选同样匹配,无法决断,报错 |
| 变长参数 | VARIADIC | 最后一位接收不定个数的实参,聚成数组 |
| 首选类型 | Preferred Type | 同类别内有多候选时的默认倾向(如 float8) |
参数名与默认值不是签名的一部分:
| 变化 | 是否改变签名 | 后果 |
|---|---|---|
| 改参数名 | 否 | 与原函数冲突(already exists) |
| 改参数默认值 | 否 | 与原函数冲突 |
| 改参数类型 | 是 | 变成另一个函数,旧的仍存在 |
| 改返回类型 | 否(但影响替换) | OR REPLACE 会报 cannot change return type |
| 改易变性 | 否 | 可以 OR REPLACE 直接改 |
语法与用法
-- 三个同名重载,靠参数类型区分
CREATE FUNCTION describe(v integer) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'integer:' || v $$;
CREATE FUNCTION describe(v text) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'text:' || v $$;
CREATE FUNCTION describe(v numeric) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'numeric:' || v $$;SELECT describe(42) AS int_call,
describe('hello') AS text_call,
describe(3.14) AS numeric_call; int_call | text_call | numeric_call
--------------+-----------+--------------
integer:42 | text:hello| numeric:3.14
(3 rows)VARIADIC 最小示例:
CREATE FUNCTION join_all(VARIADIC parts text[]) RETURNS text
LANGUAGE sql IMMUTABLE
AS $$ SELECT array_to_string(parts, '-') $$;
-- 展开传参
SELECT join_all('a', 'b', 'c') AS expanded;
-- 传数组
SELECT join_all(VARIADIC ARRAY['a', 'b', 'c']) AS as_array; expanded
----------
a-b-c
(1 row) as_array
----------
a-b-c
(1 row)详细说明
重载解析的三层优先级
调用 f(x) 时,PostgreSQL 按下面的顺序找候选:
- 精确匹配:
x的类型与参数类型完全一致 → 直接胜出。 - 隐式转换:没有精确匹配,但
x的类型能隐式转换到某个候选的参数类型 → 在这些候选中做选择。 - 歧义报错:多个候选处于同一优先级且无法决断 →
function is not unique。
CREATE FUNCTION pick(v integer) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'integer' $$;
CREATE FUNCTION pick(v bigint) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'bigint' $$;
-- 精确匹配:integer 命中 pick(integer)
SELECT pick(1) AS exact_int; exact_int
-----------
integer
(1 row)-- 精确匹配:bigint 命中 pick(bigint)
SELECT pick(1::bigint) AS exact_bigint; exact_bigint
-------------
bigint
(1 row)精确匹配优先于隐式转换,所以即便 integer 能隐式转 bigint,也仍走 pick(integer)。
歧义:同一优先级有多个候选
当没有精确匹配、且有多个等价的隐式转换路径时,解析失败:
CREATE FUNCTION amb(v numeric) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'numeric' $$;
CREATE FUNCTION amb(v real) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'real' $$;
-- 错误!integer 到 numeric 与 real 都能隐式转换,无法决断
SELECT amb(1);ERROR: function amb(integer) is not unique
LINE 1: SELECT amb(1);
^
HINT: Could not choose a best candidate function. You might need to add explicit type casts.解法是显式加类型转换:
SELECT amb(1::numeric) AS as_numeric, amb(1::real) AS as_real; as_numeric | as_real
------------+---------
numeric | real
(2 rows)缩小重载面是最有效的预防
与其记住解析规则,不如减少重载数量。同一个名字只重载 2~3 个「语义一致、类型明显不同」的版本(如 jsonb 版与 text 版),比堆十几个数值类型重载要好维护得多。
优选类型(Preferred Type)
当候选处于同一类型类别内时,PostgreSQL 用「优选类型」打破平局:
| 类型类别 | 优选类型 |
|---|---|
| 数值 | float8(double precision) |
| 字符串 | text |
| 日期时间 | timestamptz |
| 位串 | varbit |
| 布尔 | boolean |
CREATE FUNCTION pref(v real) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'real' $$;
CREATE FUNCTION pref(v double precision) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'double precision' $$;
-- 无精确匹配时,数值类别优选 float8
SELECT pref(1::smallint) AS chosen; chosen
------------------
double precision
(1 row)参数名与默认值不参与签名
-- 第一次
CREATE FUNCTION named(a integer) RETURNS integer
LANGUAGE sql IMMUTABLE AS $$ SELECT a $$;-- 错误!参数名不同不影响签名,仍视为同一个函数
CREATE FUNCTION named(b integer) RETURNS integer
LANGUAGE sql IMMUTABLE AS $$ SELECT b $$;ERROR: function "named" already exists with same argument types这意味着:给已有函数加参数名、改参数名,都必须用 CREATE OR REPLACE 或先 DROP。
CREATE OR REPLACE 在重载下的陷阱
改参数类型 = 新建函数,旧的会留下:
-- 原本存在
CREATE FUNCTION calc(a integer) RETURNS integer
LANGUAGE sql IMMUTABLE AS $$ SELECT a * 2 $$;
-- 想"升级",实际新建了另一个函数
CREATE OR REPLACE FUNCTION calc(a bigint) RETURNS bigint
LANGUAGE sql IMMUTABLE AS $$ SELECT a * 3 $$;\df calc List of functions
Schema | Name | Result data type | Argument data types | Type
--------+------+------------------+---------------------+--------
public | calc | integer | a integer | func
public | calc | bigint | a bigint | func
(2 rows)两个函数共存。更糟的是调用方 calc(5) 会精确命中 integer 版(字面量 5 是 integer),得到 10 而不是你以为的 15。
这是重载场景下最常见的生产事故
「我把函数逻辑改了,怎么没生效?」——十有八九是改了参数类型导致新建了重载,而老调用点仍然命中老版本。改函数前先 \df 函数名 看清现有签名,改完再 \df 核对,不要只看 CREATE FUNCTION 成功就以为替换完成。
排查重载
-- psql:按名字列出所有重载
\df calc
\df+ calc
-- 目录表:按参数类型查
SELECT oid,
proname,
proargtypes::regtype[] AS arg_types,
prorettype::regtype AS return_type
FROM pg_proc
WHERE proname = 'calc'
ORDER BY oid; oid | proname | arg_types | return_type
-------+---------+-----------+-------------
16901 | calc | {integer} | integer
16902 | calc | {bigint} | bigint
(2 rows)proargtypes 是 oidvector 类型,::regtype[] 转换后可直接读出类型名。若已知参数类型想反查:
SELECT 'calc(integer)'::regprocedure AS resolved,
'calc(bigint)'::regprocedure AS resolved_bigint;regprocedure 转换会在类型无法唯一确定时直接报错,非常适合做「是否存在」的检查。
VARIADIC 的规则
CREATE FUNCTION f(a integer, VARIADIC rest text[]) RETURNS ...四条硬规则:
| 规则 | 说明 |
|---|---|
| 必须放最后 | 否则报 VARIADIC parameter must be the last input parameter |
| 类型必须是数组 | 否则报 VARIADIC parameter must be an array type |
| 只能有一个 | 否则报 cannot have multiple VARIADIC parameters |
| 实参可直接展开 | f(1, 'a', 'b'),或整体传数组 f(1, VARIADIC ARRAY['a','b']) |
CREATE FUNCTION tally(p_label text, VARIADIC p_nums numeric[])
RETURNS text
LANGUAGE sql IMMUTABLE
AS $$
SELECT p_label || ':' || COALESCE(sum(n), 0)
FROM unnest(p_nums) AS n;
$$;
SELECT tally('sum', 1, 2, 3.5) AS expanded; expanded
----------
sum:6.5
(1 row)不写 VARIADIC 直接传数组会报错——这是最常见的误用:
-- 错误!数组被当成一个 numeric 实参,与 numeric[] 形参不匹配
SELECT tally('sum', ARRAY[1, 2, 3]);ERROR: function tally(unknown, numeric[]) does not exist
LINE 1: SELECT tally('sum', ARRAY[1, 2, 3]);
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.正确写法是加 VARIADIC 关键字:
SELECT tally('sum', VARIADIC ARRAY[1, 2, 3]) AS as_array; as_array
----------
sum:6
(1 row)空参数边界
VARIADIC 参数可以传零个实参,此时收到空数组:
SELECT tally('empty') AS no_args; no_args
---------
empty:0
(1 row)注意与「传一个 NULL」的区别:
-- 传一个 NULL 元素:数组是 {NULL},长度 1
SELECT tally('null') AS null_arg; null_arg
-----------
null:0
(1 row)sum(NULL) 是 NULL,经 COALESCE 变 0。但 array_length 会是 1 而不是 NULL:
SELECT array_length(VARIADIC ARRAY[]::numeric[], 1) IS NULL AS empty_len_is_null; empty_len_is_null
-------------------
t
(1 row)空数组的 array_length 返回 NULL 而不是 0,这是数组的通用行为,遍历前要做 COALESCE。
遍历 VARIADIC 参数
plpgsql 里两种遍历方式:
-- 方式一:FOREACH 遍历元素
CREATE FUNCTION count_words_foreach(VARIADIC p_words text[])
RETURNS integer
LANGUAGE plpgsql IMMUTABLE
AS $$
DECLARE
w text;
total integer := 0;
BEGIN
FOREACH w IN ARRAY p_words LOOP
total := total + length(w);
END LOOP;
RETURN total;
END;
$$;
-- 方式二:unnest + 聚合(更 SQL 化)
CREATE FUNCTION count_words_sql(VARIADIC p_words text[])
RETURNS integer
LANGUAGE sql IMMUTABLE
AS $$
SELECT COALESCE(sum(length(w)), 0)::integer FROM unnest(p_words) AS w;
$$;
SELECT count_words_foreach('ab', 'cde') AS foreach_result,
count_words_sql('ab', 'cde') AS sql_result; foreach_result | sql_result
----------------+------------
5 | 5
(1 row)FOREACH 与 FOR ... IN 1..array_length(...) 的区别:FOREACH 会自动跳过 NULL 元素并正确处理多维数组,优先用 FOREACH。
array_position 适合「找第几个」:
CREATE FUNCTION index_of(p_needle text, VARIADIC p_haystack text[])
RETURNS integer
LANGUAGE sql IMMUTABLE
AS $$ SELECT array_position(p_haystack, p_needle) $$;
SELECT index_of('b', 'a', 'b', 'c') AS pos; pos
-----
2
(1 row)内置的 VARIADIC 函数
不少内置函数就是 VARIADIC,理解这一点能解释它们的边界行为:
| 函数 | 签名形态 | 说明 |
|---|---|---|
format(fmt text, VARIADIC args any[]) | 变长 | 格式化字符串 |
concat(VARIADIC any) | 变长(伪类型 any) | 拼接任意值,NULL 当空串 |
concat_ws(sep text, VARIADIC any) | 变长 | 带分隔符拼接,会跳过 NULL |
coalesce(VARIADIC any) | 变长 | 取第一个非 NULL |
greatest(VARIADIC any) / least(VARIADIC any) | 变长 | 最大 / 最小 |
jsonb_build_array(VARIADIC any) | 变长 | 构造 JSON 数组 |
jsonb_build_object(VARIADIC any) | 变长 | 构造 JSON 对象(参数成对) |
num_nonnulls(VARIADIC any) | 变长 | 非 NULL 参数个数 |
SELECT concat('a', NULL, 'b') AS concat_keeps,
concat_ws('-', 'a', NULL, 'b') AS concat_ws_skips,
coalesce(NULL, NULL, 'x') AS coalesce_pick,
greatest(1, 9, 5) AS greatest_pick,
num_nonnulls(1, NULL, 3) AS nonnull_count; concat_keeps | concat_ws_skips | coalesce_pick | greatest_pick | nonnull_count
--------------+-----------------+---------------+---------------+---------------
ab | a-b | x | 9 | 2
(1 row)注意 concat 与 concat_ws 的差异:前者把 NULL 当空串从而保留拼接位置,后者跳过 NULL 从而不产生多余分隔符。这是 VARIADIC 语义下最容易搞混的一对。
jsonb_build_object 的 VARIADIC 要求参数成对:
SELECT jsonb_build_object('a', 1, 'b', 2) AS obj; obj
----------------------------
{"a": 1, "b": 2}
(1 row)-- 错误!参数个数为奇数,无法配成键值对
SELECT jsonb_build_object('a', 1, 'b');ERROR: argument list must have even number of elements
HINT: The arguments of jsonb_build_object() must consist of alternating keys and values.重载 vs 命名不同的函数
工程上该选哪个?对照如下:
| 维度 | 用重载(同名) | 用不同名字 |
|---|---|---|
| 调用方书写 | 统一名字,简洁 | 名字要区分,冗长 |
| 类型解析风险 | 有(歧义、误命中) | 无 |
CREATE OR REPLACE 风险 | 高(改类型即新建) | 低 |
| 文档与搜索 | 需按签名检索 | 按名字即可 |
| 语义一致性 | 强制保持同名同义 | 无约束 |
| 排查难度 | \df 要看多行 | 一目了然 |
| 适用场景 | 同一操作支持多种输入类型 | 不同语义的操作 |
推荐:只在「同一语义、输入类型明显不同」时重载,且数量控制在少数几个。语义不同的一律用不同名字。
DROP FUNCTION 必须写全签名
重载存在时,删函数必须给参数类型:
-- 错误!calc 有两个重载
DROP FUNCTION calc;ERROR: function name "calc" is not unique
HINT: Specify the argument list to select the function unambiguously.-- 正确
DROP FUNCTION calc(integer);
DROP FUNCTION calc(bigint);批量删除同名重载,可用目录表生成脚本:
SELECT format('DROP FUNCTION %s;', p.oid::regprocedure) AS drop_stmt
FROM pg_proc AS p
JOIN pg_namespace AS n ON n.oid = p.pronamespace
WHERE p.proname = 'calc' AND n.nspname = 'public'; drop_stmt
--------------------------------------
DROP FUNCTION public.calc(integer);
DROP FUNCTION public.calc(bigint);
(2 rows)重载与 search_path 的跨 schema 风险
函数解析会遍历整个 search_path。不同 schema 下的同名函数会互相干扰:
-- public 下有 app_safe(text)
-- 攻击者在自己 schema 建 app_safe(text)
-- 若该 schema 在 search_path 中更靠前,调用会命中攻击者的版本这不只影响安全性(见 SECURITY DEFINER / INVOKER),也影响正确性:search_path 变化会导致同一段 SQL 调用到不同实现。
-- 检查当前 search_path
SHOW search_path; search_path
-----------------------
"$user", public
(1 row)生产函数里建议用全限定名调用关键函数:
SELECT public.calc(5);实战示例
完整的重载与 VARIADIC 演练:建表、建重载函数、验证解析、排查、清理。
建表与造数据
CREATE SCHEMA IF NOT EXISTS demo;
SET search_path = demo, public;
CREATE TABLE demo.orders (
id bigserial PRIMARY KEY,
customer text NOT NULL,
amount numeric(12, 2) NOT NULL,
status text NOT NULL DEFAULT 'pending',
tags text[] NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO demo.orders (customer, amount, status, tags) VALUES
('alice', 120.00, 'paid', ARRAY['vip', 'wholesale']),
('alice', 30.00, 'pending', ARRAY['vip']),
('bob', 80.50, 'pending', ARRAY['retail']),
('carol', 300.00, 'paid', ARRAY['vip', 'priority']),
('carol', 50.00, 'paid', ARRAY[]::text[]),
('dave', 10.00, 'cancelled', ARRAY['retail']);建一组重载
-- 按客户名汇总
CREATE FUNCTION demo.total(key text)
RETURNS numeric
LANGUAGE sql STABLE
AS $$ SELECT COALESCE(sum(amount), 0) FROM demo.orders WHERE customer = key $$;
-- 按订单 id 取金额
CREATE FUNCTION demo.total(key bigint)
RETURNS numeric
LANGUAGE sql STABLE
AS $$ SELECT COALESCE(sum(amount), 0) FROM demo.orders WHERE id = key $$;
-- 按状态汇总
CREATE FUNCTION demo.total(key demo_orders_status_dummy) RETURNS numeric
LANGUAGE sql STABLE
AS $$ SELECT 0::numeric $$;前三行演示「同名不同签名」。实际使用中只保留前两个:
SELECT demo.total('alice') AS by_name,
demo.total(1::bigint) AS by_id; by_name | by_id
---------+--------
150.00 | 120.00
(1 row)验证精确匹配优先
-- 字面量 1 是 integer,没有 integer 版本,会尝试隐式转换
-- 此时 bigint 是唯一候选,成功
SELECT demo.total(1) AS literal_call; literal_call
--------------
120.00
(1 row)查看最终命中了哪个签名:
SELECT 'demo.total(integer)'::regprocedure AS resolved_signature; resolved_signature
-----------------------------
demo.total(bigint)
(1 row)regprocedure 的转换过程就等同于重载解析,因此它能告诉你会命中哪个。
演示歧义
CREATE FUNCTION demo.category(v numeric) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'numeric' $$;
CREATE FUNCTION demo.category(v real) RETURNS text
LANGUAGE sql IMMUTABLE AS $$ SELECT 'real' $$;
-- 错误!integer → numeric 与 integer → real 都能隐式转换
SELECT demo.category(1);ERROR: function demo.category(integer) is not unique
HINT: Could not choose a best candidate function. You might need to add explicit type casts.显式转换后正常:
SELECT demo.category(1::numeric) AS as_numeric,
demo.category(1::real) AS as_real; as_numeric | as_real
------------+---------
numeric | real
(2 rows)演示「改类型 = 新建函数」
CREATE OR REPLACE FUNCTION demo.total(key bigint) RETURNS numeric
LANGUAGE sql STABLE
AS $$ SELECT COALESCE(sum(amount), 1) FROM demo.orders WHERE id = key $$;上面的语句改的是函数体(签名不变),属于真正的替换。若改成 key int:
CREATE OR REPLACE FUNCTION demo.total(key int) RETURNS numeric
LANGUAGE sql STABLE
AS $$ SELECT COALESCE(sum(amount), 2) FROM demo.orders WHERE id = key $$;
\df demo.total List of functions
Schema | Name | Result data type | Argument data types | Type
--------+-------+------------------+---------------------+------
demo | total | numeric | key bigint | func
demo | total | numeric | key int | func
demo | total | numeric | key text | func
(3 rows)现在有三个 total。这正是要警惕的场景。
VARIADIC 实战:按多个标签筛选
CREATE FUNCTION demo.orders_with_all_tags(VARIADIC p_tags text[])
RETURNS TABLE(id bigint, customer text, tags text[])
LANGUAGE sql STABLE
AS $$
SELECT o.id, o.customer, o.tags
FROM demo.orders AS o
WHERE o.tags @> p_tags
ORDER BY o.id;
$$;
-- 展开传参:要求同时带 vip 与 wholesale
SELECT * FROM demo.orders_with_all_tags('vip', 'wholesale'); id | customer | tags
----+----------+---------------------
1 | alice | {vip,wholesale}
(1 row)-- 传数组:要求同时带 vip 与 priority
SELECT * FROM demo.orders_with_all_tags(VARIADIC ARRAY['vip', 'priority']); id | customer | tags
----+----------+---------------
4 | carol | {vip,priority}
(1 row)-- 零参数:空数组,@> 空数组恒真,返回全部
SELECT count(*) AS all_rows FROM demo.orders_with_all_tags(); all_rows
----------
6
(1 row)VARIADIC + plpgsql 遍历
CREATE FUNCTION demo.tag_report(VARIADIC p_tags text[])
RETURNS TABLE(tag text, order_count bigint)
LANGUAGE plpgsql STABLE
AS $$
DECLARE
t text;
BEGIN
FOREACH t IN ARRAY p_tags LOOP
RETURN QUERY
SELECT t, count(*)
FROM demo.orders AS o
WHERE t = ANY (o.tags);
END LOOP;
END;
$$;
SELECT * FROM demo.tag_report('vip', 'retail', 'nonexistent'); tag | order_count
-------------+-------------
vip | 3
retail | 2
nonexistent | 0
(3 rows)排查与清理
-- 查出某个名字下的全部重载及其定义入口
SELECT p.oid::regprocedure AS signature,
p.provolatile AS volatility,
p.prorettype::regtype AS returns
FROM pg_proc AS p
JOIN pg_namespace AS n ON n.oid = p.pronamespace
WHERE p.proname = 'total' AND n.nspname = 'demo'
ORDER BY p.oid; signature | volatility | returns
----------------------+------------+---------
demo.total(bigint) | s | numeric
demo.total(integer) | s | numeric
demo.total(text) | s | numeric
(3 rows)DROP FUNCTION demo.tag_report(text[]);
DROP FUNCTION demo.orders_with_all_tags(text[]);
DROP FUNCTION demo.category(numeric);
DROP FUNCTION demo.category(real);
DROP FUNCTION demo.total(bigint);
DROP FUNCTION demo.total(integer);
DROP FUNCTION demo.total(text);
DROP SCHEMA demo CASCADE;注意事项
参数名与默认值不构成签名。改名字、改默认值都会与既有函数冲突,必须
CREATE OR REPLACE或先DROP。改参数类型等于新建函数。旧重载会保留,调用方可能仍命中旧版,这是最常见的「改了不生效」原因。
精确匹配优先于隐式转换。
pick(1)永远命中pick(integer),不会命中pick(bigint)。同优先级多候选报
is not unique。用显式::类型消解,不要指望 PostgreSQL「猜」。VARIADIC必须是最后一个参数、类型必须是数组、只能有一个,三条硬规则违反任一条都报错。传数组给
VARIADIC必须加VARIADIC关键字,否则数组被当成单个实参,报function does not exist。VARIADIC可以零实参,此时收到空数组;array_length对空数组返回NULL而不是 0。concat与concat_ws对 NULL 的处理不同:前者当空串(保留位置),后者跳过(不产生额外分隔符)。jsonb_build_object参数必须成对,奇数个报argument list must have even number of elements。DROP FUNCTION必须带签名,重载存在时不写签名报is not unique。search_path变化会导致同名函数解析到不同实现。跨 schema 场景用全限定名调用。OUT参数不进签名。这决定了f(IN a int, OUT b int)与f(IN a int) RETURNS int无法共存。重载过多会显著增加解析开销与排查难度。同一名字下的重载建议不超过 3~5 个。
\df默认只显示部分信息,用\df+看易变性、所有者、描述;用\df schema.*限定范围。重载无法重载「仅返回类型不同」。
f() RETURNS int与f() RETURNS text是两个签名完全相同的函数,不能共存。
最佳实践
只在语义完全相同时重载。「按名汇总」与「按 id 汇总」如果语义不同,就用
total_by_name/total_by_id两个名字。重载数量控制在 2~3 个,避免解析歧义与维护负担。
改函数前先
\df 函数名看清现有签名,改完再核对。把这一步写进上线检查单。给关键函数调用加显式类型转换,尤其是在 SQL 脚本与迁移文件中,避免依赖隐式解析:
sqlSELECT app_total(1::bigint);用
regprocedure做签名固化测试:sqlSELECT 'app_total(integer)'::regprocedure IS NOT NULL AS exists;VARIADIC函数的调用一律显式写VARIADIC当传的是数组变量时,避免「忘了写关键字」的报错。VARIADIC函数体优先用unnest+ 聚合,比FOREACH循环更 SQL 化、通常也更快:sqlSELECT sum(length(w)) FROM unnest(p_words) AS w;VARIADIC参数给命名前缀(p_tags、p_nums),避免与列名、变量名撞车。导出函数清单作为基线,纳入版本管理,便于发现意外新增的重载:
sqlSELECT p.oid::regprocedure FROM pg_proc AS p JOIN pg_namespace AS n ON n.oid = p.pronamespace WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') ORDER BY 1;删除重载用脚本生成,不要手写,减少漏删:
sqlSELECT format('DROP FUNCTION %s;', p.oid::regprocedure) FROM pg_proc AS p JOIN pg_namespace AS n ON n.oid = p.pronamespace WHERE p.proname = '目标名' AND n.nspname = '目标schema';搜索路径明确的函数用全限定名调用,尤其在扩展、触发器、
SECURITY DEFINER场景。为每个重载写
COMMENT,注明与其他重载的区别,\df+时能直接看到。
下一节