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
(整數) #指示符(指示 null 值或值截斷)。
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
關鍵字被省略了。關於 INTO
和 USING
關鍵字用法的段落(在 第 34.7.1 節)也適用於此處,但有一個補充。在 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 通用資料庫中的 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);
這是一個示例程式,它描述瞭如何從系統目錄中檢索指定輸入引數的資料庫的訪問統計資訊。
此應用程式透過資料庫 OID 連線兩個系統表 pg_database 和 pg_stat_database,並獲取和顯示由兩個輸入引數(資料庫 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
結構中儲存型別、值和值長度到 sqltype
、sqldata
和 sqllen
。
/* 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) { ...
讀取第一條記錄中的每個列。列的數量儲存在 sqlda_t
結構的一個成員 sqld
中,第一列的實際資料儲存在 sqlvar[0]
中。
/* 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)
如果您在文件中發現任何不正確之處、與您在該特定功能上的使用經驗不符之處或需要進一步澄清之處,請使用 此表單 報告文件問題。