Skip to content

函数参数与返回类型 ​

函数的价值一半在参数、一半在返回。本节讲清 IN / OUT / INOUT / VARIADIC 四种参数模式、DEFAULT 默认值的摆放规则、RETURNS SETOF 与 RETURNS TABLE 的等价与差异,以及 plpgsql 中最容易踩的 column reference is ambiguous 到底该怎么解。

与「CREATE FUNCTION 基础」的分工

CREATE FUNCTION 基础 讲语句骨架与语言选择,本节只聚焦参数列表与 RETURNS 子句:参数怎么声明、返回值怎么定型、调用方怎么接。

前置知识

阅读本节前,建议先了解:CREATE FUNCTION 基础

基础概念 ​

一个函数在系统目录里的签名(Signature)只由两件事决定:

  1. 函数名;
  2. IN 与 INOUT 参数的类型列表(OUT 参数不算)。

而调用者看到的是三件事:参数怎么传、返回几行、每行几列。

概念英文说明
参数模式Parameter ModeIN / OUT / INOUT / VARIADIC,决定参数方向
命名参数Named Argument用 名称 => 值 传参,不受位置限制
默认值Default Value参数声明里带 DEFAULT,调用可省略
集合返回函数Set-Returning Function(SRF)返回多行,写在 FROM 里当表用
复合类型Composite Type多字段组合成的一个类型,可作单行返回值
记录类型record结构未定型的行,需靠 OUT/TABLE 或 RETURNS 定
输出参数OUT Parameter一个 OUT 参数 = 一个返回列
变长参数VARIADIC接收不定个数的实参,收成一个数组

返回形态可以按「几行 × 几列」归类:

返回形态行数列数声明方式调用方式
标量11RETURNS integerSELECT f()
数组11(数组)RETURNS integer[]SELECT f() / unnest(f())
无返回00RETURNS voidSELECT f()
单行复合1nRETURNS my_typeSELECT * FROM f()
多行单列n1RETURNS SETOF integerSELECT * FROM f()
多行多列nmRETURNS TABLE(...)SELECT * FROM f()
动态多列nmRETURNS SETOF record + OUTSELECT * FROM f() AS (…)

语法与用法 ​

text
CREATE FUNCTION 名称 (
    [ IN | OUT | INOUT | VARIADIC ] [ 参数名 ] 参数类型 [ { DEFAULT | = } 默认值 ]
  [, ...]
)
RETURNS 返回类型
LANGUAGE 语言名
AS 函数体

RETURNS 的几种写法:

text
RETURNS void
RETURNS integer
RETURNS integer[]
RETURNS SETOF integer
RETURNS TABLE(列名 类型 [, ...])
RETURNS my_composite_type
RETURNS SETOF my_composite_type
RETURNS record

最小可运行示例——同一个查询的四种返回声明:

sql
-- 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 $$;
sql
SELECT * FROM nums_setof(3);
text
 nums_setof
------------
          1
          2
          3
(3 rows)
sql
SELECT * FROM nums_table(3);
text
 v | sq
---+----
 1 |  1
 2 |  4
 3 |  9
(3 rows)
sql
SELECT * FROM nums_out(3);
text
 v | sq
---+----
 1 |  1
 2 |  4
 3 |  9
(3 rows)

注意 nums_out 的签名只有 (integer)——OUT 参数不进签名:

sql
SELECT proname, proargtypes::regtype[] AS in_args
FROM pg_proc
WHERE proname IN ('nums_out', 'nums_table');
text
  proname  |   in_args
-----------+--------------
 nums_out  | {integer}
 nums_table| {integer}
(2 rows)

两者签名完全相同,因此不能共存:

sql
-- 错误!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 $$;
text
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 一行。
sql
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');
text
 left_part | right_part
-----------+------------
 a         | b
(1 row)

INOUT 实战——累加器:

sql
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;
text
 by_one | by_five
--------+---------
     11 |      15
(1 row)

DEFAULT 默认值的摆放规则 ​

有默认值的参数之后,不能再出现无默认值的参数。 原因很直接:调用时按位置匹配,如果后面还有个必填参数,省略前一个后调用者根本无法表达「这个位置我要用默认值」。

sql
-- 错误!有默认值参数之后还有必填参数
CREATE FUNCTION bad_default(a integer DEFAULT 1, b integer)
RETURNS integer LANGUAGE sql AS $$ SELECT a + b $$;
text
ERROR:  input parameters after one with a default value must also have defaults

三种解法:

解法一:调整顺序,把必填参数放前面:

sql
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;
text
 use_default | explicit
-------------+----------
          11 |       15
(1 row)

解法二:给后面的参数也加默认值:

sql
CREATE FUNCTION both_default(a integer DEFAULT 1, b integer DEFAULT 0)
RETURNS integer LANGUAGE sql IMMUTABLE
AS $$ SELECT a + b $$;

解法三:用命名参数调用,跳过中间的参数:

sql
-- 假设签名是 (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
text
 all_default | override_b | override_c
-------------+------------+------------
         123 |        193 |        127
(3 rows)

命名参数调用规则 ​

命名参数用 名称 => 值 或 名称 := 值(:= 是旧写法,=> 更推荐,不会与 = 混淆)。

规则:位置参数必须写在所有命名参数之前。

sql
SELECT mixed(1, b => 5) AS ok;          -- 位置参数在前,合法
sql
-- 错误!命名参数之后不能再出现位置参数
SELECT mixed(a => 1, 2) AS bad;
text
ERROR:  positional argument cannot follow named argument

参数名是声明里的名字。加了参数名之后再改名,会让所有用命名方式调用的地方失效——生产库里改参数名要当作破坏性变更对待。

RETURNS SETOF 与 RETURNS TABLE ​

两者返回的都是「多行」,调用方式都是 SELECT * FROM f():

维度RETURNS SETOF typeRETURNS TABLE(...)
返回结构1 列(类型可复合)多列,列名显式声明
等价写法SETOF my_composite_type = TABLE(同结构)TABLE(...) 实质是「RETURNS SETOF record + 若干 OUT」
列名来源来自类型的字段名 / 单值无名声明里直接给出
单列场景SETOF integer 最合适TABLE(v integer) 多此一举
泛化能力需要预先定义复合类型可临时定义任意列组合
与 OUT 的关系不能带 OUT每个列就是一个 OUT

单列时用 SETOF:

sql
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;
text
      evens
------------------
 {2,4,6,8,10}
(1 row)

多列时用 TABLE:

sql
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:

sql
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 表示「结构在创建时不定型」,必须在调用处指定列定义:

sql
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不能(先全量构造)能,可流式
构造开销需先物化整数组可边算边吐
sql
-- 数组:适合「小集合、需要整块传递」
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 表示「没有返回值」,用于有副作用的函数:

sql
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);
text
 touch_order
-------------
 
(1 row)

void 函数也能出现在 FROM 里,但只会产生零列的行,实际用途不多:

sql
SELECT count(*) FROM touch_order(1);
text
 count
-------
     0
(1 row)

plpgsql 的经典坑:column reference is ambiguous ​

RETURNS TABLE(col ...) 或 OUT col 会在函数体内引入同名变量。当 SQL 语句里也出现同名列时,plpgsql 无法判断你指的是变量还是表的列:

sql
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 的表列,会报:

text
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.

三种解法:

解法一:给表加别名,把列限定住(最推荐):

sql
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;
$$;

解法二:给参数加前缀,避免与列名撞车:

sql
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;
$$;

解法三:显式声明冲突消解策略:

sql
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 的结果就是返回值。

sql
-- 返回标量:单条 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 exprRETURN expr;
返回一行末尾 SELECT ...(返回复合)RETURN row_var;
返回多行(整查询)末尾 SELECT ...RETURN QUERY SELECT ...;
返回多行(逐行攒)不适用RETURN NEXT expr; 循环内调用
返回动态 SQL 结果不适用RETURN QUERY EXECUTE ...;

RETURN NEXT 的用法:

sql
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;
text
          fib10
---------------------------
 {0,1,1,2,3,5,8,13,21,34}
(1 row)

RETURNING 与函数结合 ​

INSERT / UPDATE / DELETE 的 RETURNING 可以直接作为 sql 函数的返回来源,这是「写入后回传播」最简洁的写法(见上面的 insert_order)。

若 plpgsql 里要做同样的事,用 RETURNING ... INTO:

sql
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)
sql
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 对绝大多数函数都是错的,返回行数明显偏离时应该显式声明。更完整的代价与计划器交互见函数易变性与「函数规划器支持」相关内容。

实战示例 ​

完整的「建表 → 多种返回形态 → 调用 → 清理」演练。

建表与造数据 ​

sql
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');

返回标量 ​

sql
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;
text
 alice_total | nobody_total
-------------+--------------
      150.00 |         0.00
(1 row)

返回行集(TABLE 多列) ​

sql
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();
text
  status   | order_count | total
-----------+-------------+--------
 paid      |           3 | 470.00
 pending   |           2 | 110.50
 cancelled |           1 |  10.00
(3 rows)
sql
SELECT * FROM demo.status_report(50);
text
  status  | order_count | total
----------+-------------+--------
 paid     |           3 | 470.00
 pending  |           1 |  80.50
(2 rows)

返回数组 ​

sql
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;
text
   customers
----------------
 {alice,carol}
(1 row)

OUT 参数返回多列 ​

sql
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');
text
  lo   |  hi
-------+--------
 30.00 | 120.00
(1 row)

VARIADIC 汇总多客户 ​

sql
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;
text
 two_customers
---------------
        230.50
(1 row)
text
 array_form
------------
     620.00
(1 row)

消除歧义的完整对照 ​

sql
-- 会报 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;
$$;
text
ERROR:  column reference "status" is ambiguous
sql
-- 修正:表别名 + 参数前缀
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');
text
 status | order_count
--------+-------------
 paid   |           3
(1 row)

清理 ​

sql
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),从根上避免与列名、变量名撞车:

    sql
    CREATE FUNCTION app_find_orders(p_customer text, p_min numeric)
  • 优先 RETURNS TABLE(...) 而不是 OUT 参数,除非确实需要「函数体内直接给 OUT 变量赋值」的写法。返回结构写在 RETURNS 处更醒目。

  • 能定结构就不要用 SETOF record。类型检查提前到创建期,问题早暴露。

  • 参数顺序遵循「必填在前、可选在后」,天然规避默认值摆放限制。

  • 调用时优先使用命名参数,可读性显著提升,且对参数顺序调整免疫:

    sql
    SELECT app_find_orders(p_min => 100, p_customer => 'alice');
  • 公网/应用接口层的函数一律不用命名参数,避免参数名变更造成外部破坏;内部函数则可以放心用。

  • SRF 显式声明 ROWS,尤其是返回行数偏离 1000 明显的函数。

  • 返回行集而不是数组,除非集合很小且调用方需要整块(如 = ANY(arr))。

  • 把「写入 + 返回 id」封装成 sql 函数,用 RETURNING 一步到位,避免应用层两次往返:

    sql
    CREATE 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 报错。

下一节

继续学习:SQL 函数 vs PL/pgSQL 函数

参考链接 ​