盘点PostgreSQL C语言自定义函数返回数据集的各种方法

简介: 本文介绍在C语言中开发PostgreSQL用户自定义函数时,免去手动创建返回类型的四种方法。核心仍基于SPI查询和SRF返回数据,区别仅在于SQL中声明返回结构的方式。重点推荐`RETURNS TABLE`方式,一步到位、简洁直观,适合绝大多数场景;`CREATE TYPE+SETOF`适用于多函数复用结构;`OUT参数`风格类似TABLE;而`SETOF record`需调用时指定列,使用繁琐,建议仅用于临时测试。四种方法C代码完全相同,只需调整SQL注册语法,新手掌握`RETURNS TABLE`即可高效开发。

一、先搞懂核心变化

上篇用C语言开发PostgreSQL用户自定义函数之数据查询篇学的方法需要先手动CREATE TYPE定义返回类型,就像先给“快递盒”定制一个专属尺寸;本篇介绍几种更省事的新方法,基于一套C语言代码,不用提前创建返回类型,通过定义不同的参数类型、返回类型达到目的。

核心逻辑没变:还是用SPI查数据库,用SRF返回多行数据;唯一的区别是数据库里注册函数时的返回类型写法,C语言代码几乎不用改。

二、傻瓜式教程:免CREATE TYPE的完整实现

第一步:C语言代码

PG_FUNCTION_INFO_V1(get_all_empsalary2);

Datum
get_all_empsalary2(PG_FUNCTION_ARGS)
{
   
    ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
    Tuplestorestate *tupstore;
    TupleDesc result_tupdesc;    
    SPIPlanPtr plan;
    int spi_rc;    

    PG_TRY();
    {
   
        InitMaterializedSRF(fcinfo, 0);

        /* 连接到SPI管理器 */
        if (SPI_connect() != SPI_OK_CONNECT)
            elog(ERROR, "get_all_empsalary2: SPI_connect failed");

        /* 准备查询 */
        if ((plan = SPI_prepare("SELECT depname, empno, salary, enroll_date FROM "
                            "empsalary ORDER BY empno",
                            0, NULL)) == NULL)
            elog(ERROR, "get_all_empsalary2: SPI_prepare failed");

        /* 执行查询 */
        spi_rc = SPI_execute_plan(plan, NULL, NULL, true, 0);
        if (spi_rc != SPI_OK_SELECT)
            elog(ERROR, "get_all_empsalary2: SPI_execute_plan failed");

        /* 检查是否有数据 */
        if (SPI_processed == 0) {
   
            SPI_finish();
            PG_RETURN_NULL();
        }

        uint64 proc = SPI_processed;
        SPITupleTable *spi_tuptable = SPI_tuptable;
        TupleDesc spi_tupdesc = spi_tuptable->tupdesc;
        for (uint64 i = 0; i < proc; i++) {
   
            Datum values[4];
            bool nulls[4];            

            memset(values, 0, sizeof(values));
            memset(nulls, 0, sizeof(nulls));

            HeapTuple tuple = spi_tuptable->vals[i];
            char *p = SPI_getvalue(tuple, spi_tupdesc, 1);
            if(p != NULL)
            {
   
                //返回的是text类型,所以要用CStringGetTextDatum
                //这里可千万别用CStringGetDatum
                values[0] = CStringGetTextDatum(p);
                values[0] = CStringGetTextDatum(p);
            }                
            else
                nulls[0] = true;
            p = SPI_getvalue(tuple, spi_tupdesc, 2);
            if(p != NULL)
            {
   
                int32 empno = pg_strtoint32(p);
                values[1] = Int32GetDatum(empno);
            }
            else{
   
                nulls[1] = true;
            }

            p = SPI_getvalue(tuple, spi_tupdesc, 3);
            if(p != NULL)
            {
   
                int salary = pg_strtoint16(p);
                values[2] = Int16GetDatum(salary);
            }
            else
                nulls[2] = true;      
            p = SPI_getvalue(tuple, spi_tupdesc, 4);      
            if(p != NULL)
                values[3] = DirectFunctionCall1(date_in, CStringGetDatum(p));
            else
                nulls[3] = true;

            tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);           
        } 
        SPI_finish();       
    }
    PG_CATCH();
    {
   
        ereport(NOTICE, errmsg("catch error in acct_stat"));        
        PG_RE_THROW();
    }
    PG_END_TRY();    

    PG_RETURN_NULL();    
}

用到的新函数讲解:

  • InitMaterializedSRF
    这是一个辅助函数,用于构建在物化模式下单次调用场景中使用的集合返回函数的状态。此代码包含对 ReturnSetInfo 的完整性检查,创建与该函数一起使用的 Tuplestore和 TupleDesc,并将它们存储到函数的 ReturnSetInfo 中。
    可通过设置 "flags" 为 MAT_SRF_USE_EXPECTED_DESC,来使用来自 expectedDesc的元组描述符(即调用方期望的元组描述符)。也可设置 MAT_SRF_BLESS 以完善与元组描述符相关的信息,这在某些元组描述符来自瞬态 RECORD 数据类型的情况下是必需的。
  • tuplestore_putvalues
    这个就不解释了,就是把查询结果塞到tupstore里。

第二步:编译C代码(依然使用CMake)

cmake_minimum_required(VERSION 3.25)
project(spi_demo C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_BUILD_TYPE debug)

list(APPEND flags "-fPIC")

find_program(PG_CONFIG pg_config REQUIRED)

execute_process(COMMAND ${PG_CONFIG} --includedir-server
    OUTPUT_VARIABLE POSTGRESQL_INCLUDE_DIR
    OUTPUT_STRIP_TRAILING_WHITESPACE)

execute_process(COMMAND ${PG_CONFIG} --libdir
    OUTPUT_VARIABLE POSTGRESQL_LIB_DIR
    OUTPUT_STRIP_TRAILING_WHITESPACE)

execute_process(COMMAND ${PG_CONFIG} --libs
    OUTPUT_VARIABLE POSTGRESQL_LIBS
    OUTPUT_STRIP_TRAILING_WHITESPACE)

add_library(spi_demo SHARED spi_demo.c)

# 设置生成的共享库名称,去掉lib前缀
set_target_properties(spi_demo PROPERTIES PREFIX "")
# 最终so文件名
set_target_properties(spi_demo PROPERTIES OUTPUT_NAME spi_demo)

# 安装目录
install(TARGETS spi_demo LIBRARY DESTINATION ${POSTGRESQL_LIB_DIR})

target_compile_options(spi_demo PUBLIC "-fPIC" "-g3" "-O0")
target_link_options(spi_demo PUBLIC "-shared")
target_include_directories(spi_demo PUBLIC ${POSTGRESQL_INCLUDE_DIR})
target_link_directories(spi_demo PUBLIC ${POSTGRESQL_LIB_DIR})
cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=1
make

sudo cp spi_demo.so /usr/local/pgsql18/lib/spi_demo.1.0.so

第三步:数据库注册函数(关键区别在这!)

不用先CREATE TYPE了,直接在RETURNS table()里写清楚返回的列名和类型,一步到位:

-- 直接创建函数,无需提前建类型!
CREATE OR REPLACE FUNCTION get_all_empsalary2()
RETURNS table (
    depname text,    -- 列1:名称+类型
    empno int,       -- 列2:名称+类型(注意和SQL查询的类型匹配)
    salary int,      -- 列3:名称+类型
    enroll_date date -- 列4:名称+类型
)
AS '$libdir/spi_demo.1.0', 'get_all_empsalary2'  -- 第二个参数是C函数名
LANGUAGE C STRICT;

第四步:调用函数(和之前一样)

执行SQL就能看到结果,和手动查empsalary表完全一致:

SELECT * FROM get_all_empsalary2();

以上是第二种方法,还有别的方法吗?当然有,C语言代码不变,SQL代码如下:

CREATE OR REPLACE FUNCTION get_all_empsalary3()
RETURNS SETOF record  -- 核心:返回匿名记录集合
AS '$libdir/spi_demo.1.0', 'get_all_empsalary2'  -- C函数名对应
LANGUAGE C STRICT;

-- 格式:SELECT * FROM 函数名() AS 别名(列名1 类型1, 列名2 类型2,...)
SELECT * FROM get_all_empsalary3() AS emp(
    depname text,
    empno bigint,
    salary int,
    enroll_date date
);

以上是第三种方法:RETURNS SETOF record(匿名记录)然后用AS指定列。还有第四种吗?继续往下看:

CREATE OR REPLACE FUNCTION get_all_empsalary4(
    OUT depname text,    -- OUT参数:声明这是返回列
    OUT empno bigint,
    OUT salary int,
    OUT enroll_date date
)
RETURNS SETOF record  -- 搭配返回SETOF record
AS '$libdir/spi_demo.1.0', 'get_all_empsalary4'
LANGUAGE C STRICT;

-- 直接调用就行
SELECT * FROM get_all_empsalary4();

4种SRF返回数据集方法终极对比

一、先给核心结论

「CREATE TYPE+SETOF」「RETURNS TABLE」「SETOF record」「OUT参数+SETOF record」这4种方法,底层都是靠PostgreSQL的SRF(返回集合函数)机制逐行返回数据,C语言代码逻辑完全一致,唯一区别是「在SQL里声明函数返回列结构的方式」。下面用“人话+表格+实操例子”把差异讲透,帮你一眼选对。

二、4种方法核心对比(大白话表格)

对比维度 CREATE TYPE+SETOF RETURNS TABLE SETOF record(匿名) OUT参数+SETOF record
核心写法 1. 先建自定义类型
2. 函数返回SETOF 类型
函数直接写RETURNS TABLE(列名 类型,...) 函数返回SETOF record,调用时指定列 1. 函数加OUT参数声明列
2. 返回SETOF record
步骤数量 两步(建类型+建函数) 一步(直接建函数) 一步(建函数)+ 调用加列 一步(直接建函数)
列结构可见性 类型名和列结构分离,需查类型定义 列结构写在函数里,直观易读 列结构仅在调用时可见,函数里无信息 列结构写在参数里,直观易读
调用方式 简单:SELECT * FROM 函数() 简单:SELECT * FROM 函数() 复杂:SELECT * FROM 函数() AS 别名(列...) 简单:SELECT * FROM 函数()
复用性 高(一个类型可被多个函数复用) 低(列结构仅属于当前函数) 无(每次调用都要写列) 低(列结构仅属于当前函数)
新手友好度 ★★★☆☆(多一步建类型) ★★★★★(最优) ★★☆☆☆(调用易出错) ★★★★☆(和TABLE几乎一样)
典型适用场景 多个函数返回相同列结构(如统一的“员工薪资”结构) 单个函数专用列结构(90%的新手场景) 动态列结构/临时测试(极少用) 习惯用“参数”声明返回列(个人编码风格)

三、每种方法的实操代码(最简示例)

1. CREATE TYPE+SETOF(复用型)

-- 第一步:先建类型(可被多个函数复用)
CREATE TYPE empsalary_type AS (
    depname text,
    empno bigint,
    salary int,
    enroll_date date
);

-- 第二步:创建函数
CREATE OR REPLACE FUNCTION get_all_empsalary1()
RETURNS SETOF empsalary_type
AS '$libdir/spi_demo.1.0', 'get_all_empsalary1'
LANGUAGE C STRICT;

-- 调用
SELECT * FROM get_all_empsalary1();

2. RETURNS TABLE(新手首选)

-- 一步到位,列结构写在函数里
CREATE OR REPLACE FUNCTION get_all_empsalary2()
RETURNS table (
    depname text,
    empno bigint,
    salary int,
    enroll_date date
)
AS '$libdir/spi_demo.1.0', 'get_all_empsalary2'
LANGUAGE C STRICT;

-- 调用
SELECT * FROM get_all_empsalary2();

3. SETOF record(匿名型)

-- 仅声明返回匿名记录,无列信息
CREATE OR REPLACE FUNCTION get_all_empsalary3()
RETURNS SETOF record
AS '$libdir/spi_demo.1.0', 'get_all_empsalary3'
LANGUAGE C STRICT;

-- 调用(必须指定列,否则报错)
SELECT * FROM get_all_empsalary3() AS emp(
    depname text,
    empno bigint,
    salary int,
    enroll_date date
);

4. OUT参数+SETOF record(参数型)

-- 用OUT参数声明返回列,本质和TABLE一样
CREATE OR REPLACE FUNCTION get_all_empsalary4(
    OUT depname text,
    OUT empno bigint,
    OUT salary int,
    OUT enroll_date date
)
RETURNS SETOF record
AS '$libdir/spi_demo.1.0', 'get_all_empsalary4'
LANGUAGE C STRICT;

-- 调用
SELECT * FROM get_all_empsalary4();

👉 选方法的关键

  1. 是否复用列结构:只有CREATE TYPE+SETOF能复用列结构,其他3种都是“函数专属”;
  2. 调用是否额外操作:只有SETOF record需要在调用时指定列,其他3种直接调用即可;
  3. 写法风格差异RETURNS TABLE是“把列写在返回值里”,OUT参数是“把列写在参数里”,本质等价,仅编码风格不同。

新手选择建议(不用纠结)

  1. 优先选 RETURNS TABLE:一步到位、直观易读、调用简单,覆盖90%的场景(比如单个函数返回固定列结构);
  2. 次选 CREATE TYPE+SETOF:仅当多个函数需要返回相同列结构时用(比如5个函数都返回“员工薪资”4列),避免重复写列;
  3. OUT参数 作为备选:如果你习惯把返回列当成“输出参数”来写(比如有Java/C#编码习惯),可以用这种方式,和TABLE效果完全一样;
  4. SETOF record 尽量不用:调用时必须手动写列,容易写错,仅临时测试/动态列场景(比如返回列数不固定)才考虑。

总结

4种方法的核心是“换汤不换药”——C代码逻辑不变,只是SQL声明返回类型的方式不同。对新手来说,不用掌握所有方法,只要吃透RETURNS TABLE,就能应对几乎所有场景;其他方法只是不同场景下的“语法糖”而已。

相关文章
|
2天前
|
人工智能 JavaScript Linux
【Claude Code 全攻略】终端AI编程助手从入门到进阶(2026最新版)
Claude Code是Anthropic推出的终端原生AI编程助手,支持40+语言、200k超长上下文,无需切换IDE即可实现代码生成、调试、项目导航与自动化任务。本文详解其安装配置、四大核心功能及进阶技巧,助你全面提升开发效率,搭配GitHub Copilot使用更佳。
|
4天前
|
存储 人工智能 自然语言处理
OpenSpec技术规范+实例应用
OpenSpec 是面向 AI 智能体的轻量级规范驱动开发框架,通过“提案-审查-实施-归档”工作流,解决 AI 编程中的需求偏移与不可预测性问题。它以机器可读的规范为“单一真相源”,将模糊提示转化为可落地的工程实践,助力开发者高效构建稳定、可审计的生产级系统,实现从“凭感觉聊天”到“按规范开发”的跃迁。
744 12
|
4天前
|
消息中间件 人工智能 Kubernetes
阿里云云原生应用平台岗位急招,加入我们,打造 AI 最强基础设施
云原生应用平台作为中国最大云计算公司的基石,现全面转向 AI,打造 AI 时代最强基础设施。寻找热爱技术、具备工程极致追求的架构师、极客与算法专家,共同重构计算、定义未来。杭州、北京、深圳、上海热招中,让我们一起在云端,重构 AI 的未来。
|
8天前
|
存储 JavaScript 前端开发
JavaScript基础
本节讲解JavaScript基础核心知识:涵盖值类型与引用类型区别、typeof检测类型及局限性、===与==差异及应用场景、内置函数与对象、原型链五规则、属性查找机制、instanceof原理,以及this指向和箭头函数中this的绑定时机。重点突出类型判断、原型继承与this机制,助力深入理解JS面向对象机制。(238字)
|
7天前
|
云安全 人工智能 安全
阿里云2026云上安全健康体检正式开启
新年启程,来为云上环境做一次“深度体检”
1627 6
|
6天前
|
IDE 开发工具 C语言
【2026最新】VS2026下载安装使用保姆级教程(附安装包+图文步骤)
Visual Studio 2026是微软推出的最新Windows专属IDE,启动更快、内存占用更低,支持C++、Python等开发。推荐免费的Community版,安装简便,适合初学者与个人开发者使用。
803 11
|
7天前
|
人工智能 Shell 开发工具
Claude Code 2.1.2超详细更新说明,小白也能10分钟上手
Claude Code 2.1.x重磅更新:Shift+Enter换行、Esc+Esc撤销、Ctrl+B后台运行,Skills技能系统全面升级,支持多语言、通配符权限与动态MCP检测,性能提升50%,迭代速度惊人,开发者效率暴涨!
Claude Code 2.1.2超详细更新说明,小白也能10分钟上手
|
3天前
|
存储 人工智能 测试技术
【Claude Skills】从原理到实战的完全指南
Claude Skills通过模块化设计,将AI变为领域专家,实现工作流标准化。它支持指令封装、自动触发与脚本集成,提升复用性与协作效率,适用于个人提效与团队协同,是AI定制化的新范式。
|
3天前
|
人工智能 JavaScript 前端开发
【2026最新最全】一篇文章带你学会Cursor编程工具
本文介绍了Cursor的下载安装、账号注册、汉化设置、核心模式(Agent、Plan、Debug、Ask)及高阶功能,如@引用、@Doc文档库、@Browser自动化和Rules规则配置,助力开发者高效使用AI编程工具。
456 4