SQL 描述符区域是一种更复杂的方法,用于处理 SELECT
、FETCH
或 DESCRIBE
语句的结果。SQL 描述符区域将一行数据的数据与元数据项组合到一个数据结构中。元数据在执行动态 SQL 语句时特别有用,因为结果列的性质可能事先未知。PostgreSQL 提供两种使用描述符区域的方法:命名 SQL 描述符区域和 C 结构 SQLDA。
命名 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
关键字是可选的,因此使用 DESCRIPTOR
和 SQL DESCRIPTOR
会生成命名 SQL 描述符区域。现在它是强制性的,省略 SQL
关键字会生成 SQLDA 描述符区域,请参见第 34.7.2 节。
在 DESCRIBE
和 FETCH
语句中,INTO
和 USING
关键字可以类似地使用:它们会在描述符区域中生成结果集和元数据。
现在如何从描述符区域中获取数据?您可以将描述符区域视为具有命名字段的结构。要检索头中字段的值并将其存储到主机变量中,请使用以下命令
EXEC SQL GET DESCRIPTORname
:hostvar
=field
;
目前,仅定义了一个头字段:COUNT
,它指示存在多少个项目描述符区域(即结果中包含多少列)。主机变量需要是整数类型。要从项目描述符区域获取字段,请使用以下命令
EXEC SQL GET DESCRIPTORname
VALUEnum
:hostvar
=field
;
num
可以是文字整数或包含整数的主机变量。可能的字段是
CARDINALITY
(整数) #结果集中的行数
DATA
#实际数据项(因此,此字段的数据类型取决于查询)
DATETIME_INTERVAL_CODE
(整数) #当 TYPE
为 9
时,DATETIME_INTERVAL_CODE
的值为 1
表示 DATE
,2
表示 TIME
,3
表示 TIMESTAMP
,4
表示 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
(整数) #列的数据类型的数字代码
在 EXECUTE
、DECLARE
和 OPEN
语句中,INTO
和 USING
关键字的效果不同。还可以手动构建描述符区域以提供查询或游标的输入参数,并且 USING SQL DESCRIPTOR
是将输入参数传递给参数化查询的方法。构建命名 SQL 描述符区域的语句如下name
EXEC SQL SET DESCRIPTORname
VALUEnum
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;
SQLDA 描述符区域是 C 语言结构,也可以用来获取查询的结果集和元数据。一个结构存储结果集中的一个记录。
EXEC SQL include sqlda.h; sqlda_t *mysqlda; EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;
请注意,省略了 SQL
关键字。第 34.7.1 节 中关于 INTO
和 USING
关键字用例的段落也适用于此,并增加了一点。在 DESCRIBE
语句中,如果使用了 INTO
关键字,则可以完全省略 DESCRIPTOR
关键字
EXEC SQL DESCRIBE prepared_statement INTO mysqlda;
使用 SQLDA 的程序的一般流程是
准备查询,并为其声明一个游标。
为结果行声明一个 SQLDA。
为输入参数声明一个 SQLDA,并对其进行初始化(内存分配,参数设置)。
使用输入 SQLDA 打开游标。
从游标中提取行,并将它们存储到输出 SQLDA 中。
将输出 SQLDA 中的值读取到主机变量中(如有必要,进行转换)。
关闭游标。
释放为输入 SQLDA 分配的内存区域。
SQLDA 使用三种数据结构类型:sqlda_t
、sqlvar_t
和 struct sqlname
。
PostgreSQL 的 SQLDA 具有与 IBM DB2 Universal Database 中的 SQLDA 类似的数据结构,因此,有关 DB2 的 SQLDA 的一些技术信息可能有助于更好地理解 PostgreSQL 的 SQLDA。
结构类型 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;
字段的含义是
结构类型 sqlvar_t
包含列值和元数据(如类型和长度)。该类型的定义为
struct sqlvar_struct { short sqltype; short sqllen; char *sqldata; short *sqlind; struct sqlname sqlname; }; typedef struct sqlvar_struct sqlvar_t;
字段的含义是
通过 SQLDA 检索查询结果集的一般步骤如下
声明一个 sqlda_t
结构来接收结果集。
执行 FETCH
/EXECUTE
/DESCRIBE
命令以处理指定已声明 SQLDA 的查询。
通过查看 sqlda_t
结构的成员 sqln
来检查结果集中的记录数。
从 sqlda_t
结构的成员 sqlvar[0]
、sqlvar[1]
等获取每列的值。
通过遵循 sqlda_t
结构的成员 desc_next
指针,转到下一行(sqlda_t
结构)。
根据需要重复上述步骤。
以下是一个通过 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; ... }
使用 SQLDA 将输入参数传递给准备好的查询的一般步骤如下
创建准备好的查询(预处理语句)
声明一个 sqlda_t 结构作为输入 SQLDA。
为输入 SQLDA 分配内存区域(作为 sqlda_t 结构)。
在分配的内存中设置(复制)输入值。
打开一个游标,并指定输入 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);
这是一个示例程序,它描述了如何从系统目录中获取由输入参数指定的数据库的访问统计信息。
此应用程序将两个系统表 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
。将类型、值和值长度存储到 sqltype
、sqldata
和 sqllen
中,这些都是 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)
如果您在文档中看到任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用 此表单 报告文档问题。