支持的版本:当前 (17) / 16 / 15 / 14 / 13
开发版本:devel
不支持的版本:12 / 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 对于 DATE 将具有值 1,对于 TIME 将具有值 2,对于 TIMESTAMP 将具有值 3,对于 TIME WITH TIME ZONE 将具有值 4,对于 TIMESTAMP WITH TIME ZONE 将具有值 5

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 中类似的数据结构,因此有关 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。将类型、值和值长度存储到 sqlvar 结构中的 sqltypesqldatasqllen 中。

    /* 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)

提交更正

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