2024年9月26日: PostgreSQL 17 发布!
支持版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4

34.7. 使用描述符区域 #

SQL 描述符区域是一种更复杂的方法,用于处理 SELECTFETCHDESCRIBE 语句的结果。SQL 描述符区域将一行数据的数据与元数据项组合到一个数据结构中。元数据在执行动态 SQL 语句时特别有用,因为结果列的性质可能事先未知。PostgreSQL 提供两种使用描述符区域的方法:命名 SQL 描述符区域和 C 结构 SQLDA。

34.7.1. 命名 SQL 描述符区域 #

命名 SQL 描述符区域由一个头组成,其中包含有关整个描述符的信息,以及一个或多个项目描述符区域,每个区域基本上描述结果行中的一列。

在可以使用 SQL 描述符区域之前,需要分配一个

EXEC SQL ALLOCATE DESCRIPTOR identifier;

标识符用作描述符区域的“变量名”。当不再需要描述符时,应将其释放

EXEC SQL DEALLOCATE DESCRIPTOR identifier;

要使用描述符区域,请将其指定为 INTO 子句中的存储目标,而不是列出主机变量

EXEC SQL FETCH NEXT FROM mycursor INTO SQL DESCRIPTOR mydesc;

如果结果集为空,则描述符区域仍将包含查询的元数据,即字段名称。

对于尚未执行的准备好的查询,可以使用 DESCRIBE 语句获取结果集的元数据

EXEC SQL BEGIN DECLARE SECTION;
char *sql_stmt = "SELECT * FROM table1";
EXEC SQL END DECLARE SECTION;

EXEC SQL PREPARE stmt1 FROM :sql_stmt;
EXEC SQL DESCRIBE stmt1 INTO SQL DESCRIPTOR mydesc;

在 PostgreSQL 9.0 之前,SQL 关键字是可选的,因此使用 DESCRIPTORSQL DESCRIPTOR 会生成命名 SQL 描述符区域。现在它是强制性的,省略 SQL 关键字会生成 SQLDA 描述符区域,请参见第 34.7.2 节

DESCRIBEFETCH 语句中,INTOUSING 关键字可以类似地使用:它们会在描述符区域中生成结果集和元数据。

现在如何从描述符区域中获取数据?您可以将描述符区域视为具有命名字段的结构。要检索头中字段的值并将其存储到主机变量中,请使用以下命令

EXEC SQL GET DESCRIPTOR name :hostvar = field;

目前,仅定义了一个头字段:COUNT,它指示存在多少个项目描述符区域(即结果中包含多少列)。主机变量需要是整数类型。要从项目描述符区域获取字段,请使用以下命令

EXEC SQL GET DESCRIPTOR name VALUE num :hostvar = field;

num 可以是文字整数或包含整数的主机变量。可能的字段是

CARDINALITY(整数) #

结果集中的行数

DATA #

实际数据项(因此,此字段的数据类型取决于查询)

DATETIME_INTERVAL_CODE(整数) #

TYPE9 时,DATETIME_INTERVAL_CODE 的值为 1 表示 DATE2 表示 TIME3 表示 TIMESTAMP4 表示 TIME WITH TIME ZONE,或 5 表示 TIMESTAMP WITH TIME ZONE

DATETIME_INTERVAL_PRECISION(整数) #

未实现

INDICATOR(整数) #

指示符(指示空值或值截断)

KEY_MEMBER(整数) #

未实现

LENGTH(整数) #

数据的字符长度

NAME(字符串) #

列的名称

NULLABLE(整数) #

未实现

OCTET_LENGTH(整数) #

数据的字符表示形式的字节长度

PRECISION(整数) #

精度(对于类型 numeric

RETURNED_LENGTH(整数) #

数据的字符长度

RETURNED_OCTET_LENGTH(整数) #

数据的字符表示形式的字节长度

SCALE(整数) #

比例(对于类型 numeric

TYPE(整数) #

列的数据类型的数字代码

EXECUTEDECLAREOPEN 语句中,INTOUSING 关键字的效果不同。还可以手动构建描述符区域以提供查询或游标的输入参数,并且 USING SQL DESCRIPTOR name 是将输入参数传递给参数化查询的方法。构建命名 SQL 描述符区域的语句如下

EXEC SQL SET DESCRIPTOR name VALUE num field = :hostvar;

PostgreSQL 支持在一个 FETCH 语句中检索多条记录,在这种情况下,将数据存储到主机变量中假设变量是数组。例如:

EXEC SQL BEGIN DECLARE SECTION;
int id[5];
EXEC SQL END DECLARE SECTION;

EXEC SQL FETCH 5 FROM mycursor INTO SQL DESCRIPTOR mydesc;

EXEC SQL GET DESCRIPTOR mydesc VALUE 1 :id = DATA;

34.7.2. SQLDA 描述符区域 #

SQLDA 描述符区域是 C 语言结构,也可以用来获取查询的结果集和元数据。一个结构存储结果集中的一个记录。

EXEC SQL include sqlda.h;
sqlda_t         *mysqlda;

EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;

请注意,省略了 SQL 关键字。第 34.7.1 节 中关于 INTOUSING 关键字用例的段落也适用于此,并增加了一点。在 DESCRIBE 语句中,如果使用了 INTO 关键字,则可以完全省略 DESCRIPTOR 关键字

EXEC SQL DESCRIBE prepared_statement INTO mysqlda;

使用 SQLDA 的程序的一般流程是

  1. 准备查询,并为其声明一个游标。

  2. 为结果行声明一个 SQLDA。

  3. 为输入参数声明一个 SQLDA,并对其进行初始化(内存分配,参数设置)。

  4. 使用输入 SQLDA 打开游标。

  5. 从游标中提取行,并将它们存储到输出 SQLDA 中。

  6. 将输出 SQLDA 中的值读取到主机变量中(如有必要,进行转换)。

  7. 关闭游标。

  8. 释放为输入 SQLDA 分配的内存区域。

34.7.2.1. SQLDA 数据结构 #

SQLDA 使用三种数据结构类型:sqlda_tsqlvar_tstruct sqlname

提示

PostgreSQL 的 SQLDA 具有与 IBM DB2 Universal Database 中的 SQLDA 类似的数据结构,因此,有关 DB2 的 SQLDA 的一些技术信息可能有助于更好地理解 PostgreSQL 的 SQLDA。

34.7.2.1.1. sqlda_t 结构 #

结构类型 sqlda_t 是实际 SQLDA 的类型。它保存一条记录。并且两个或多个 sqlda_t 结构可以通过 desc_next 字段中的指针连接到一个链接列表中,从而表示一个有序的行集合。因此,当提取两行或多行时,应用程序可以通过在每个 sqlda_t 节点中跟随 desc_next 指针来读取它们。

sqlda_t 的定义是

struct sqlda_struct
{
    char            sqldaid[8];
    long            sqldabc;
    short           sqln;
    short           sqld;
    struct sqlda_struct *desc_next;
    struct sqlvar_struct sqlvar[1];
};

typedef struct sqlda_struct sqlda_t;

字段的含义是

sqldaid #

它包含文字字符串 "SQLDA "

sqldabc #

它包含分配空间的大小(以字节为单位)。

sqln #

如果它通过 USING 关键字传递到 OPENDECLAREEXECUTE 语句中,则它包含参数化查询的输入参数的数量。如果用作 SELECTEXECUTEFETCH 语句的输出,则其值与 sqld 语句相同

sqld #

它包含结果集中的字段数。

desc_next #

如果查询返回多条记录,则返回多个链接的 SQLDA 结构,并且 desc_next 包含指向列表中下一条记录的指针。

sqlvar #

这是结果集中列的数组。

34.7.2.1.2. sqlvar_t 结构 #

结构类型 sqlvar_t 包含列值和元数据(如类型和长度)。该类型的定义为

struct sqlvar_struct
{
    short          sqltype;
    short          sqllen;
    char          *sqldata;
    short         *sqlind;
    struct sqlname sqlname;
};

typedef struct sqlvar_struct sqlvar_t;

字段的含义是

sqltype #

包含字段的类型标识符。有关值,请参阅 ecpgtype.h 中的 enum ECPGttype

sqllen #

包含字段的二进制长度。例如,ECPGt_int 为 4 字节。

sqldata #

指向数据。数据的格式在 第 34.4.4 节 中描述。

sqlind #

指向空指示符。0 表示非空,-1 表示空。

sqlname #

字段的名称。

34.7.2.1.3. struct sqlname 结构 #

struct sqlname 结构保存列名。它用作 sqlvar_t 结构的成员。结构的定义为

#define NAMEDATALEN 64

struct sqlname
{
        short           length;
        char            data[NAMEDATALEN];
};

字段的含义是

length #

包含字段名的长度。

data #

包含实际的字段名。

34.7.2.2. 使用 SQLDA 检索结果集 #

通过 SQLDA 检索查询结果集的一般步骤如下

  1. 声明一个 sqlda_t 结构来接收结果集。

  2. 执行 FETCH/EXECUTE/DESCRIBE 命令以处理指定已声明 SQLDA 的查询。

  3. 通过查看 sqlda_t 结构的成员 sqln 来检查结果集中的记录数。

  4. sqlda_t 结构的成员 sqlvar[0]sqlvar[1] 等获取每列的值。

  5. 通过遵循 sqlda_t 结构的成员 desc_next 指针,转到下一行(sqlda_t 结构)。

  6. 根据需要重复上述步骤。

以下是一个通过 SQLDA 检索结果集的示例。

首先,声明一个 sqlda_t 结构来接收结果集。

sqlda_t *sqlda1;

接下来,在命令中指定 SQLDA。这是一个 FETCH 命令示例。

EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;

运行一个循环,沿着链接列表检索行。

sqlda_t *cur_sqlda;

for (cur_sqlda = sqlda1;
     cur_sqlda != NULL;
     cur_sqlda = cur_sqlda->desc_next)
{
    ...
}

在循环内,运行另一个循环来检索行的每一列数据(sqlvar_t 结构)。

for (i = 0; i < cur_sqlda->sqld; i++)
{
    sqlvar_t v = cur_sqlda->sqlvar[i];
    char *sqldata = v.sqldata;
    short sqllen  = v.sqllen;
    ...
}

要获取列值,请检查 sqlvar_t 结构的成员 sqltype 值。然后,根据列类型切换到适当的方法,将数据从 sqlvar 字段复制到主机变量。

char var_buf[1024];

switch (v.sqltype)
{
    case ECPGt_char:
        memset(&var_buf, 0, sizeof(var_buf));
        memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf) - 1 : sqllen));
        break;

    case ECPGt_int: /* integer */
        memcpy(&intval, sqldata, sqllen);
        snprintf(var_buf, sizeof(var_buf), "%d", intval);
        break;

    ...
}

34.7.2.3. 使用 SQLDA 传递查询参数 #

使用 SQLDA 将输入参数传递给准备好的查询的一般步骤如下

  1. 创建准备好的查询(预处理语句)

  2. 声明一个 sqlda_t 结构作为输入 SQLDA。

  3. 为输入 SQLDA 分配内存区域(作为 sqlda_t 结构)。

  4. 在分配的内存中设置(复制)输入值。

  5. 打开一个游标,并指定输入 SQLDA。

以下是一个示例。

首先,创建一个预处理语句。

EXEC SQL BEGIN DECLARE SECTION;
char query[1024] = "SELECT d.oid, * FROM pg_database d, pg_stat_database s WHERE d.oid = s.datid AND (d.datname = ? OR d.oid = ?)";
EXEC SQL END DECLARE SECTION;

EXEC SQL PREPARE stmt1 FROM :query;

接下来,为 SQLDA 分配内存,并在 sqlda_t 结构的成员变量 sqln 中设置输入参数的数量。当准备好的查询需要两个或多个输入参数时,应用程序必须分配额外的内存空间,该空间由 (参数数量 - 1) * sizeof(sqlvar_t) 计算得出。此处显示的示例为两个输入参数分配了内存空间。

sqlda_t *sqlda2;

sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));

sqlda2->sqln = 2; /* number of input variables */

内存分配后,将参数值存储到 sqlvar[] 数组中。(这是在 SQLDA 接收结果集时用于检索列值的相同数组。)在此示例中,输入参数为 "postgres"(字符串类型)和 1(整数类型)。

sqlda2->sqlvar[0].sqltype = ECPGt_char;
sqlda2->sqlvar[0].sqldata = "postgres";
sqlda2->sqlvar[0].sqllen  = 8;

int intval = 1;
sqlda2->sqlvar[1].sqltype = ECPGt_int;
sqlda2->sqlvar[1].sqldata = (char *) &intval;
sqlda2->sqlvar[1].sqllen  = sizeof(intval);

通过打开一个游标并指定之前设置的 SQLDA,将输入参数传递给准备好的语句。

EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;

最后,在使用输入 SQLDA 后,必须显式释放分配的内存空间,这与用于接收查询结果的 SQLDA 不同。

free(sqlda2);

34.7.2.4. 使用 SQLDA 的示例应用程序 #

这是一个示例程序,它描述了如何从系统目录中获取由输入参数指定的数据库的访问统计信息。

此应用程序将两个系统表 pg_database 和 pg_stat_database 在数据库 OID 上连接,并获取和显示由两个输入参数检索到的数据库统计信息(数据库 postgres 和 OID 1)。

首先,为输入声明一个 SQLDA,为输出声明一个 SQLDA。

EXEC SQL include sqlda.h;

sqlda_t *sqlda1; /* an output descriptor */
sqlda_t *sqlda2; /* an input descriptor  */

接下来,连接到数据库,准备一个语句,并为准备好的语句声明一个游标。

int
main(void)
{
    EXEC SQL BEGIN DECLARE SECTION;
    char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )";
    EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO testdb AS con1 USER testuser;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    EXEC SQL PREPARE stmt1 FROM :query;
    EXEC SQL DECLARE cur1 CURSOR FOR stmt1;

接下来,将一些值放入输入 SQLDA 中作为输入参数。为输入 SQLDA 分配内存,并将输入参数的数量设置为 sqln。将类型、值和值长度存储到 sqltypesqldatasqllen 中,这些都是 sqlvar 结构中的成员。

    /* Create SQLDA structure for input parameters. */
    sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
    memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
    sqlda2->sqln = 2; /* number of input variables */

    sqlda2->sqlvar[0].sqltype = ECPGt_char;
    sqlda2->sqlvar[0].sqldata = "postgres";
    sqlda2->sqlvar[0].sqllen  = 8;

    intval = 1;
    sqlda2->sqlvar[1].sqltype = ECPGt_int;
    sqlda2->sqlvar[1].sqldata = (char *)&intval;
    sqlda2->sqlvar[1].sqllen  = sizeof(intval);

设置输入 SQLDA 后,使用输入 SQLDA 打开游标。

    /* Open a cursor with input parameters. */
    EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;

从打开的游标中将行提取到输出 SQLDA 中。(通常,您必须在循环中重复调用 FETCH,以提取结果集中的所有行。)

    while (1)
    {
        sqlda_t *cur_sqlda;

        /* Assign descriptor to the cursor  */
        EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;

接下来,通过遵循 sqlda_t 结构的链接列表,从 SQLDA 中检索提取的记录。

    for (cur_sqlda = sqlda1 ;
         cur_sqlda != NULL ;
         cur_sqlda = cur_sqlda->desc_next)
    {
        ...

读取第一条记录中的每一列。列的数量存储在 sqld 中,第一列的实际数据存储在 sqlvar[0] 中,这两个都是 sqlda_t 结构的成员。

        /* Print every column in a row. */
        for (i = 0; i < sqlda1->sqld; i++)
        {
            sqlvar_t v = sqlda1->sqlvar[i];
            char *sqldata = v.sqldata;
            short sqllen  = v.sqllen;

            strncpy(name_buf, v.sqlname.data, v.sqlname.length);
            name_buf[v.sqlname.length] = '\0';

现在,列数据存储在变量 v 中。查看 v.sqltype 以获取列的类型,并将每个数据复制到主机变量中。

            switch (v.sqltype) {
                int intval;
                double doubleval;
                unsigned long long int longlongval;

                case ECPGt_char:
                    memset(&var_buf, 0, sizeof(var_buf));
                    memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf)-1 : sqllen));
                    break;

                case ECPGt_int: /* integer */
                    memcpy(&intval, sqldata, sqllen);
                    snprintf(var_buf, sizeof(var_buf), "%d", intval);
                    break;

                ...

                default:
                    ...
            }

            printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype);
        }

处理完所有记录后,关闭游标,并断开与数据库的连接。

    EXEC SQL CLOSE cur1;
    EXEC SQL COMMIT;

    EXEC SQL DISCONNECT ALL;

整个程序显示在 示例 34.1 中。

示例 34.1. 示例 SQLDA 程序

#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

EXEC SQL include sqlda.h;

sqlda_t *sqlda1; /* descriptor for output */
sqlda_t *sqlda2; /* descriptor for input */

EXEC SQL WHENEVER NOT FOUND DO BREAK;
EXEC SQL WHENEVER SQLERROR STOP;

int
main(void)
{
    EXEC SQL BEGIN DECLARE SECTION;
    char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )";

    int intval;
    unsigned long long int longlongval;
    EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO uptimedb AS con1 USER uptime;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    EXEC SQL PREPARE stmt1 FROM :query;
    EXEC SQL DECLARE cur1 CURSOR FOR stmt1;

    /* Create an SQLDA structure for an input parameter */
    sqlda2 = (sqlda_t *)malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
    memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
    sqlda2->sqln = 2; /* a number of input variables */

    sqlda2->sqlvar[0].sqltype = ECPGt_char;
    sqlda2->sqlvar[0].sqldata = "postgres";
    sqlda2->sqlvar[0].sqllen  = 8;

    intval = 1;
    sqlda2->sqlvar[1].sqltype = ECPGt_int;
    sqlda2->sqlvar[1].sqldata = (char *) &intval;
    sqlda2->sqlvar[1].sqllen  = sizeof(intval);

    /* Open a cursor with input parameters. */
    EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;

    while (1)
    {
        sqlda_t *cur_sqlda;

        /* Assign descriptor to the cursor  */
        EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;

        for (cur_sqlda = sqlda1 ;
             cur_sqlda != NULL ;
             cur_sqlda = cur_sqlda->desc_next)
        {
            int i;
            char name_buf[1024];
            char var_buf[1024];

            /* Print every column in a row. */
            for (i=0 ; i<cur_sqlda->sqld ; i++)
            {
                sqlvar_t v = cur_sqlda->sqlvar[i];
                char *sqldata = v.sqldata;
                short sqllen  = v.sqllen;

                strncpy(name_buf, v.sqlname.data, v.sqlname.length);
                name_buf[v.sqlname.length] = '\0';

                switch (v.sqltype)
                {
                    case ECPGt_char:
                        memset(&var_buf, 0, sizeof(var_buf));
                        memcpy(&var_buf, sqldata, (sizeof(var_buf)<=sqllen ? sizeof(var_buf)-1 : sqllen) );
                        break;

                    case ECPGt_int: /* integer */
                        memcpy(&intval, sqldata, sqllen);
                        snprintf(var_buf, sizeof(var_buf), "%d", intval);
                        break;

                    case ECPGt_long_long: /* bigint */
                        memcpy(&longlongval, sqldata, sqllen);
                        snprintf(var_buf, sizeof(var_buf), "%lld", longlongval);
                        break;

                    default:
                    {
                        int i;
                        memset(var_buf, 0, sizeof(var_buf));
                        for (i = 0; i < sqllen; i++)
                        {
                            char tmpbuf[16];
                            snprintf(tmpbuf, sizeof(tmpbuf), "%02x ", (unsigned char) sqldata[i]);
                            strncat(var_buf, tmpbuf, sizeof(var_buf));
                        }
                    }
                        break;
                }

                printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype);
            }

            printf("\n");
        }
    }

    EXEC SQL CLOSE cur1;
    EXEC SQL COMMIT;

    EXEC SQL DISCONNECT ALL;

    return 0;
}

此示例的输出应类似于以下内容(某些数字会有所不同)。

oid = 1 (type: 1)
datname = template1 (type: 1)
datdba = 10 (type: 1)
encoding = 0 (type: 5)
datistemplate = t (type: 1)
datallowconn = t (type: 1)
dathasloginevt = f (type: 1)
datconnlimit = -1 (type: 5)
datfrozenxid = 379 (type: 1)
dattablespace = 1663 (type: 1)
datconfig =  (type: 1)
datacl = {=c/uptime,uptime=CTc/uptime} (type: 1)
datid = 1 (type: 1)
datname = template1 (type: 1)
numbackends = 0 (type: 5)
xact_commit = 113606 (type: 9)
xact_rollback = 0 (type: 9)
blks_read = 130 (type: 9)
blks_hit = 7341714 (type: 9)
tup_returned = 38262679 (type: 9)
tup_fetched = 1836281 (type: 9)
tup_inserted = 0 (type: 9)
tup_updated = 0 (type: 9)
tup_deleted = 0 (type: 9)

oid = 11511 (type: 1)
datname = postgres (type: 1)
datdba = 10 (type: 1)
encoding = 0 (type: 5)
datistemplate = f (type: 1)
datallowconn = t (type: 1)
dathasloginevt = f (type: 1)
datconnlimit = -1 (type: 5)
datfrozenxid = 379 (type: 1)
dattablespace = 1663 (type: 1)
datconfig =  (type: 1)
datacl =  (type: 1)
datid = 11511 (type: 1)
datname = postgres (type: 1)
numbackends = 0 (type: 5)
xact_commit = 221069 (type: 9)
xact_rollback = 18 (type: 9)
blks_read = 1176 (type: 9)
blks_hit = 13943750 (type: 9)
tup_returned = 77410091 (type: 9)
tup_fetched = 3253694 (type: 9)
tup_inserted = 0 (type: 9)
tup_updated = 0 (type: 9)
tup_deleted = 0 (type: 9)

提交更正

如果您在文档中看到任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用 此表单 报告文档问题。