Skip to content

隐式游标(FOR IN) ​

PL/pgSQL 提供了两条「不用手写游标」的循环语句:FOR 记录变量 IN 查询 LOOP 与 FOR 记录变量 IN 游标 LOOP。它们内部会自动完成「打开 → 逐行取 → 在退出时关闭」的全部动作,编译器还替你把退出条件安排好了。本节讲清这两种 FOR ... IN 循环(社区常称「隐式游标」)的语义、它们与显式游标的取舍,以及几个容易被忽略的细节:循环变量只在循环内可见、FOUND 的行为、参数化游标的循环写法。

术语澄清

严格来说,PL/pgSQL 并没有一个叫「隐式游标」的类型。所谓「隐式游标(implicit cursor)」是业界对 FOR ... IN 查询 这种由系统自动管理游标的说法的沿用。PostgreSQL 官方文档把它归入 LOOP 控制结构。本节沿用这个通俗叫法,但内容以官方语义为准。

前置知识

阅读本节前,建议先了解:FETCH / MOVE / CLOSE

基础概念 ​

FOR ... IN 有两种数据来源,语义几乎一致,只是「结果从哪来」不同。

形式结果来源语法
查询循环一条 SELECT / VALUESFOR r IN SELECT ... LOOP
游标循环一个已声明的绑定游标FOR r IN 游标 [ ( 参数 ) ] LOOP

两者共同的特征:你只写循环体,取行与关闭由系统负责。

text
显式游标                            隐式游标(FOR ... IN)
──────────────────────             ──────────────────────
DECLARE cur CURSOR FOR ...         FOR r IN SELECT ... LOOP
OPEN cur;                              -- 循环体
LOOP                                   ...
  FETCH NEXT FROM cur INTO r;      END LOOP;
  EXIT WHEN NOT FOUND;
  ...
END LOOP;
CLOSE cur;

记录变量由系统隐式声明 ​

FOR 循环的循环变量不需要在 DECLARE 段声明:

sql
-- 正确:r 在 FOR 里就地声明
DO $$
BEGIN
    FOR r IN SELECT id, name FROM products ORDER BY id LOOP
        RAISE NOTICE '% - %', r.id, r.name;
    END LOOP;
END;
$$;
text
NOTICE:  1 - 机械键盘
NOTICE:  2 - 显示器
NOTICE:  3 - 鼠标垫
DO

这个变量:

性质说明
类型record(RECORD)
作用域仅限循环体,出了 END LOOP 就消失
是否可在 DECLARE 声明不需要,也不能重复声明同名
每轮是否重新赋值是,每轮指向当前行

退出条件自动生成 ​

显式游标要自己写 EXIT WHEN NOT FOUND;FOR ... IN 等价于:

text
LOOP
    FETCH 游标 INTO 记录变量;
    EXIT WHEN NOT FOUND;
    ... 循环体 ...
END LOOP;

也就是说,取不到行时自动退出,不需要你判断。这是它最省心的地方。

循环变量的可见性 ​

循环变量是「局部于循环」的。这一点在嵌套循环里尤其重要:

sql
DO $$
BEGIN
    FOR r IN SELECT id FROM products ORDER BY id LOOP
        RAISE NOTICE '外层 r.id = %', r.id;
    END LOOP;
    -- 这里无法访问 r,r 已超出作用域
END;
$$;

如果外层已经有一个同名变量,FOR 里的声明会遮蔽(shadow)它:

sql
DO $$
DECLARE
    r text := '外层变量';
BEGIN
    FOR r IN SELECT id FROM products ORDER BY id LIMIT 1 LOOP
        RAISE NOTICE '循环内 r 是记录: %', r.id;   -- 遮蔽了 text 的 r
    END LOOP;
    RAISE NOTICE '循环外 r 仍是: %', r;             -- 回到 text
END;
$$;
text
NOTICE:  循环内 r 是记录: 1
NOTICE:  循环外 r 仍是: 外层变量
DO

可以用已声明的变量做循环变量吗 ​

可以,但循环内会忽略它原有的定义。官方文档的原话是:

The variable recordvar is automatically defined as type record and exists only inside the loop (any existing definition of the variable name is ignored within the loop).

也就是说,FOR 循环变量总是被自动声明为 record 类型,与你在 DECLARE 里声明的同名变量无关;出了 END LOOP 之后,原有变量恢复可用。

sql
-- 可运行,但不推荐:预先声明的 r 在循环内被忽略
DO $$
DECLARE
    r record;                    -- 这个声明的 r 在循环内看不到
BEGIN
    FOR r IN SELECT id, name FROM products ORDER BY id LOOP
        RAISE NOTICE '循环内: % -> %', r.id, r.name;
    END LOOP;
END;
$$;
text
NOTICE:  循环内: 1 -> 机械键盘
NOTICE:  循环内: 2 -> 显示器
NOTICE:  循环内: 3 -> 鼠标垫
DO

既然原有定义会被忽略,那么「预先声明一个同名变量」只会让读者困惑,属于多余代码:

sql
-- 不推荐:DECLARE 里的 rec 纯属多余,且容易误以为能拿到循环外的值
DO $$
DECLARE
    rec record;
BEGIN
    FOR rec IN SELECT id, name FROM products LOOP
        RAISE NOTICE '%', rec.name;
    END LOOP;
    -- 这里 rec 若从未被赋值,仍为 NULL
    RAISE NOTICE '循环外 rec IS NULL = %', rec IS NULL;
END;
$$;
sql
-- 推荐:不预先声明,让循环变量干净地只在循环内存在
DO $$
BEGIN
    FOR rec IN SELECT id, name FROM products LOOP
        RAISE NOTICE '%', rec.name;
    END LOOP;
END;
$$;

一个真实会报错的变体

上文的「忽略」只发生在循环变量位置上。如果你声明的是一个 record 变量、却把它当成普通变量在循环体里当另一类型用,那是另一回事。真正会触发 ERROR: loop variable of loop over rows must be a record or row variable or list of scalar variables 的场景是:把已声明的标量变量(如 integer、text)直接放在循环变量位置上。

sql
-- 错误!n 是 integer,不能作为行循环的循环变量
DO $$
DECLARE
    n integer;
BEGIN
    FOR n IN SELECT id FROM products LOOP
        RAISE NOTICE '%', n;
    END LOOP;
END;
$$;
text
ERROR:  loop variable of loop over rows must be a record or row variable or list of scalar variables
LINE 5:     FOR n IN SELECT id FROM products LOOP
                ^
DETAIL:  Number of variables specified is not equal to the number of columns returned. (或者变量的类型不受支持)

要用标量承接,必须写成标量变量列表(见下一节),且列数与变量数一致。

也可以用标量变量列表 ​

除了记录变量,FOR 还支持「多个标量变量」的形式,功能上等价于把每列拆开:

sql
DO $$
BEGIN
    FOR v_id, v_name IN SELECT id, name FROM products ORDER BY id LOOP
        RAISE NOTICE '% -> %', v_id, v_name;
    END LOOP;
END;
$$;
text
NOTICE:  1 -> 机械键盘
NOTICE:  2 -> 显示器
NOTICE:  3 -> 鼠标垫
DO
循环变量形式写法说明
单个记录变量FOR r IN ...通过 r.列名 访问
多个标量变量FOR a, b, c IN ...列数须与变量数一致

查询循环 vs 游标循环 ​

对比项FOR r IN SELECT ...FOR r IN 游标 LOOP
查询何时确定循环开始时游标声明时
是否可传参直接写 WHERE 条件用游标形参
是否复用游标定义否是
是否自动开关游标是是
能否带 SCROLL不适用不适用(循环只向前)

一个实用结论:如果你已经有一个绑定游标,且需要反复用它遍历,就用游标循环;否则直接写查询循环。

语法与用法 ​

语法原型 ​

text
[ <<标签>> ]
FOR 目标 IN 查询 LOOP
    语句;
END LOOP [ 标签 ];

[ <<标签>> ]
FOR 目标 IN 绑定游标 [ ( 参数 [, ...] ) ] LOOP
    语句;
END LOOP [ 标签 ];

其中 目标 是「记录变量」或「标量变量列表」。

最小可运行示例 ​

sql
DO $$
BEGIN
    FOR r IN SELECT id, name FROM products ORDER BY id LOOP
        RAISE NOTICE '% - %', r.id, r.name;
    END LOOP;
END;
$$;
text
NOTICE:  1 - 机械键盘
NOTICE:  2 - 显示器
NOTICE:  3 - 鼠标垫
DO

游标循环示例 ​

sql
DO $$
DECLARE
    cur CURSOR FOR SELECT id, name FROM products ORDER BY id;
BEGIN
    FOR r IN cur LOOP
        RAISE NOTICE '% - %', r.id, r.name;
    END LOOP;
END;
$$;
text
NOTICE:  1 - 机械键盘
NOTICE:  2 - 显示器
NOTICE:  3 - 鼠标垫
DO

注意这里没有 OPEN 也没有 CLOSE——FOR 循环自己包办了。

带参游标的循环 ​

sql
DO $$
DECLARE
    cur CURSOR (min_price numeric) FOR
        SELECT name, price FROM products WHERE price >= min_price ORDER BY price;
BEGIN
    FOR r IN cur (500) LOOP
        RAISE NOTICE '% = %', r.name, r.price;
    END LOOP;
END;
$$;
text
NOTICE:  机械键盘 = 599.00
NOTICE:  显示器 = 1299.00
DO

参数写在游标名后的括号里,与 OPEN cur (500) 的写法一致。

循环内提前退出 ​

EXIT 与 CONTINUE 在 FOR ... IN 里照常可用:

sql
DO $$
BEGIN
    FOR r IN SELECT id, name FROM products ORDER BY id LOOP
        CONTINUE WHEN r.id = 2;          -- 跳过 id=2
        RAISE NOTICE '处理 %', r.name;
        EXIT WHEN r.id = 3;              -- 到 id=3 就停
    END LOOP;
END;
$$;
text
NOTICE:  处理 机械键盘
NOTICE:  处理 鼠标垫
DO

带标签的嵌套循环 ​

sql
DO $$
BEGIN
    <<outer_loop>>
    FOR r IN SELECT id FROM products ORDER BY id LOOP
        FOR s IN SELECT id FROM products ORDER BY id LOOP
            EXIT outer_loop WHEN r.id + s.id > 4;    -- 一次跳出两层
            RAISE NOTICE 'r=% s=%', r.id, s.id;
        END LOOP;
    END LOOP outer_loop;
END;
$$;
text
NOTICE:  r=1 s=1
NOTICE:  r=1 s=2
NOTICE:  r=1 s=3
NOTICE:  r=2 s=1
NOTICE:  r=2 s=2
DO

详细说明 ​

查询循环背后发生了什么 ​

FOR r IN SELECT ... LOOP 在语义上完全等价于:

text
DECLARE
    内部游标 refcursor;
BEGIN
    OPEN 内部游标 FOR SELECT ...;
    LOOP
        FETCH 内部游标 INTO r;
        EXIT WHEN NOT FOUND;
        ... 循环体 ...
    END LOOP;
    CLOSE 内部游标;      -- 正常退出与异常退出都会执行
END;

这个「等价图」解释了三条行为:

行为原因
循环结束游标必被关闭编译器在循环出口插入关闭逻辑
循环体抛异常也会关闭内部游标挂在子事务上,异常时一并释放
不能对循环用 FETCH PRIOR内部游标是向前读的,没有 SCROLL

FOUND 在循环里的行为 ​

FOUND 在 FOR ... IN 循环内外都有定义,但取值有讲究:

位置FOUND 的值
循环体内(每轮开始)true
循环正常结束后false
循环体里的 SELECT INTO / UPDATE 之后被那条语句覆盖
sql
DO $$
DECLARE
    dummy int;
BEGIN
    FOR r IN SELECT id FROM products ORDER BY id LOOP
        RAISE NOTICE '循环内 FOUND = %', FOUND;      -- true
        SELECT 1 INTO dummy;                          -- 覆盖了 FOUND
        RAISE NOTICE 'SELECT INTO 后 FOUND = %', FOUND;
    END LOOP;
    RAISE NOTICE '循环后 FOUND = %', FOUND;           -- false
END;
$$;
text
NOTICE:  循环内 FOUND = t
NOTICE:  SELECT INTO 后 FOUND = t
NOTICE:  循环内 FOUND = t
NOTICE:  SELECT INTO 后 FOUND = t
NOTICE:  循环内 FOUND = t
NOTICE:  SELECT INTO 后 FOUND = t
NOTICE:  循环后 FOUND = f
DO

在 FOR 循环体内不要依赖 FOUND,因为它会被循环体内的其他语句覆盖。循环本身不需要 FOUND——退出是自动的。

循环变量是只读的吗 ​

循环变量本质上是一个记录变量,你可以给它赋值,但这样做没有意义并且容易出错:

sql
-- 不推荐:改写循环变量
DO $$
BEGIN
    FOR r IN SELECT id, name FROM products ORDER BY id LOOP
        r.name := '被改了';        -- 允许,但下一轮会被覆盖
        RAISE NOTICE '%', r.id;
    END LOOP;
END;
$$;

改动只在本轮有效,FOR 下一轮会重新赋值。而且这个改动不会写回表——FOR ... IN 是只读遍历,没有 WHERE CURRENT OF 那种「当前行」概念。

循环查询里的变量替换 ​

FOR ... IN SELECT 里的查询,和绑定游标一样,会引用外层 PL/pgSQL 变量:

sql
DO $$
DECLARE
    min_price numeric := 500;
BEGIN
    FOR r IN SELECT name, price FROM products WHERE price >= min_price ORDER BY price LOOP
        RAISE NOTICE '% 超过阈值', r.name;
    END LOOP;
END;
$$;
text
NOTICE:  机械键盘 超过阈值
NOTICE:  显示器 超过阈值
DO

但要小心歧义:如果查询里的名字同时是「表列名」和「PL/pgSQL 变量名」,默认按列名解释。

sql
-- 陷阱:min_price 若也是列名,这里取的是列
DO $$
DECLARE
    min_price numeric := 500;
BEGIN
    FOR r IN SELECT name FROM products WHERE price >= min_price LOOP
        RAISE NOTICE '%', r.name;
    END LOOP;
END;
$$;

要强制用变量,可以给变量换个不会冲突的名字,或在 PL/pgSQL 里用 function_name.变量名 / 在查询里用参数化游标。最省事的做法是给 PL/pgSQL 变量加前缀(如 v_min_price)。

设计目标:为什么官方推荐它 ​

官方文档明确推荐:能用 FOR ... IN 就优先用它,而不是手写游标。理由是少犯三类错误:

手写游标易犯的错FOR ... IN 如何避免
忘记 OPEN不需要
忘记 CLOSE自动关闭
忘记 EXIT WHEN NOT FOUND自动退出

只有当你要显式控制读取位置(如 MOVE、FETCH PRIOR)或把游标返回给客户端时,才需要显式游标。

性能:隐式游标会不会更慢 ​

不会。FOR ... IN 内部就是一个门户,与手写游标在服务端的执行方式一致。唯一的差别是它总是向前扫描,因此不能带 SCROLL。

维度FOR ... IN手写游标
服务端机制门户门户
是否可向后否可(需 SCROLL)
是否可返回客户端否可
代码量少多

网络往返与分批 ​

FOR ... IN 一次取一行的模型意味着:如果循环体里做了额外查询(N+1 问题),性能会急剧下降。

sql
-- 不推荐:循环体内再查一次(N+1)
DO $$
BEGIN
    FOR o IN SELECT id FROM orders LOOP
        FOR d IN SELECT qty FROM order_items WHERE order_id = o.id LOOP
            RAISE NOTICE '订单 % 数量 %', o.id, d.qty;
        END LOOP;
    END LOOP;
END;
$$;
sql
-- 推荐:用一次连接把数据取全
DO $$
BEGIN
    FOR r IN
        SELECT o.id, d.qty
        FROM orders o
        JOIN order_items d ON d.order_id = o.id
        ORDER BY o.id
    LOOP
        RAISE NOTICE '订单 % 数量 %', r.id, r.qty;
    END LOOP;
END;
$$;
方式查询次数复杂度
嵌套循环1 + N可能 O(N²)
单次连接1一遍扫描

FOR ... IN 与 EXECUTE 结合 ​

查询需要动态构造时,可以配合 EXECUTE:

sql
DO $$
DECLARE
    sql text;
    r   record;
BEGIN
    sql := 'SELECT id, name FROM products WHERE price >= $1 ORDER BY id';
    FOR r IN EXECUTE sql USING 500 LOOP
        RAISE NOTICE '% -> %', r.id, r.name;
    END LOOP;
END;
$$;
text
NOTICE:  1 -> 机械键盘
NOTICE:  2 -> 显示器
DO

FOR ... IN EXECUTE 是官方支持的写法,等价于「动态查询 + 隐式游标」。它比 OPEN cur FOR EXECUTE 更省事。

循环变量的字段访问 ​

record 类型的循环变量支持 r.列名 访问,也支持 r.* 展开:

sql
DO $$
BEGIN
    FOR r IN SELECT id, name, price FROM products ORDER BY id LOOP
        RAISE NOTICE '整行: %', r;          -- 输出 (1,机械键盘,599.00)
        RAISE NOTICE '字段: %', r.name;
    END LOOP;
END;
$$;
text
NOTICE:  整行: (1,机械键盘,599.00)
NOTICE:  字段: 机械键盘
NOTICE:  整行: (2,显示器,1299.00)
NOTICE:  字段: 显示器
NOTICE:  整行: (3,鼠标垫,299.00)
NOTICE:  字段: 鼠标垫
DO

如果查询用 SELECT t.*,r 就包含整表的所有列。

遍历数组 vs 遍历查询 ​

FOR ... IN 循环专门用于「行集」。遍历数组要用 FOREACH:

sql
-- 遍历数组:用 FOREACH
DO $$
DECLARE
    nums int[] := ARRAY[10, 20, 30];
    n    int;
BEGIN
    FOREACH n IN ARRAY nums LOOP
        RAISE NOTICE '元素: %', n;
    END LOOP;
END;
$$;
text
NOTICE:  元素: 10
NOTICE:  元素: 20
NOTICE:  元素: 30
DO
需求用哪个
遍历查询结果行FOR r IN SELECT ...
遍历游标FOR r IN 游标
遍历数组元素FOREACH x IN ARRAY ...
遍历整数区间FOR i IN 1..10

空结果集时的行为 ​

如果查询返回 0 行,循环体一次也不执行:

sql
DO $$
DECLARE
    cnt int := 0;
BEGIN
    FOR r IN SELECT id FROM products WHERE price > 99999 LOOP
        cnt := cnt + 1;
    END LOOP;
    RAISE NOTICE '执行了 % 次', cnt;
END;
$$;
text
NOTICE:  执行了 0 次
DO

这和「SELECT INTO STRICT 无数据报错」不同:FOR ... IN 对空结果集是静默零次迭代。

游标循环不能带 INSENSITIVE 等选项 ​

游标循环只能对一个已存在的绑定游标做遍历,不能内联声明 SCROLL / NO SCROLL:

sql
-- 错误!循环里不能重新声明游标选项
DO $$
BEGIN
    FOR r IN SCROLL CURSOR FOR SELECT 1 LOOP     -- 语法错误
        RAISE NOTICE '%', r;
    END LOOP;
END;
$$;
text
ERROR:  syntax error at or near "SCROLL"

要使用循环遍历游标,游标必须先在 DECLARE 里声明好。

带标签退出:多层循环的正确退出方式 ​

嵌套 FOR ... IN 时,退出外层循环需要标签:

sql
DO $$
BEGIN
    <<outer>>
    FOR a IN SELECT id FROM products ORDER BY id LOOP
        FOR b IN SELECT id FROM products ORDER BY id LOOP
            IF a.id = 1 AND b.id = 2 THEN
                EXIT outer;              -- 直接跳出两层
            END IF;
            RAISE NOTICE 'a=% b=%', a.id, b.id;
        END LOOP;
    END LOOP outer;
END;
$$;
text
NOTICE:  a=1 b=1
DO
写法退出层级
EXIT;最内层循环
EXIT 标签;标签标识的那层循环
CONTINUE;跳过本轮(最内层)
CONTINUE 标签;跳到标签那层的下一轮

实战示例 ​

示例一:遍历并汇总 ​

需求:统计商品总价值(价格之和)。

sql
DO $$
DECLARE
    total numeric := 0;
BEGIN
    FOR r IN SELECT price FROM products ORDER BY id LOOP
        total := total + r.price;
    END LOOP;
    RAISE NOTICE '总价值 = %', total;
END;
$$;
text
NOTICE:  总价值 = 2197.00
DO

示例二:带参游标循环 ​

需求:遍历价格不低于给定值的商品,打印名称和价格。

sql
DO $$
DECLARE
    cur CURSOR (p_min numeric) FOR
        SELECT name, price FROM products WHERE price >= p_min ORDER BY price;
BEGIN
    FOR r IN cur (300) LOOP
        RAISE NOTICE '%: %', r.name, r.price;
    END LOOP;
END;
$$;
text
NOTICE:  机械键盘: 599.00
NOTICE:  显示器: 1299.00
DO

示例三:嵌套循环 + 标签退出 ​

需求:从订单与明细两张表里找出「同一订单有多条明细」的前若干组合,遇到条件即停。

sql
DO $$
BEGIN
    <<outer_loop>>
    FOR o IN SELECT id FROM orders ORDER BY id LOOP
        FOR d IN SELECT id, qty FROM order_items WHERE order_id = o.id ORDER BY id LOOP
            RAISE NOTICE '订单 % 明细 % 数量 %', o.id, d.id, d.qty;
            EXIT outer_loop WHEN d.qty > 10;
        END LOOP;
    END LOOP outer_loop;
END;
$$;
text
NOTICE:  订单 1001 明细 1 数量 3
NOTICE:  订单 1001 明细 2 数量 15
DO

示例四:动态 SQL + 隐式游标 ​

需求:按传入的最小价格动态构造查询并遍历。

sql
CREATE OR REPLACE FUNCTION list_expensive(p_min numeric)
RETURNS int AS $$
DECLARE
    sql text := 'SELECT name, price FROM products WHERE price >= $1 ORDER BY price';
    n   int := 0;
    r   record;
BEGIN
    FOR r IN EXECUTE sql USING p_min LOOP
        RAISE NOTICE '% 卖 %', r.name, r.price;
        n := n + 1;
    END LOOP;
    RETURN n;
END;
$$ LANGUAGE plpgsql;

SELECT list_expensive(500);
text
NOTICE:  机械键盘 卖 599.00
NOTICE:  显示器 卖 1299.00
 list_expensive
----------------
              2
(1 row)

示例五:用循环建汇总表 ​

需求:按固定分档统计每个价格档的商品数量。

sql
CREATE TABLE IF NOT EXISTS price_buckets (
    label text PRIMARY KEY,
    cnt   int
);

DO $$
DECLARE
    bucket record;
BEGIN
    FOR bucket IN
        SELECT '低价' AS label, count(*) AS cnt FROM products WHERE price < 500
        UNION ALL
        SELECT '中价', count(*) FROM products WHERE price >= 500 AND price < 1000
        UNION ALL
        SELECT '高价', count(*) FROM products WHERE price >= 1000
    LOOP
        INSERT INTO price_buckets VALUES (bucket.label, bucket.cnt)
        ON CONFLICT (label) DO UPDATE SET cnt = EXCLUDED.cnt;
        RAISE NOTICE '%: %', bucket.label, bucket.cnt;
    END LOOP;
END;
$$;
text
NOTICE:  低价: 1
NOTICE:  中价: 1
NOTICE:  高价: 1
DO

注意事项 ​

  • 循环变量无需声明,也不能与 DECLARE 段已有变量同名。
  • 循环变量只在循环体内可见;出了 END LOOP 失效。
  • 循环变量赋值不影响表数据,FOR ... IN 是只读遍历。
  • 退出是自动的,不要在循环里写 EXIT WHEN NOT FOUND(FOUND 会被循环体语句覆盖)。
  • FOR ... IN 只向前读,不支持 SCROLL。
  • 嵌套循环退出外层要用标签。
  • 循环内不要在 FOR 上叠 N+1 查询,优先用连接。
  • 变量与列名同名时按列名解释,给变量加前缀规避。
错误写法原因正确做法
DECLARE r record; 后又 FOR r IN ...变量重复循环变量不预先声明,或改用别的名字
循环里写 EXIT WHEN NOT FOUNDFOUND 已被覆盖,逻辑错乱依赖自动退出
FOR r IN cur,其中 cur 未声明为游标编译错误先在 DECLARE 声明绑定游标
FOR r IN SCROLL CURSOR FOR ...语法错误游标须事先声明
期望循环变量的修改写回表FOR ... IN 只读用游标 + WHERE CURRENT OF 或 UPDATE

最佳实践 ​

  • 默认用 FOR r IN SELECT ... LOOP,只在需要复用游标定义或控制位置时才用显式游标。
  • 给 PL/pgSQL 变量加前缀(v_)以避免与列名混淆。
  • 避免循环内再查(N+1):用 JOIN / 聚合一次取全。
  • 多层循环用标签,让 EXIT / CONTINUE 意图明确。
  • 需要向后读、需要返回游标给客户端时,改用显式游标。
  • 遍历数组用 FOREACH、遍历区间用 FOR i IN 1..n,不要硬套查询循环。
场景推荐反例
遍历查询结果FOR r IN SELECT ...手写 OPEN/FETCH/CLOSE
复用游标定义FOR r IN 游标 (参数)每次重写 SELECT
需要向后读显式 SCROLL 游标用 FOR 循环(做不到)
返回结果给客户端RETURNS refcursor用 FOR 循环收集再返回
遍历数组FOREACHFOR r IN SELECT unnest(...)
sql
-- 推荐:简洁、自动管理
FOR r IN SELECT id, name FROM products ORDER BY id LOOP
    RAISE NOTICE '% - %', r.id, r.name;
END LOOP;

-- 不推荐:手写一切
OPEN cur;
LOOP
    FETCH cur INTO r;
    EXIT WHEN NOT FOUND;
    RAISE NOTICE '% - %', r.id, r.name;
END LOOP;
CLOSE cur;

下一节

下一节我们学习:异常处理(EXCEPTION),理解 PL/pgSQL 如何用 BEGIN ... EXCEPTION ... END 捕获错误、如何用子事务保护部分逻辑,以及异常块对已执行变更的回滚边界。

参考链接 ​