PL/pgSQL 入门教程(四):使用游标(cursor)

简介: 游标是PostgreSQL中“按需取数”的数据指针,避免大查询内存溢出;支持逐行处理、动态查询、精准更新/删除及函数返回大结果集。分未绑定(灵活)与绑定(固定)两类,核心操作为声明→打开→FETCH/MOVE/UPDATE→关闭,FOR循环可自动简化遍历。

一、什么是游标?为什么要用游标?

1.1 游标的本质

你可以把游标理解为 PostgreSQL 中的“数据指针”——它不像普通 SELECT 语句那样一次性把所有查询结果加载到内存,而是像翻书一样,每次只读取一行或几行数据,还能自由控制读取的方向(向前、向后)和位置。

1.2 游标能解决什么问题?

  • 避免内存溢出:查询几十万、几百万行数据时,一次性加载会占满内存,游标逐行读取可规避这个问题;
  • 逐行处理数据:需要对每行数据做个性化处理(比如调用函数、复杂计算、条件更新)时,游标能精准操作每一行;
  • 函数返回大结果集:PL/pgSQL 函数无法直接返回超大结果集,游标可以作为“桥梁”,让调用方按需读取数据;
  • 精准更新/删除指定行:通过游标定位到某一行后,可直接更新或删除这一行。

小提醒:PL/pgSQL 的 FOR 循环会自动用游标,日常简单遍历不用手动写游标,但复杂场景(比如动态查询、返回结果集)必须手动操作。

二、游标基础:变量与分类

2.1 游标变量的类型

所有游标都基于 refcursor 类型的变量,这是 PostgreSQL 专门为游标设计的特殊类型,你可以把它理解为“游标身份证”。

2.2 游标的两种分类

类型 特点 适用场景
未绑定游标 声明时不指定查询,打开时再定义 动态查询(比如表名、条件可变)
绑定游标 声明时就绑定固定查询(可带参数) 固定逻辑的重复查询

三、游标核心操作:声明 → 打开 → 使用 → 关闭

3.1 第一步:声明游标变量

声明有两种方式,对应两种游标类型:

方式1:声明未绑定游标(最灵活)

直接声明 refcursor 类型变量,后续可绑定任意查询:

DECLARE
    cur_unbound refcursor; -- 未绑定游标,无固定查询

方式2:声明绑定游标(固定查询)

声明时直接绑定查询,支持带参数,语法:

name [ [ NO ] SCROLL ] CURSOR [ (参数名 类型) ] FOR 查询语句;
  • SCROLL:允许游标向后读取(比如读上一行、最后一行);
  • NO SCROLL:禁止向后读取,仅能向前;
  • 参数:查询中可使用的变量,打开时传值。

示例:

DECLARE
    -- 绑定固定查询(无参数):查询用户表所有数据
    cur_bind1 CURSOR FOR SELECT id, name FROM t_user;

    -- 绑定参数化查询:按用户ID范围查询
    cur_bind2 CURSOR (min_id int, max_id int) FOR 
        SELECT id, name FROM t_user WHERE id BETWEEN min_id AND max_id;

3.2 第二步:打开游标

游标必须“打开”才能用,相当于给游标“加载查询逻辑”。PL/pgSQL 有3种打开方式,对应不同场景:

方式1:打开未绑定游标(固定查询)

语法:

OPEN 未绑定游标名 [ SCROLL | NO SCROLL ] FOR 查询语句;

示例:

-- 给未绑定游标绑定“查询订单表”的逻辑
OPEN cur_unbound FOR SELECT order_id, amount FROM t_order WHERE status = 'paid';

方式2:打开未绑定游标(动态查询)

如果查询的表名、字段名是变量(比如用户传入),用 EXECUTE 动态拼接查询:
语法:

OPEN 未绑定游标名 FOR EXECUTE 动态查询字符串 [ USING 参数1, 参数2... ];

示例:

DECLARE
    tab_name text := 't_order'; -- 动态表名
    status_val text := 'paid';  -- 查询条件参数
BEGIN
    -- 拼接查询语句(%I 避免表名/字段名注入),USING 传参数(避免SQL注入)
    OPEN cur_unbound FOR EXECUTE 
        format('SELECT order_id, amount FROM %I WHERE status = $1', tab_name)
        USING status_val;
END;

方式3:打开绑定游标

绑定游标声明时已有查询,打开时只需传参数(如果有):
语法:

OPEN 绑定游标名 [ (参数值1, 参数值2...) ];
-- 或命名参数(更清晰)
OPEN 绑定游标名 (参数名 := 参数值);

示例:

-- 打开无参数的绑定游标
OPEN cur_bind1;

-- 打开带参数的绑定游标(两种传参方式)
OPEN cur_bind2(1, 100); -- 位置传参:min_id=1,max_id=100
-- OPEN cur_bind2(min_id := 1, max_id := 100); -- 命名传参(推荐)

3.3 第三步:使用游标(核心操作)

打开游标后,可通过 FETCHMOVEUPDATE/DELETE 操作数据:

1. FETCH:读取游标数据

最常用的操作,把游标指向的行读取到变量中。
语法:

FETCH [ 方向 ] FROM 游标名 INTO 变量1 [, 变量2...];
  • 方向可选:NEXT(下一行,默认)、PRIOR(上一行)、FIRST(第一行)、LAST(最后一行)、RELATIVE n(相对当前位置移动n行);
  • 变量:可以是行变量、记录变量,或多个普通变量(与查询字段一一对应)。

示例:

DECLARE
    cur_unbound refcursor;
    v_order_id int;
    v_amount numeric;
BEGIN
    -- 打开游标
    OPEN cur_unbound FOR SELECT order_id, amount FROM t_order WHERE status = 'paid';

    -- 读取第一行
    FETCH NEXT FROM cur_unbound INTO v_order_id, v_amount;
    RAISE NOTICE '第一行订单:ID=%,金额=%', v_order_id, v_amount;

    -- 读取上一行(需游标声明/打开时加SCROLL)
    FETCH PRIOR FROM cur_unbound INTO v_order_id, v_amount;
    RAISE NOTICE '回到上一行:ID=%,金额=%', v_order_id, v_amount;
END;

2. MOVE:移动游标位置(不读取数据)

只想调整游标位置,不想读取数据时用 MOVE,语法和 FETCH 几乎一样:

MOVE [ 方向 ] FROM 游标名;

示例:

-- 把游标移动到最后一行(不读取数据)
MOVE LAST FROM cur_unbound;
-- 从最后一行向前移动2行
MOVE RELATIVE -2 FROM cur_unbound;

3. UPDATE/DELETE:修改游标当前行

游标定位到某一行后,可直接修改/删除这一行,精准度极高:
语法:

-- 更新当前行
UPDATE 表名 SET 字段 = 值 WHERE CURRENT OF 游标名;
-- 删除当前行
DELETE FROM 表名 WHERE CURRENT OF 游标名;

示例:

DECLARE
    cur_unbound refcursor;
    v_order_id int;
    v_amount numeric;
BEGIN
    -- 打开游标(加FOR UPDATE锁定行,避免并发修改)
    OPEN cur_unbound FOR SELECT order_id, amount FROM t_order WHERE id = 100 FOR UPDATE;

    -- 读取目标行
    FETCH NEXT FROM cur_unbound INTO v_order_id, v_amount;

    -- 更新当前行的金额
    UPDATE t_order SET amount = v_amount * 0.9 WHERE CURRENT OF cur_unbound;
END;

3.4 第四步:关闭游标

用完游标后必须关闭,释放服务器资源(门户/portal):
语法:

CLOSE 游标名;

示例:

CLOSE cur_unbound;

四、简化操作:FOR循环自动遍历游标

如果只是简单遍历游标结果,不用手动写 OPEN/FETCH/CLOSE,PL/pgSQL 的 FOR 循环会自动处理,这是日常最常用的方式。

语法:

[ <<循环标签>> ]
FOR 记录变量 IN 绑定游标名 [ (参数值) ] LOOP
    -- 循环体:处理每一行数据
END LOOP [ 循环标签 ];
  • 记录变量:自动创建 record 类型变量,无需提前声明;
  • 游标会自动打开,循环结束后自动关闭。

示例:遍历用户表数据

CREATE OR REPLACE FUNCTION fn_loop_cursor()
RETURNS void AS $$
DECLARE
    -- 声明绑定游标:查询ID<10的用户
    cur_user CURSOR (max_id int) FOR SELECT id, name FROM t_user WHERE id < max_id;
    v_user record; -- 接收每行数据的记录变量
BEGIN
    -- FOR循环自动打开游标,遍历完自动关闭
    FOR v_user IN cur_user(10) LOOP
        RAISE NOTICE '用户ID:%,姓名:%', v_user.id, v_user.name;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

-- 调用函数
SELECT fn_loop_cursor();

五、高级用法:函数返回游标(返回大结果集)

PL/pgSQL 函数无法直接返回几十万行数据,但可以返回游标,让调用方按需读取。注意:游标依赖事务,必须在事务内调用!

场景1:返回单个游标(自动生成游标名)

-- 1. 创建测试表
CREATE TABLE t_product (id int, name text, price numeric);
INSERT INTO t_product VALUES (1, '手机', 2999), (2, '电脑', 5999);

-- 2. 创建返回游标的函数
CREATE OR REPLACE FUNCTION fn_return_cursor()
RETURNS refcursor AS $$
DECLARE
    cur_prod refcursor; -- 未绑定游标
BEGIN
    -- 打开游标(自动生成游标名)
    OPEN cur_prod FOR SELECT id, name, price FROM t_product;
    RETURN cur_prod; -- 返回游标名
END;
$$ LANGUAGE plpgsql;

-- 3. 调用函数(必须在事务内)
BEGIN;
    -- 获取游标名(比如返回 <unnamed cursor 1>)
    SELECT fn_return_cursor();
    -- 读取游标数据
    FETCH ALL IN "<unnamed cursor 1>";
COMMIT; -- 事务结束,游标自动关闭

场景2:返回多个游标(同时返回多张表数据)

CREATE OR REPLACE FUNCTION fn_return_multi_cursor(cur1 refcursor, cur2 refcursor)
RETURNS SETOF refcursor AS $$
BEGIN
    -- 第一个游标:返回用户表数据
    OPEN cur1 FOR SELECT id, name FROM t_user;
    RETURN NEXT cur1;
    
    -- 第二个游标:返回订单表数据
    OPEN cur2 FOR SELECT order_id, amount FROM t_order;
    RETURN NEXT cur2;
END;
$$ LANGUAGE plpgsql;

-- 调用(事务内)
BEGIN;
    -- 指定两个游标名:cur_user、cur_order
    SELECT * FROM fn_return_multi_cursor('cur_user', 'cur_order');

    -- 读取第一个游标
    FETCH ALL FROM cur_user;
    -- 读取第二个游标
    FETCH ALL FROM cur_order;
COMMIT;

六、实战案例:游标解决实际问题

注意:以下案例只是举例说明游标的用法,请勿直接用于生产环境!!!

案例1:逐行处理订单,计算优惠金额

需求:对所有“未支付”的订单,按金额计算优惠(>1000减100,否则减50),更新到表中。
```plpgsql
CREATE OR REPLACE FUNCTION fn_calc_discount()
RETURNS void AS $$ DECLARE cur_order refcursor; v_order record; v_discount numeric; BEGIN -- 打开游标:查询未支付订单 OPEN cur_order FOR SELECT id, amount FROM t_order WHERE status = 'unpaid'; -- 循环读取每一行 LOOP -- 读取下一行,无数据则退出循环 FETCH NEXT FROM cur_order INTO v_order; EXIT WHEN NOT FOUND; -- 计算优惠 IF v_order.amount > 1000 THEN v_discount := 100; ELSE v_discount := 50; END IF; -- 更新当前订单的优惠金额 UPDATE t_order SET discount = v_discount, final_amount = amount - v_discount WHERE CURRENT OF cur_order; END LOOP; -- 关闭游标 CLOSE cur_order; RAISE NOTICE '优惠计算完成'; END; $$ LANGUAGE plpgsql;

-- 调用函数
SELECT fn_calc_discount();


### 案例2:动态游标查询不同表的数据
需求:根据传入的表名,查询该表的前10条数据(动态表名用游标实现)。
```plpgsql
CREATE OR REPLACE FUNCTION fn_dynamic_cursor(tab_name text, OUT result refcursor)
RETURNS refcursor AS $$
BEGIN
    -- 打开动态游标:拼接表名,返回前10行
    OPEN result FOR EXECUTE 
        format('SELECT * FROM %I LIMIT 10', tab_name);
END;
$$ LANGUAGE plpgsql;

-- 调用(查询t_user表)
BEGIN;
    SELECT fn_dynamic_cursor('t_user');
    FETCH ALL IN "<unnamed cursor 1>";
COMMIT;

案例3:批量更新大数据集

需求:更新100万行用户数据,一次性更新会锁表,用游标分批更新(每次更1000行)。

CREATE OR REPLACE FUNCTION fn_batch_update()
RETURNS void AS $$
DECLARE
    cur_user CURSOR FOR SELECT id FROM t_user WHERE update_flag = 'N';
    v_id int;
    v_count int := 0;
BEGIN
    OPEN cur_user;
    LOOP
        -- 读取一行
        FETCH NEXT FROM cur_user INTO v_id;
        EXIT WHEN NOT FOUND;
        
        -- 批量更新:每1000行提交一次
        UPDATE t_user SET update_flag = 'Y' WHERE id = v_id;
        v_count := v_count + 1;
        IF v_count % 1000 = 0 THEN
            COMMIT;
            RAISE NOTICE '已更新%行', v_count;
        END IF;
    END LOOP;
    
    -- 提交剩余数据
    COMMIT;
    CLOSE cur_user;
    RAISE NOTICE '批量更新完成,总计更新%行', v_count;
END;
$$ LANGUAGE plpgsql;

-- 调用函数
SELECT fn_batch_update();
相关文章
|
6天前
|
JSON API 数据格式
OpenCode入门使用教程
本教程介绍如何通过安装OpenCode并配置Canopy Wave API来使用开源模型。首先全局安装OpenCode,然后设置API密钥并创建配置文件,最后在控制台中连接模型并开始交互。
2829 6
|
12天前
|
人工智能 JavaScript Linux
【Claude Code 全攻略】终端AI编程助手从入门到进阶(2026最新版)
Claude Code是Anthropic推出的终端原生AI编程助手,支持40+语言、200k超长上下文,无需切换IDE即可实现代码生成、调试、项目导航与自动化任务。本文详解其安装配置、四大核心功能及进阶技巧,助你全面提升开发效率,搭配GitHub Copilot使用更佳。
|
3天前
|
人工智能 API 开发者
Claude Code 国内保姆级使用指南:实测 GLM-4.7 与 Claude Opus 4.5 全方案解
Claude Code是Anthropic推出的编程AI代理工具。2026年国内开发者可通过配置`ANTHROPIC_BASE_URL`实现本地化接入:①极速平替——用Qwen Code v0.5.0或GLM-4.7,毫秒响应,适合日常编码;②满血原版——经灵芽API中转调用Claude Opus 4.5,胜任复杂架构与深度推理。
|
14天前
|
存储 人工智能 自然语言处理
OpenSpec技术规范+实例应用
OpenSpec 是面向 AI 智能体的轻量级规范驱动开发框架,通过“提案-审查-实施-归档”工作流,解决 AI 编程中的需求偏移与不可预测性问题。它以机器可读的规范为“单一真相源”,将模糊提示转化为可落地的工程实践,助力开发者高效构建稳定、可审计的生产级系统,实现从“凭感觉聊天”到“按规范开发”的跃迁。
2148 18
|
7天前
|
人工智能 前端开发 Docker
Huobao Drama 开源短剧生成平台:从剧本到视频
Huobao Drama 是一个基于 Go + Vue3 的开源 AI 短剧自动化生成平台,支持剧本解析、角色与分镜生成、图生视频及剪辑合成,覆盖短剧生产全链路。内置角色管理、分镜设计、视频合成、任务追踪等功能,支持本地部署与多模型接入(如 OpenAI、Ollama、火山等),搭配 FFmpeg 实现高效视频处理,适用于短剧工作流验证与自建 AI 创作后台。
1035 4
|
5天前
|
人工智能 运维 前端开发
Claude Code 30k+ star官方插件,小白也能写专业级代码
Superpowers是Claude Code官方插件,由核心开发者Jesse打造,上线3个月获3万star。它集成brainstorming、TDD、系统化调试等专业开发流程,让AI写代码更规范高效。开源免费,安装简单,实测显著提升开发质量与效率,值得开发者尝试。
|
17天前
|
人工智能 测试技术 开发者
AI Coding后端开发实战:解锁AI辅助编程新范式
本文系统阐述了AI时代开发者如何高效协作AI Coding工具,强调破除认知误区、构建个人上下文管理体系,并精准判断AI输出质量。通过实战流程与案例,助力开发者实现从编码到架构思维的跃迁,成为人机协同的“超级开发者”。
1211 100
|
12天前
|
人工智能 JSON 自然语言处理
【2026最新最全】一篇文章带你学会Qoder编辑器
Qoder是一款面向程序员的AI编程助手,集智能补全、对话式编程、项目级理解、任务模式与规则驱动于一体,支持模型分级选择与CLI命令行操作,可自动生成文档、优化提示词,提升开发效率。
963 10
【2026最新最全】一篇文章带你学会Qoder编辑器