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
對於 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
(整數) #欄位資料型態的數字代碼
在 EXECUTE
、DECLARE
和 OPEN
語句中,INTO
和 USING
關鍵字的效果是不同的。 描述區 (Descriptor Area) 也可以手動建立,以提供查詢或游標的輸入參數,而 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 Universal Database 中相似的資料結構,因此一些關於 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 傳遞輸入參數到預備查詢的一般步驟如下:
建立預備查詢(prepared statement)。
宣告一個 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 進行 JOIN,並且擷取並顯示由兩個輸入參數(一個資料庫 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) { ...
讀取第一筆紀錄中的每個欄位。欄位的數量儲存在 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)
如果您在文件中發現任何不正確、與您特定功能的使用經驗不符或需要進一步澄清的地方,請使用此表單來報告文件問題。