1. Greenplum 里 FETCH 到底解决什么问题
如果你在 Greenplum 里写过存储过程或者做过大批量数据导出,大概率遇到过这种场景:一张表几千万行,直接SELECT *拉回来,客户端内存直接爆掉,或者网络传输卡到怀疑人生。这时候游标(CURSOR)就是那个“分批取数”的阀门,而FETCH就是拧开阀门的动作。
FETCH是 SQL 命令参考里专门用来从已声明游标中检索行的命令。它不负责创建游标,也不负责关闭游标,它只干一件事:把游标当前指向的那一行或那几行拿出来。在 Greenplum 中,游标位置只能向前移动,不支持滚动游标,所以FETCH的方向控制比标准 PostgreSQL 要收敛一些,但核心用法完全一致。
这篇文章面向的是已经在用 Greenplum 做数据处理、需要按批次读取结果集的开发或运维同学。我会把FETCH的语法骨架、游标声明配置、在 psql 里的验证动作,以及几个容易踩的坑一次性讲清楚。你跟着操作,就能在自己的库上跑通一套完整的“声明-取数-核对-关闭”流程。
需要说明的是,Greenplum 的FETCH和标准 SQL 的FETCH在嵌入式 SQL 里语义略有差异,但在交互式使用场景下,它返回的就是一个类似SELECT的结果集。这一点在后面的验证环节会体现得很明显。
2. 前置准备:连接方式与 TaoToken 接入配置
在开始写FETCH之前,得先有一个能连上 Greenplum 的客户端。你可以用psql,也可以用任何支持 PostgreSQL 协议的驱动。如果你是通过 API 方式做模型辅助生成 SQL 或做代码补全,可以先把 TaoToken 的接入配置准备好,这样在写游标逻辑时能直接让模型帮你补全语法。
TaoToken 的 API 地址是https://taotoken.net/api,官网入口在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。如果你只是想在写 SQL 时有个对话助手帮你解释FETCH的参数,可以直接用模型对话功能;如果是长期做 Greenplum 相关的编码和 Agent 任务,建议走 Coding Plan,额度更稳。
配置上,你需要在客户端里设置两个东西:一个是 API Key,一个是 Base URL。API Key 在控制台的 API Keys 页面生成,Base URL 填https://taotoken.net/api。如果你用的是 OpenAI 兼容的客户端,直接把这两项填进去就行。这一步不是必须的,但如果你想让模型帮你生成游标模板或者排查FETCH报错,提前配好会省很多事。
注意:TaoToken 只是模型接入层,不替代你的数据库客户端。Greenplum 的连接还是走你自己的
psql或驱动,两者不要混在一起理解。
3. 可复制的 FETCH 语法骨架与游标声明
3.1 完整语法结构
FETCH的基本形式是这样的:
FETCH [ forward_direction { FROM | IN } ] cursorname其中forward_direction可以是空,也可以是下面这些之一:
NEXT FIRST LAST ABSOLUTE count RELATIVE count count ALL FORWARD FORWARD count FORWARD ALL在 Greenplum 里,你只能向前取数,所以BACKWARD相关的方向是不支持的。FORWARD和NEXT等价,FORWARD count和直接写count等价。
3.2 游标声明与事务边界
游标必须在事务里声明,这是很多人第一次写会忘的地方。标准流程是:
BEGIN; DECLARE mycursor CURSOR FOR SELECT * FROM films; FETCH FORWARD 5 FROM mycursor; CLOSE mycursor; COMMIT;DECLARE负责定义游标并绑定查询,FETCH负责取数,CLOSE负责释放,COMMIT结束事务。如果你在BEGIN之前就DECLARE,Greenplum 会直接报错。
3.3 方向参数对照表
| 方向写法 | 含义 | Greenplum 是否支持 |
|---|---|---|
NEXT | 取下一行,默认值 | 支持 |
FIRST | 取第一行,仅第一次 FETCH 可用 | 支持 |
LAST | 取最后一行 | 支持 |
ABSOLUTE count | 取指定行,只能向前 | 支持 |
RELATIVE count | 相对当前位置向前取 | 支持 |
count | 取接下来 count 行 | 支持 |
ALL | 取剩余所有行 | 支持 |
FORWARD | 同 NEXT | 支持 |
FORWARD count | 向前取 count 行 | 支持 |
FORWARD ALL | 向前取所有剩余行 | 支持 |
BACKWARD | 向后取 | 不支持 |
FORWARD 0和RELATIVE 0是个特殊用法:它们不移动游标,只是重新获取当前行。如果游标已经在第一行之前或者最后一行之后,这个操作不会返回任何行。
3.4 一个可直接跑的模板
假设你有一张films表,结构里有code、title、did、date_prod、kind、len这些字段。下面这段可以直接复制到psql里执行:
BEGIN; DECLARE mycursor CURSOR FOR SELECT code, title, did, date_prod, kind, len FROM films ORDER BY code; FETCH FORWARD 5 FROM mycursor; CLOSE mycursor; COMMIT;执行完你会看到前 5 行数据以表格形式返回。psql不会显示FETCH 5这样的命令标签,而是直接把结果集画出来,这一点和文档里说的“命令标签在 psql 中不显示”是一致的。
4. 执行取数与结果集核对
4.1 分批取数的实际写法
如果你要处理的数据量很大,通常会写一个循环,每次FETCH一批。在 PL/pgSQL 里可以这样:
DO $$ DECLARE cur CURSOR FOR SELECT code, title FROM films ORDER BY code; rec RECORD; batch_size INT := 100; fetched INT := 0; BEGIN OPEN cur; LOOP FETCH FORWARD batch_size FROM cur INTO rec; EXIT WHEN NOT FOUND; fetched := fetched + 1; RAISE NOTICE '当前行: %, %', rec.code, rec.title; END LOOP; CLOSE cur; RAISE NOTICE '共处理 % 行', fetched; END $$;这里FETCH ... INTO是 PL/pgSQL 里的用法,和交互式FETCH返回结果集不同,它把行放进变量里。EXIT WHEN NOT FOUND是判断游标是否走到末尾的标准写法。
4.2 核对结果集是否完整
取完数之后,怎么确认没有漏行?一个简单办法是用COUNT(*)和游标取出的行数做对比:
-- 先看总行数 SELECT COUNT(*) FROM films; -- 再用游标取全部 BEGIN; DECLARE c CURSOR FOR SELECT * FROM films; FETCH ALL FROM c; CLOSE c; COMMIT;如果FETCH ALL返回的行数和COUNT(*)一致,说明取数完整。注意FETCH ALL执行后游标会停在最后一行之后,再执行FETCH不会返回任何行。
4.3 用 MOVE 做位置校验
MOVE和FETCH的区别是:MOVE只移动游标位置,不返回数据。你可以用它来验证游标位置:
BEGIN; DECLARE c CURSOR FOR SELECT * FROM films ORDER BY code; MOVE FORWARD 3 IN c; FETCH NEXT FROM c; CLOSE c; COMMIT;上面这段会跳过前 3 行,然后取第 4 行。如果你把MOVE换成FETCH FORWARD 3,效果是取回前 3 行并停在第 3 行,再FETCH NEXT取第 4 行。两者最终取到的行是一样的,区别在于中间有没有把数据传回来。
4.4 成功执行的返回特征
FETCH成功执行后,命令标签是FETCH count,count是实际读取的行数,可能是 0。在psql里你看到的是结果集,在驱动里你可以通过rowcount拿到这个数字。如果count是 0,说明游标已经越过了可用行范围,位置停在第一行之前或最后一行之后。
5. 本篇常见报错与排查
5.1 报错:cursor "xxx" does not exist
这个通常是因为DECLARE和FETCH不在同一个事务里,或者事务已经COMMIT了。游标的作用域是事务级的,事务一结束游标就没了。解决办法是把DECLARE、FETCH、CLOSE放在同一个BEGIN ... COMMIT块里。
5.2 报错:DECLARE CURSOR can only be used in transaction blocks
Greenplum 不允许在事务外声明游标。如果你在自动提交模式下直接写DECLARE,就会看到这个报错。先执行BEGIN;再声明即可。
5.3 报错:cursor can only scan forward
这是 Greenplum 和标准 PostgreSQL 的一个关键差异。Greenplum 不支持滚动游标,所以任何试图向后移动的操作都会失败。如果你写了FETCH BACKWARD或者FETCH ABSOLUTE -1这类语法,就会触发这个错误。检查你的方向参数,确保只用了FORWARD、NEXT、FIRST、LAST、ABSOLUTE(正数)、RELATIVE(正数)这些向前方向。
5.4 取数结果为空但表里有数据
先确认游标位置。如果你之前已经FETCH ALL过,游标停在最后一行之后,再FETCH自然返回空。另外检查DECLARE里的SELECT是否带了WHERE条件把数据过滤掉了。可以用MOVE 0重新获取当前行来确认位置,但注意MOVE 0在游标越界时也不会返回数据。
5.5 性能问题:ABSOLUTE 取数很慢
文档里明确说了,ABSOLUTE抓取不会比导航到该行更快,底层实现必须遍历所有中间行。如果你要取第 10000 行,用ABSOLUTE 10000和连续FETCH NEXT10000 次的代价差不多。大批量取数时,优先用FETCH FORWARD count顺序读取,不要用ABSOLUTE跳行。
5.6 通过游标更新数据不支持
Greenplum 不支持通过游标做UPDATE ... WHERE CURRENT OF。如果你需要更新,得先FETCH出主键,再用单独的UPDATE语句按主键更新。这一点在文档的 Notes 里也提到了,不要在这上面浪费时间。
6. 继续深入:相关命令与工具链
FETCH不是孤立存在的,它和DECLARE、CLOSE、MOVE是一套组合拳。DECLARE定义游标,MOVE调整位置不取数,FETCH取数,CLOSE释放资源。你在写存储过程时,基本就是这四个命令来回用。
如果你在写 SQL 的过程中需要快速查FETCH的参数含义,或者想让模型帮你生成一个带异常处理的游标模板,可以用 TaoToken 的模型对话功能,把报错信息贴进去让它帮你分析。地址是https://taotoken.net/api,模型对话入口在控制台里能找到。长期做 Greenplum 开发的话,Coding Plan 更适合高频调用场景,API Keys 页面可以管理你的密钥。
接入文档里有完整的 Base URL 配置和鉴权说明,遇到 401 或 404 先查文档里的请求示例。把FETCH的方向参数和事务边界这两点吃透,Greenplum 游标取数基本就不会再卡住你了。