函数参数与返回类型
函数的价值一半在参数、一半在返回。本节讲清 IN / OUT / INOUT / VARIADIC 四种参数模式、DEFAULT 默认值的摆放规则、RETURNS SETOF 与 RETURNS TABLE 的等价与差异,以及 plpgsql 中最容易踩的 column reference is ambiguous 到底该怎么解。
与「CREATE FUNCTION 基础」的分工
CREATE FUNCTION 基础 讲语句骨架与语言选择,本节只聚焦参数列表与 RETURNS 子句:参数怎么声明、返回值怎么定型、调用方怎么接。
前置知识
阅读本节前,建议先了解:CREATE FUNCTION 基础
基础概念
一个函数在系统目录里的签名(Signature)只由两件事决定:
- 函数名;
IN与INOUT参数的类型列表(OUT参数不算)。
而调用者看到的是三件事:参数怎么传、返回几行、每行几列。
| 概念 | 英文 | 说明 |
|---|---|---|
| 参数模式 | Parameter Mode | IN / OUT / INOUT / VARIADIC,决定参数方向 |
| 命名参数 | Named Argument | 用 名称 => 值 传参,不受位置限制 |
| 默认值 | Default Value | 参数声明里带 DEFAULT,调用可省略 |
| 集合返回函数 | Set-Returning Function(SRF) | 返回多行,写在 FROM 里当表用 |
| 复合类型 | Composite Type | 多字段组合成的一个类型,可作单行返回值 |
| 记录类型 | record | 结构未定型的行,需靠 OUT/TABLE 或 RETURNS 定 |
| 输出参数 | OUT Parameter | 一个 OUT 参数 = 一个返回列 |
| 变长参数 | VARIADIC | 接收不定个数的实参,收成一个数组 |
返回形态可以按「几行 × 几列」归类:
| 返回形态 | 行数 | 列数 | 声明方式 | 调用方式 |
|---|---|---|---|---|
| 标量 | 1 | 1 | RETURNS integer | SELECT f() |
| 数组 | 1 | 1(数组) | RETURNS integer[] | SELECT f() / unnest(f()) |
| 无返回 | 0 | 0 | RETURNS void | SELECT f() |
| 单行复合 | 1 | n | RETURNS my_type | SELECT * FROM f() |
| 多行单列 | n | 1 | RETURNS SETOF integer | SELECT * FROM f() |
| 多行多列 | n | m | RETURNS TABLE(...) | SELECT * FROM f() |
| 动态多列 | n | m | RETURNS SETOF record + OUT | SELECT * FROM f() AS (…) |
语法与用法
CREATE FUNCTION 名称 (
[ IN | OUT | INOUT | VARIADIC ] [ 参数名 ] 参数类型 [ { DEFAULT | = } 默认值 ]
[, ...]
)
RETURNS 返回类型
LANGUAGE 语言名
AS 函数体RETURNS 的几种写法:
RETURNS void
RETURNS integer
RETURNS integer[]
RETURNS SETOF integer
RETURNS TABLE(列名 类型 [, ...])
RETURNS my_composite_type
RETURNS SETOF my_composite_type
RETURNS record最小可运行示例——同一个查询的四种返回声明:
-- 1) SETOF 标量
CREATE FUNCTION nums_setof(n integer) RETURNS SETOF integer
LANGUAGE sql IMMUTABLE
AS $$ SELECT g FROM generate_series(1, n) AS g $$;
-- 2) TABLE 多列
CREATE FUNCTION nums_table(n integer) RETURNS TABLE(v integer, sq integer)
LANGUAGE sql IMMUTABLE
AS $$ SELECT g, g * g FROM generate_series(1, n) AS g $$;
-- 3) OUT 参数(等价于 TABLE)
CREATE FUNCTION nums_out(n integer, OUT v integer, OUT sq integer)
LANGUAGE sql IMMUTABLE
AS $$ SELECT g, g * g FROM generate_series(1, n) AS g $$;
-- 4) 单行复合(用 record + OUT 或命名复合类型)
CREATE FUNCTION nums_one(n integer, OUT lo integer, OUT hi integer)
LANGUAGE sql IMMUTABLE
AS $$ SELECT 1, n $$;SELECT * FROM nums_setof(3); nums_setof
------------
1
2
3
(3 rows)SELECT * FROM nums_table(3); v | sq
---+----
1 | 1
2 | 4
3 | 9
(3 rows)SELECT * FROM nums_out(3); v | sq
---+----
1 | 1
2 | 4
3 | 9
(3 rows)注意 nums_out 的签名只有 (integer)——OUT 参数不进签名:
SELECT proname, proargtypes::regtype[] AS in_args
FROM pg_proc
WHERE proname IN ('nums_out', 'nums_table'); proname | in_args
-----------+--------------
nums_out | {integer}
nums_table| {integer}
(2 rows)两者签名完全相同,因此不能共存:
-- 错误!nums_out 已占用签名 (integer),RETURNS TABLE 也一样
CREATE FUNCTION nums_out(n integer) RETURNS TABLE(v integer, sq integer)
LANGUAGE sql IMMUTABLE
AS $$ SELECT g, g * g FROM generate_series(1, n) AS g $$;ERROR: function "nums_out" already exists with same argument types详细说明
IN / OUT / INOUT
| 模式 | 调用时传入 | 参与签名 | 参与返回 | 典型用途 |
|---|---|---|---|---|
IN(默认) | 是 | 是 | 否 | 普通输入 |
OUT | 否 | 否 | 是 | 返回多列,替代 RETURNS TABLE |
INOUT | 是 | 是 | 是 | 输入同时又要回传(如计数器) |
VARIADIC | 是(可变个数) | 是 | 否 | 变长参数 |
OUT 与 TABLE 的取舍:
RETURNS TABLE(...)更直观:列名紧贴返回位置,读函数头就知道返回什么。OUT参数在函数体里能直接当变量用:plpgsql 里可以直接v := 10;赋值,而不必RETURN NEXT一行。
CREATE FUNCTION split_pair(p_input text, OUT left_part text, OUT right_part text)
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
left_part := split_part(p_input, ',', 1);
right_part := split_part(p_input, ',', 2);
END;
$$;
SELECT * FROM split_pair('a,b'); left_part | right_part
-----------+------------
a | b
(1 row)INOUT 实战——累加器:
CREATE FUNCTION bump(INOUT counter integer, step integer DEFAULT 1)
RETURNS integer
LANGUAGE sql
IMMUTABLE
AS $$ SELECT counter + step $$;
SELECT bump(10) AS by_one, bump(10, 5) AS by_five; by_one | by_five
--------+---------
11 | 15
(1 row)DEFAULT 默认值的摆放规则
有默认值的参数之后,不能再出现无默认值的参数。 原因很直接:调用时按位置匹配,如果后面还有个必填参数,省略前一个后调用者根本无法表达「这个位置我要用默认值」。
-- 错误!有默认值参数之后还有必填参数
CREATE FUNCTION bad_default(a integer DEFAULT 1, b integer)
RETURNS integer LANGUAGE sql AS $$ SELECT a + b $$;ERROR: input parameters after one with a default value must also have defaults三种解法:
解法一:调整顺序,把必填参数放前面:
CREATE FUNCTION ok_default(b integer, a integer DEFAULT 1)
RETURNS integer LANGUAGE sql IMMUTABLE
AS $$ SELECT a + b $$;
SELECT ok_default(10) AS use_default, ok_default(10, 5) AS explicit; use_default | explicit
-------------+----------
11 | 15
(1 row)解法二:给后面的参数也加默认值:
CREATE FUNCTION both_default(a integer DEFAULT 1, b integer DEFAULT 0)
RETURNS integer LANGUAGE sql IMMUTABLE
AS $$ SELECT a + b $$;解法三:用命名参数调用,跳过中间的参数:
-- 假设签名是 (a int DEFAULT 1, b int DEFAULT 0, c int)
-- 不行,c 仍必须放前面。命名参数只能"跳过有默认值的":
CREATE FUNCTION mixed(a integer, b integer DEFAULT 2, c integer DEFAULT 3)
RETURNS integer LANGUAGE sql IMMUTABLE
AS $$ SELECT a * 100 + b * 10 + c $$;
SELECT mixed(1) AS all_default, -- 1,2,3
mixed(1, 9) AS override_b, -- 1,9,3
mixed(1, c => 7) AS override_c; -- 1,2,7 all_default | override_b | override_c
-------------+------------+------------
123 | 193 | 127
(3 rows)命名参数调用规则
命名参数用 名称 => 值 或 名称 := 值(:= 是旧写法,=> 更推荐,不会与 = 混淆)。
规则:位置参数必须写在所有命名参数之前。
SELECT mixed(1, b => 5) AS ok; -- 位置参数在前,合法-- 错误!命名参数之后不能再出现位置参数
SELECT mixed(a => 1, 2) AS bad;ERROR: positional argument cannot follow named argument参数名是声明里的名字。加了参数名之后再改名,会让所有用命名方式调用的地方失效——生产库里改参数名要当作破坏性变更对待。
RETURNS SETOF 与 RETURNS TABLE
两者返回的都是「多行」,调用方式都是 SELECT * FROM f():
| 维度 | RETURNS SETOF type | RETURNS TABLE(...) |
|---|---|---|
| 返回结构 | 1 列(类型可复合) | 多列,列名显式声明 |
| 等价写法 | SETOF my_composite_type = TABLE(同结构) | TABLE(...) 实质是「RETURNS SETOF record + 若干 OUT」 |
| 列名来源 | 来自类型的字段名 / 单值无名 | 声明里直接给出 |
| 单列场景 | SETOF integer 最合适 | TABLE(v integer) 多此一举 |
| 泛化能力 | 需要预先定义复合类型 | 可临时定义任意列组合 |
与 OUT 的关系 | 不能带 OUT | 每个列就是一个 OUT |
单列时用 SETOF:
CREATE FUNCTION even_numbers(n integer) RETURNS SETOF integer
LANGUAGE sql IMMUTABLE
AS $$ SELECT g FROM generate_series(2, n, 2) AS g $$;
SELECT array_agg(x) AS evens FROM even_numbers(10) AS x; evens
------------------
{2,4,6,8,10}
(1 row)多列时用 TABLE:
CREATE FUNCTION customer_stats(p_min numeric)
RETURNS TABLE(customer text, order_count bigint, total numeric)
LANGUAGE sql STABLE
AS $$
SELECT customer, count(*), sum(amount)
FROM orders
WHERE amount >= p_min
GROUP BY customer
ORDER BY total DESC;
$$;SETOF 也可以返回复合类型,效果与 TABLE 相同但需要事先 CREATE TYPE:
CREATE TYPE order_row AS (id bigint, amount numeric);
CREATE FUNCTION big_orders(p_min numeric) RETURNS SETOF order_row
LANGUAGE sql STABLE
AS $$ SELECT id, amount FROM orders WHERE amount >= p_min ORDER BY id $$;RETURNS TABLE 与 RETURNS SETOF record
RETURNS SETOF record 表示「结构在创建时不定型」,必须在调用处指定列定义:
CREATE FUNCTION dyn_cols(p_sql text) RETURNS SETOF record
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY EXECUTE p_sql;
END;
$$;
-- 调用时必须给出列定义
SELECT * FROM dyn_cols('SELECT id, amount FROM orders') AS t(id bigint, amount numeric);| 维度 | RETURNS TABLE(...) | RETURNS SETOF record |
|---|---|---|
| 结构何时确定 | 创建函数时 | 调用时(AS t(...)) |
| 调用方便度 | 好,直接 SELECT * FROM f() | 差,每次都要写列定义 |
| 适用场景 | 结构固定的常规函数 | 通用查询执行器、动态报表 |
| 类型检查 | 创建时校验 | 运行时才知道 |
能定死结构就用 TABLE。SETOF record 只在「列数真的由输入决定」时才用,它会把类型错误推迟到运行期。
返回数组 vs 返回行集
同样是「给出一组值」,数组与行集的行为差异很大:
| 维度 | RETURNS integer[] | RETURNS SETOF integer |
|---|---|---|
| 结果形状 | 一行的数组值 | 多行 |
在 FROM 里 | 需 unnest() 展开 | 直接当表 |
在 WHERE IN 里 | 可直接 = ANY(arr) | 需子查询 |
能否 LIMIT | 不能(先全量构造) | 能,可流式 |
| 构造开销 | 需先物化整数组 | 可边算边吐 |
-- 数组:适合「小集合、需要整块传递」
CREATE FUNCTION tag_list(p_id bigint) RETURNS text[]
LANGUAGE sql STABLE
AS $$ SELECT array_agg(tag) FROM tags WHERE order_id = p_id $$;
-- 行集:适合「可能很大、需要 LIMIT / 连接」
CREATE FUNCTION tag_rows(p_id bigint) RETURNS SETOF text
LANGUAGE sql STABLE
AS $$ SELECT tag FROM tags WHERE order_id = p_id $$;RETURNS void
void 表示「没有返回值」,用于有副作用的函数:
CREATE FUNCTION touch_order(p_id bigint) RETURNS void
LANGUAGE sql VOLATILE
AS $$ UPDATE orders SET updated_at = now() WHERE id = p_id $$;
SELECT touch_order(1); touch_order
-------------
(1 row)void 函数也能出现在 FROM 里,但只会产生零列的行,实际用途不多:
SELECT count(*) FROM touch_order(1); count
-------
0
(1 row)plpgsql 的经典坑:column reference is ambiguous
RETURNS TABLE(col ...) 或 OUT col 会在函数体内引入同名变量。当 SQL 语句里也出现同名列时,plpgsql 无法判断你指的是变量还是表的列:
CREATE FUNCTION ambiguous_demo(p_status text)
RETURNS TABLE(order_count bigint, total_amount numeric)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT count(*), sum(amount)
FROM orders
WHERE status = p_status; -- status 是谁?
END;
$$;若函数里也有个叫 status 的表列,会报:
ERROR: column reference "status" is ambiguous
LINE 4: WHERE status = p_status;
^
DETAIL: It could refer to either a PL/pgSQL variable or a table column.三种解法:
解法一:给表加别名,把列限定住(最推荐):
CREATE FUNCTION stats_fixed(p_status text)
RETURNS TABLE(order_count bigint, total_amount numeric)
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
RETURN QUERY
SELECT count(*), COALESCE(sum(o.amount), 0)
FROM orders AS o
WHERE o.status = p_status;
END;
$$;解法二:给参数加前缀,避免与列名撞车:
CREATE FUNCTION stats_prefixed(p_status text)
RETURNS TABLE(order_count bigint, total_amount numeric)
LANGUAGE plpgsql
STABLE
AS $$
BEGIN
RETURN QUERY
SELECT count(*), COALESCE(sum(amount), 0)
FROM orders
WHERE status = p_status; -- status 现在只可能是列名
END;
$$;解法三:显式声明冲突消解策略:
CREATE FUNCTION stats_directive(p_status text)
RETURNS TABLE(order_count bigint, total_amount numeric)
LANGUAGE plpgsql
STABLE
AS $$
#variable_conflict use_column
BEGIN
RETURN QUERY
SELECT count(*), COALESCE(sum(amount), 0)
FROM orders
WHERE status = p_status;
END;
$$;#variable_conflict 的取值:
| 取值 | 含义 |
|---|---|
error(默认) | 有歧义就报错 |
use_variable | 优先当变量 |
use_column | 优先当列 |
别把 use_column 当万能药
#variable_conflict use_column 只是把默认行为从「报错」改成「静默选列」。一旦某处你其实想用变量,它会静默选错列,问题比报错更难查。优先用表别名——既消除歧义,又让 SQL 更清晰。
LANGUAGE sql 的返回写法
sql 函数不需要(也不能)写 RETURN。它的约定是:最后一条 SELECT / INSERT ... RETURNING 的结果就是返回值。
-- 返回标量:单条 SELECT
CREATE FUNCTION tax(v numeric) RETURNS numeric
LANGUAGE sql IMMUTABLE
AS $$ SELECT round(v * 1.06, 2) $$;
-- 返回行集:单条 SELECT 多行
CREATE FUNCTION big_ids(p_min numeric) RETURNS SETOF bigint
LANGUAGE sql STABLE
AS $$ SELECT id FROM orders WHERE amount >= p_min $$;
-- 返回写入结果
CREATE FUNCTION insert_order(p_customer text, p_amount numeric)
RETURNS bigint
LANGUAGE sql VOLATILE
AS $$ INSERT INTO orders (customer, amount) VALUES (p_customer, p_amount) RETURNING id $$;plpgsql 则需要显式返回:
| 目的 | sql 写法 | plpgsql 写法 |
|---|---|---|
| 返回单值 | 末尾 SELECT expr | RETURN expr; |
| 返回一行 | 末尾 SELECT ...(返回复合) | RETURN row_var; |
| 返回多行(整查询) | 末尾 SELECT ... | RETURN QUERY SELECT ...; |
| 返回多行(逐行攒) | 不适用 | RETURN NEXT expr; 循环内调用 |
| 返回动态 SQL 结果 | 不适用 | RETURN QUERY EXECUTE ...; |
RETURN NEXT 的用法:
CREATE FUNCTION fib(n integer) RETURNS SETOF bigint
LANGUAGE plpgsql IMMUTABLE
AS $$
DECLARE
a bigint := 0;
b bigint := 1;
tmp bigint;
i integer;
BEGIN
FOR i IN 1..n LOOP
RETURN NEXT a;
tmp := a + b;
a := b;
b := tmp;
END LOOP;
END;
$$;
SELECT array_agg(x) AS fib10 FROM fib(10) AS x; fib10
---------------------------
{0,1,1,2,3,5,8,13,21,34}
(1 row)RETURNING 与函数结合
INSERT / UPDATE / DELETE 的 RETURNING 可以直接作为 sql 函数的返回来源,这是「写入后回传播」最简洁的写法(见上面的 insert_order)。
若 plpgsql 里要做同样的事,用 RETURNING ... INTO:
CREATE FUNCTION upsert_customer(p_name text, p_amount numeric)
RETURNS bigint
LANGUAGE plpgsql VOLATILE
AS $$
DECLARE
v_id bigint;
BEGIN
INSERT INTO orders (customer, amount)
VALUES (p_name, p_amount)
RETURNING id INTO v_id;
RETURN v_id;
END;
$$;ROWS 与 COST 提示
这两个子句给优化器估算提示,只影响计划,不影响结果:
| 子句 | 含义 | 默认 |
|---|---|---|
ROWS n | 预估返回行数(针对 SRF) | 1000 |
COST n | 单次调用的大致代价(cpu_operator_cost 为单位) | sql/plpgsql 为 1(ROWS>1 时为 10) |
CREATE FUNCTION big_ids_hint(p_min numeric) RETURNS SETOF bigint
LANGUAGE sql STABLE
ROWS 5000
COST 20
AS $$ SELECT id FROM orders WHERE amount >= p_min $$;ROWS 估小了,优化器可能选择嵌套循环;估大了,可能选择哈希连接却拿不到那么多行。默认值 1000 对绝大多数函数都是错的,返回行数明显偏离时应该显式声明。更完整的代价与计划器交互见函数易变性与「函数规划器支持」相关内容。
实战示例
完整的「建表 → 多种返回形态 → 调用 → 清理」演练。
建表与造数据
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',
created_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO demo.orders (customer, amount, status) VALUES
('alice', 120.00, 'paid'),
('alice', 30.00, 'pending'),
('bob', 80.50, 'pending'),
('carol', 300.00, 'paid'),
('carol', 50.00, 'paid'),
('dave', 10.00, 'cancelled');返回标量
CREATE FUNCTION demo.customer_total(p_customer text)
RETURNS numeric
LANGUAGE sql STABLE
AS $$
SELECT COALESCE(sum(o.amount), 0)
FROM demo.orders AS o
WHERE o.customer = p_customer;
$$;
SELECT demo.customer_total('alice') AS alice_total,
demo.customer_total('nobody') AS nobody_total; alice_total | nobody_total
-------------+--------------
150.00 | 0.00
(1 row)返回行集(TABLE 多列)
CREATE FUNCTION demo.status_report(p_min numeric DEFAULT 0)
RETURNS TABLE(status text, order_count bigint, total numeric)
LANGUAGE sql STABLE
AS $$
SELECT o.status, count(*), sum(o.amount)
FROM demo.orders AS o
WHERE o.amount >= p_min
GROUP BY o.status
ORDER BY total DESC;
$$;
SELECT * FROM demo.status_report(); status | order_count | total
-----------+-------------+--------
paid | 3 | 470.00
pending | 2 | 110.50
cancelled | 1 | 10.00
(3 rows)SELECT * FROM demo.status_report(50); status | order_count | total
----------+-------------+--------
paid | 3 | 470.00
pending | 1 | 80.50
(2 rows)返回数组
CREATE FUNCTION demo.paid_customers()
RETURNS text[]
LANGUAGE sql STABLE
AS $$
SELECT array_agg(DISTINCT o.customer ORDER BY o.customer)
FROM demo.orders AS o
WHERE o.status = 'paid';
$$;
SELECT demo.paid_customers() AS customers; customers
----------------
{alice,carol}
(1 row)OUT 参数返回多列
CREATE FUNCTION demo.order_span(p_customer text, OUT lo numeric, OUT hi numeric)
LANGUAGE sql STABLE
AS $$
SELECT min(o.amount), max(o.amount)
FROM demo.orders AS o
WHERE o.customer = p_customer;
$$;
SELECT * FROM demo.order_span('alice'); lo | hi
-------+--------
30.00 | 120.00
(1 row)VARIADIC 汇总多客户
CREATE FUNCTION demo.total_of(VARIADIC p_customers text[])
RETURNS numeric
LANGUAGE sql STABLE
AS $$
SELECT COALESCE(sum(o.amount), 0)
FROM demo.orders AS o
WHERE o.customer = ANY (p_customers);
$$;
-- 直接展开传参
SELECT demo.total_of('alice', 'bob') AS two_customers;
-- 传数组
SELECT demo.total_of(VARIADIC ARRAY['alice', 'carol']) AS array_form; two_customers
---------------
230.50
(1 row) array_form
------------
620.00
(1 row)消除歧义的完整对照
-- 会报 ambiguous 的版本
CREATE FUNCTION demo.stats_ambiguous(p_status text)
RETURNS TABLE(status text, order_count bigint)
LANGUAGE plpgsql STABLE
AS $$
BEGIN
RETURN QUERY
SELECT status, count(*)
FROM demo.orders
WHERE status = p_status
GROUP BY status;
END;
$$;ERROR: column reference "status" is ambiguous-- 修正:表别名 + 参数前缀
CREATE FUNCTION demo.stats_ok(p_status text)
RETURNS TABLE(status text, order_count bigint)
LANGUAGE plpgsql STABLE
AS $$
BEGIN
RETURN QUERY
SELECT o.status, count(*)
FROM demo.orders AS o
WHERE o.status = p_status
GROUP BY o.status;
END;
$$;
SELECT * FROM demo.stats_ok('paid'); status | order_count
--------+-------------
paid | 3
(1 row)清理
DROP FUNCTION demo.stats_ok(text);
DROP FUNCTION demo.stats_ambiguous(text);
DROP FUNCTION demo.total_of(text[]);
DROP FUNCTION demo.order_span(text);
DROP FUNCTION demo.paid_customers();
DROP FUNCTION demo.status_report(numeric);
DROP FUNCTION demo.customer_total(text);
DROP SCHEMA demo CASCADE;注意事项
OUT参数不参与签名。因此f(IN a int, OUT b int)与f(IN a int) RETURNS int是同一个签名,不能共存。OUT参数不能有DEFAULT。默认值只对调用方要传的参数(IN/INOUT)有意义。INOUT必须同时写RETURNS,且RETURNS类型要与INOUT参数类型兼容。DEFAULT表达式在每次调用时求值。写DEFAULT now()是「调用时刻」,写DEFAULT '2020-01-01'是编译期常量,但都不允许引用其他参数。有默认值的参数之后不能有必填参数,报
input parameters after one with a default value must also have defaults。命名参数调用时,位置参数必须在命名参数之前,否则报
positional argument cannot follow named argument。RETURNS TABLE的列名会与函数体内变量冲突,plpgsql 报column reference is ambiguous。首选表别名解法。RETURNS TABLE的列名也是变量名。函数体内RETURN NEXT单值会失败,因为返回的是整行——要么用RETURN QUERY,要么给所有列赋值后RETURN NEXT。sql-- 错误!RETURN NEXT 需要一整行,不是单个值 CREATE FUNCTION bad_next(n integer) RETURNS TABLE(v integer) LANGUAGE plpgsql AS $$ BEGIN RETURN NEXT n; -- 报错 END; $$;RETURNS SETOF record必须在调用处给列定义,否则报a column definition list is required for functions returning "record"。RETURNS void函数在FROM里产生零列,无法取到任何字段,不要指望拿它当数据源。改参数名会破坏命名参数调用点。改名等于破坏性变更,尤其当应用用了
f(x => ...)。ROWS默认 1000,常常严重偏离实际。返回行数稳定的函数应显式声明。sql函数体里不能写RETURN,写了直接语法错误。RETURNS类型无法用OR REPLACE修改。改返回类型会遇到cannot change return type of existing function,必须先DROP。
最佳实践
参数统一加前缀(
p_或p),从根上避免与列名、变量名撞车:sqlCREATE FUNCTION app_find_orders(p_customer text, p_min numeric)优先
RETURNS TABLE(...)而不是OUT参数,除非确实需要「函数体内直接给 OUT 变量赋值」的写法。返回结构写在RETURNS处更醒目。能定结构就不要用
SETOF record。类型检查提前到创建期,问题早暴露。参数顺序遵循「必填在前、可选在后」,天然规避默认值摆放限制。
调用时优先使用命名参数,可读性显著提升,且对参数顺序调整免疫:
sqlSELECT app_find_orders(p_min => 100, p_customer => 'alice');公网/应用接口层的函数一律不用命名参数,避免参数名变更造成外部破坏;内部函数则可以放心用。
SRF 显式声明
ROWS,尤其是返回行数偏离 1000 明显的函数。返回行集而不是数组,除非集合很小且调用方需要整块(如
= ANY(arr))。把「写入 + 返回 id」封装成
sql函数,用RETURNING一步到位,避免应用层两次往返:sqlCREATE FUNCTION app_create_order(p_customer text, p_amount numeric) RETURNS bigint LANGUAGE sql VOLATILE AS $$ INSERT INTO orders (customer, amount) VALUES (p_customer, p_amount) RETURNING id $$;配套写调用示例作为文档,
COMMENT ON FUNCTION里说明返回列含义与空值语义。RETURNS TABLE一律给表起别名,把它当编码规范执行,从源头消除ambiguous报错。
下一节