35.6. 基本語句

本節以及隨後的一節裡,我們描述所有 PL/pgSQL 明確可以理解的語句類型。任何無法識別為這樣類型的語句將被做為 SQL 命令看待,並且被發送到主資料庫引擎執行(在將語句中用到的任何 PL/pgSQL 變量進行替換之後)。因此, 舉例來說,SQL INSERTUPDATE,和 DELETE 命令可能被認為是 PL/pgSQL 語句,但是它們並未在此明確列出。

35.6.1. 賦值

給一個變量或行/記錄賦值用下面方法:

identIFier := expression;

如上所述,這樣的語句中的資料表達式是用一個發送到主資料庫引擎的 SQL SELECT 命令計算的。該資料表達式必須生成單一的數值。

如果資料表達式的結果資料類型和變量資料類型不一致, 或者變量具有已知的尺寸/精度(象 char(20)), 結果值將隱含地被PL/pgSQL解釋器用結果類型的輸出函數和變量類型的輸入函數轉換。 要注意的是,如果結果數值的字串形式不是輸入函數可以接受的形式, 那麼這樣做可能導致類型輸入函數產生的執行時錯誤。

例子︰

user_id := 20;
tax := subtotal * 0.06;

35.6.2. SELECT INTO

生成多個列(但只有一行)的SELECT命令的結果可以賦予一個記錄變量, 行類型變量,或者一個標量變量的列資料表。這是用下面方法實現的:

SELECT INTO target select_expressions FROM ...;

這裡的 target 可以是一個記錄變量, 行變量,或者一個用逗號分隔的簡單變量和記錄/行字串的列資料表。 select_expressions 和命令的剩餘部分和普通 SQL 一樣。

請注意這個構造和 PostgreSQL 普通的SELECT INTO構造的解釋是不一樣的, 後者的INTO目標是一個新建立的資料表。 (如果您想在 PL/pgSQL 函數里從一個SELECT 的結果中建立一個資料表,那麼使用 CREATE TABLE ... AS SELECT 語法。)

如果將一行或者一個變量列資料表用做目標,那麼選出的數值必需精確匹配目標的結構, 否則就會產生執行時錯誤。如果目標是一個記錄變量,那麼它自動將自己配置成命令結果列的行類型。

除了INTO子句,剩下的SELECT語句和普通的 SQL SELECT命令完全一樣, 並且您可以使用SELECT的全部能力。

INTO 子句幾乎可以出現在 SELECT 語句的任何地方。 習慣上它是跟在 SELECT 後面,就像上面寫的那樣, 或者就在 FROM 之前 — 也就是說,在 select_expressions 列資料表之前或者之後。

如果命令返回零行,則給目標賦與空值。 如果命令返回多行,那麼將第一行賦與目標並拋棄其它的行。 (請注意:除非您用了ORDER BY,否則"第一行"是不明確的。)

INTO子句可以出現在SELECT命令裡的幾乎任何地方。

在一個 SELECT INTO 語句之後,您可以檢查特殊變量 FOUND (參閱 Section 35.6.5)來判斷一個賦值是否成功, 也就是說,查詢至少返回一行。例如:

SELECT INTO myrec * FROM emp WHERE empname = myname;
IF NOT FOUND THEN
    RAISE EXCEPTION 'employee % not found', myname;
END IF;

要測試一個記錄/行結果是否為空,您可以使用 IS NULL 條件。不過,這個時候沒有任何辦法來判斷是否有額外的行被拋棄。下面是一個例子,處理沒有返回行的情況:

DECLARE
    users_rec RECORD;
BEGIN
    SELECT INTO users_rec * FROM users WHERE user_id=3;

    IF users_rec.homepage IS NULL THEN
        -- 用戶沒有輸入主頁,返回"http://"
        RETURN 'http://';
    END IF;
END;

35.6.3. 執行一個沒有結果的資料表達式或者命令

有時候我們希望計算一個資料表達式或者一個命令,但是卻丟棄其結果(通常因為我們經常調用一些存在有用的副作用但是不存在有用結果值的函數)。 要在 PL/pgSQL 裡幹這件事, 您可以使用PERFORM語句:

PERFORM query;

這條語句執行一個 query並且丟棄結果。 query 的寫法和您平常寫 SQL SELECT 命令是一樣的, 只是把開頭的關鍵字 SELECT 替換成 PERFORMPL/pgSQL 的變量和平常一樣代換到命令中。 同樣,如果命令生成至少一行,那麼特殊的變量 FOUND 設置為真,如果沒有生成行,則為假。

注意: 我們可能希望沒有INTO子句的SELECT也能滿足這樣的需要, 但是目前可以接受的唯一的方法是PERFORM

一個例子:

PERFORM create_mv('cs_session_page_requests_mv', my_query);

35.6.4. 執行動態命令

您經常會希望在您的PL/pgSQL函數里生成動態命令。 也就是那些每次執行的時候都會涉及不同資料表或不同資料類型的命令。 在這樣的情況下,PL/pgSQL 試圖為命令緩衝執行計劃的一般企圖將不再合適。 為了處理這樣的問題,我們提供了 EXECUTE 語句:

EXECUTE command-string;

這裡的 command-string 是一個生成字串(類型為 text)的資料表達式,該字串包含要執行的命令。 該字串的文本將被傳遞給 SQL 引擎。

請特別注意在該命令字串裡將不會發生任何 PL/pgSQL 變量代換。 變量的數值必需在構造命令字串的時候插入該字串。

和所有其它在PL/pgSQL裡的命令不同, 一個由EXECUTE語句執行的命令在伺服器生命期內並不只準備和保存一次。 相反,在該語句每次執行的時候,命令都準備一次。 命令字串可以在過程裡動態地生成以便於對各種不同的資料表和字串進行操作。

來自SELECT命令的結果被EXECUTE拋棄,並且目前EXECUTE 裡面還不支援SELECT INTO。所以我們沒有辦法從一個動態建立的 SELECT 中, 使用簡單的 EXECUTE 命令抽取結果。 但是有其它兩種方法可以實現裡抽取結果:一種是是使用在 Section 35.7.4 裡描述的FOR-IN-EXECUTE方式, 另外一種是和 OPEN-FOR-EXECUTE 一起用游標,就像 Section 35.8.2 裡描述的那樣。

在使用動態命令的時候,您經常需要逃逸單引號。我們建議包圍您的函數體內固定文本的方法是美元符包圍。 (如果您有老的,沒有使用美元符包圍的代碼,請參考 Section 35.2.1, 這樣在把老代碼轉換成更合理的結構時,會節省您的一些精力。)

要插入到構造出來的查詢中的動態數值也需要特殊的處理, 因為他們自己可能包含引號字元。 一個例子(除了特別說明之外,這裡我們都假設您使用了美元符包圍):

EXECUTE 'UPDATE tbl SET '
        || quote_ident(columnname)
        || ' = '
        || quote_literal(newvalue)
        || ' WHERE ...';

這個例子顯示了函數 quote_ident(text)quote_literal(text) 的使用。 為了安全,包含字串和資料表標識符的變量應該傳遞給函數 quote_ident。 那些包含數值的變量,如果其值在構造出來態命令字串裡應外是文本字串,那麼應該傳遞給 quote_literal。 它們倆都會採取合適的步驟把輸入文本包圍在單或雙引號裡並且對任何嵌入其中的特殊字元進行合適的逃逸處理。

請注意美元符包圍只對包圍固定文本有用。如果想像下面這樣做上面的例子,那就太糟糕了

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = $$'
        || newvalue
        || '$$ WHERE ...';

因為如果 newvalue 的內容碰巧有$$,那麼這段代碼就有毛病了。 同樣的問題可能出現在您選用的任何美元符包圍分隔符上。 因此,要想安全地包圍事先不知道地文本,您必須使用 quote_literal

動態命令和 EXECUTE 的一個更大的例子在 Example 35-5 裡, 這個例子製作病執行了一個定義新的函數的 CREATE FUNCTION 命令。

35.6.5. 獲取結果狀態

有好幾種方法可以判斷一條命令的效果。第一個方法是使用 GET DIAGNOSTICS,它的形式如下:

GET DIAGNOSTICS variable = item [ , ... ] ;

這條命令允許我們檢索系統狀態標識符。每個 item 是一個關鍵字,資料表示一個將要賦予該特定變量的狀態值(該變量應該和要接收的數值類型相同)。 目前可用的狀態項有 ROW_COUNT, 最後一個發送給 SQL 引擎的 SQL 命令處理的行的數量,和 RESULT_OID,最後一條 SQL 命令插入的最後一行的 OID。請注意 RESULT_OID 只有在一個INSERT命令之後才有用。

一個例子:

      GET DIAGNOSTICS var_integer = ROW_COUNT;

另外一個判斷命令效果的方法是一個類型為 boolean 的特殊變量 FOUNDFOUND在每個 PL/pgSQL 函數里開始都為假。它被下列語句設置:

FOUND 是每個 PL/pgSQL 裡的局部變量; 它的任何修改只影響目前的函數。