支援的版本:目前 (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 / 7.3 / 7.2

41.3. 宣告 #

區塊中使用的所有變數都必須在區塊的宣告區段中宣告。(唯一的例外是,在整數值範圍內迭代的 FOR 迴圈的迴圈變數會自動宣告為整數變數,同樣地,在游標結果中迭代的 FOR 迴圈的迴圈變數也會自動宣告為紀錄變數。)

PL/pgSQL 變數可以具有任何 SQL 資料類型,例如 integervarcharchar

以下是一些變數宣告的範例

user_id integer;
quantity numeric(5);
url varchar;
myrow tablename%ROWTYPE;
myfield tablename.columnname%TYPE;
arow RECORD;

變數宣告的一般語法為

name [ CONSTANT ] type [ COLLATE collation_name ] [ NOT NULL ] [ { DEFAULT | := | = } expression ];

如果給定 DEFAULT 子句,則指定在進入區塊時分配給變數的初始值。如果未給定 DEFAULT 子句,則變數會初始化為SQL空值。CONSTANT 選項可防止在初始化後將變數分配給變數,因此其值在區塊的持續時間內將保持恆定。COLLATE 選項指定要用於變數的定序(請參閱第 41.3.6 節)。如果指定 NOT NULL,則分配空值會導致執行階段錯誤。所有宣告為 NOT NULL 的變數都必須指定非空預設值。可以使用等號 (=) 代替符合 PL/SQL 標準的 :=

每次進入區塊時(而不只是每次函數呼叫一次)都會評估變數的預設值並將其分配給變數。因此,例如,將 now() 分配給類型為 timestamp 的變數會導致該變數具有目前函數呼叫的時間,而不是預先編譯函數的時間。

範例

quantity integer DEFAULT 32;
url varchar := 'http://mysite.com';
transaction_time CONSTANT timestamp with time zone := now();

宣告後,變數的值可用於同一區塊中後續的初始化運算式中,例如

DECLARE
  x integer := 1;
  y integer := x + 1;

41.3.1. 宣告函數參數 #

傳遞給函數的參數以識別碼 $1$2 等命名。或者,可以為 $n 參數名稱宣告別名,以提高可讀性。然後可以使用別名或數字識別碼來引用參數值。

有兩種建立別名的方式。首選方式是在 CREATE FUNCTION 命令中為參數指定名稱,例如

CREATE FUNCTION sales_tax(subtotal real) RETURNS real AS $$
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

另一種方式是使用宣告語法明確宣告別名

name ALIAS FOR $n;

這種樣式的相同範例如下所示

CREATE FUNCTION sales_tax(real) RETURNS real AS $$
DECLARE
    subtotal ALIAS FOR $1;
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

注意

這兩個範例並不完全等效。在第一種情況下,可以將 subtotal 引用為 sales_tax.subtotal,但在第二種情況下則無法。(如果我們將標籤附加到內部區塊,則可以使用該標籤來限定 subtotal。)

更多範例

CREATE FUNCTION instr(varchar, integer) RETURNS integer AS $$
DECLARE
    v_string ALIAS FOR $1;
    index ALIAS FOR $2;
BEGIN
    -- some computations using v_string and index here
END;
$$ LANGUAGE plpgsql;


CREATE FUNCTION concat_selected_fields(in_t sometablename) RETURNS text AS $$
BEGIN
    RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
END;
$$ LANGUAGE plpgsql;

當使用輸出參數宣告 PL/pgSQL 函數時,輸出參數會以與一般輸入參數相同的方式給定 $n 名稱和可選別名。輸出參數實際上是一個從 NULL 開始的變數;應該在函數執行期間分配給它。參數的最終值是傳回的值。例如,也可以用這種方式完成銷售稅範例

CREATE FUNCTION sales_tax(subtotal real, OUT tax real) AS $$
BEGIN
    tax := subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

請注意,我們省略了 RETURNS real — 我們可以包含它,但它是多餘的。

若要呼叫具有 OUT 參數的函數,請在函數呼叫中省略輸出參數

SELECT sales_tax(100.00);

當傳回多個值時,輸出參數最有用。一個簡單的例子是

CREATE FUNCTION sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
BEGIN
    sum := x + y;
    prod := x * y;
END;
$$ LANGUAGE plpgsql;

SELECT * FROM sum_n_product(2, 4);
 sum | prod
-----+------
   6 |    8

第 36.5.4 節中所討論的,這實際上為函數的結果建立了一個匿名記錄類型。如果給定 RETURNS 子句,則必須聲明 RETURNS record

這也適用於程序,例如

CREATE PROCEDURE sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
BEGIN
    sum := x + y;
    prod := x * y;
END;
$$ LANGUAGE plpgsql;

在呼叫程序時,所有參數都必須指定。對於輸出參數,從純 SQL 呼叫程序時,可以指定 NULL

CALL sum_n_product(2, 4, NULL, NULL);
 sum | prod
-----+------
   6 |    8

但是,從 PL/pgSQL 呼叫程序時,您應該為任何輸出參數撰寫一個變數;該變數將接收呼叫的結果。詳情請參閱第 41.6.3 節

另一種宣告 PL/pgSQL 函數的方法是使用 RETURNS TABLE,例如:

CREATE FUNCTION extended_sales(p_itemno int)
RETURNS TABLE(quantity int, total numeric) AS $$
BEGIN
    RETURN QUERY SELECT s.quantity, s.quantity * s.price FROM sales AS s
                 WHERE s.itemno = p_itemno;
END;
$$ LANGUAGE plpgsql;

這完全等同於宣告一個或多個 OUT 參數,並指定 RETURNS SETOF sometype

PL/pgSQL 函數的回傳型別宣告為多型別 (polymorphic type) 時 (請參閱第 36.2.5 節),會建立一個特殊的參數 $0。它的資料型別是函數的實際回傳型別,從實際的輸入型別推導而來。這允許函數存取其實際回傳型別,如第 41.3.3 節所示。$0 初始化為 null,並且可以由函數修改,因此如果需要,可以用於保存回傳值,儘管這不是必需的。$0 也可以給定別名。例如,此函數適用於任何具有 + 運算子的資料型別:

CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
RETURNS anyelement AS $$
DECLARE
    result ALIAS FOR $0;
BEGIN
    result := v1 + v2 + v3;
    RETURN result;
END;
$$ LANGUAGE plpgsql;

透過將一個或多個輸出參數宣告為多型別,可以獲得相同的效果。在這種情況下,不使用特殊的 $0 參數;輸出參數本身具有相同的作用。例如:

CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement,
                                 OUT sum anyelement)
AS $$
BEGIN
    sum := v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

實際上,使用 anycompatible 型別家族宣告多型函數可能更有用,這樣會自動將輸入引數提升為通用型別。例如:

CREATE FUNCTION add_three_values(v1 anycompatible, v2 anycompatible, v3 anycompatible)
RETURNS anycompatible AS $$
BEGIN
    RETURN v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

對於這個範例,呼叫像是:

SELECT add_three_values(1, 2, 4.7);

會正常運作,自動將整數輸入提升為數值型別。使用 anyelement 的函數會要求您手動將三個輸入強制轉換為相同的型別。

41.3.2. ALIAS #

newname ALIAS FOR oldname;

ALIAS 語法的用途比上一節所暗示的更廣泛:您可以為任何變數宣告別名,而不僅僅是函數參數。此功能的主要實際用途是為具有預定名稱的變數分配不同的名稱,例如觸發函數中的 NEWOLD

範例

DECLARE
  prior ALIAS FOR old;
  updated ALIAS FOR new;

由於 ALIAS 建立了兩種不同的方式來命名相同的物件,因此不受限制的使用可能會造成混淆。最好僅將其用於覆蓋預定名稱。

41.3.3. 複製型別 #

name table.column%TYPE
name variable%TYPE

%TYPE 提供資料表欄位或先前宣告的 PL/pgSQL 變數的資料型別。您可以使用它來宣告將保存資料庫值的變數。例如,假設您的 users 資料表中有一個名為 user_id 的欄位。要宣告一個與 users.user_id 具有相同資料型別的變數,您可以這樣寫:

user_id users.user_id%TYPE;

也可以在 %TYPE 後面撰寫陣列裝飾,從而建立一個保存引用型別陣列的變數:

user_ids users.user_id%TYPE[];
user_ids users.user_id%TYPE ARRAY[4];  -- equivalent to the above

正如宣告作為陣列的資料表欄位時一樣,無論您撰寫多個方括號對還是特定的陣列維度都無關緊要:PostgreSQL 將給定元素型別的所有陣列視為相同的型別,無論維度如何。(請參閱第 8.15.1 節。)

透過使用 %TYPE,您無需知道您正在參考的結構的資料型別,最重要的是,如果引用的項目的資料型別在將來發生變化 (例如:您將 user_id 的型別從 integer 變更為 real),您可能不需要變更您的函數定義。

%TYPE 在多型函數中特別有價值,因為內部變數所需的資料型別可能會因一次呼叫而異。透過將 %TYPE 應用於函數的引數或結果佔位符,可以建立適當的變數。

41.3.4. 列型別 #

name table_name%ROWTYPE;
name composite_type_name;

複合型別的變數稱為變數(或列型別變數)。這樣的變數可以保存 SELECTFOR 查詢結果的整列,只要該查詢的欄位集與變數的宣告型別相符即可。列值的各個欄位使用常用的點表示法存取,例如 rowvar.field

可以透過使用 table_name%ROWTYPE 表示法,宣告列變數具有與現有資料表或檢視表的列相同的型別;也可以透過給定複合型別的名稱來宣告。(由於每個資料表都有一個具有相同名稱的關聯複合型別,因此在 PostgreSQL 中,您撰寫 %ROWTYPE 與否實際上沒有關係。但是帶有 %ROWTYPE 的形式更具可移植性。)

%TYPE 一樣,%ROWTYPE 後面可以跟隨陣列裝飾,以宣告保存引用的複合型別陣列的變數。

函數的參數可以是複合型別(完整的資料表列)。在這種情況下,相應的識別符 $n 將是一個列變數,並且可以從中選擇欄位,例如 $1.user_id

以下是使用複合型別的範例。table1table2 是現有的資料表,至少具有提到的欄位:

CREATE FUNCTION merge_fields(t_row table1) RETURNS text AS $$
DECLARE
    t2_row table2%ROWTYPE;
BEGIN
    SELECT * INTO t2_row FROM table2 WHERE ... ;
    RETURN t_row.f1 || t2_row.f3 || t_row.f5 || t2_row.f7;
END;
$$ LANGUAGE plpgsql;

SELECT merge_fields(t.*) FROM table1 t WHERE ... ;

41.3.5. 紀錄型別 #

name RECORD;

紀錄變數與列型別變數相似,但它們沒有預定義的結構。它們在 SELECTFOR 命令期間取得分配給它們的列的實際列結構。紀錄變數的子結構每次被分配時都可以改變。這樣做的結果是,在首次將紀錄變數分配給它之前,它沒有子結構,並且任何存取其中欄位的嘗試都會引發執行時間錯誤。

請注意,RECORD 不是真正的資料型別,只是一個佔位符。還應該意識到,當 PL/pgSQL 函數被宣告為回傳型別 record 時,這與紀錄變數的概念並不完全相同,即使這樣的函數可能使用紀錄變數來保存其結果。在這兩種情況下,在編寫函數時實際的列結構都是未知的,但對於回傳 record 的函數,實際的結構是在解析呼叫查詢時確定的,而紀錄變數可以即時更改其列結構。

41.3.6. PL/pgSQL 變數的定序 (Collation) #

PL/pgSQL 函數有一個或多個具有定序資料類型 (collatable data types) 的參數時,會針對每個函數呼叫,根據指定給實際參數的定序 (collation),識別出一個定序,如第 23.2 節所述。如果成功識別出定序(也就是說,參數之間沒有隱含定序的衝突),則所有具有定序功能的參數都將被視為隱含地具有該定序。這將影響函數內對定序敏感的操作的行為。例如,請考慮:

CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b;
END;
$$ LANGUAGE plpgsql;

SELECT less_than(text_field_1, text_field_2) FROM table1;
SELECT less_than(text_field_1, text_field_2 COLLATE "C") FROM table1;

第一次使用 less_than 將使用 text_field_1text_field_2 的通用定序進行比較,而第二次使用將使用 C 定序。

此外,識別出的定序也被假定為任何具有定序功能的本地變數的定序。因此,如果此函數寫成以下形式,它的運作方式不會有任何不同:

CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
DECLARE
    local_a text := a;
    local_b text := b;
BEGIN
    RETURN local_a < local_b;
END;
$$ LANGUAGE plpgsql;

如果沒有具有定序功能的資料類型的參數,或者無法為它們識別出通用定序,則參數和本地變數將使用其資料類型的預設定序(通常是資料庫的預設定序,但對於網域類型 (domain types) 的變數可能不同)。

具有定序功能的資料類型的本地變數可以透過在其宣告中包含 COLLATE 選項來關聯不同的定序,例如:

DECLARE
    local_a text COLLATE "en_US";

此選項會覆寫根據上述規則原本會賦予變數的定序。

此外,如果希望強制在特定操作中使用特定定序,當然可以在函數內部編寫顯式的 COLLATE 子句。例如:

CREATE FUNCTION less_than_c(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b COLLATE "C";
END;
$$ LANGUAGE plpgsql;

這會覆寫與運算式中使用的資料表欄位、參數或本地變數相關聯的定序,就像在普通的 SQL 命令中發生的情況一樣。

提交更正

如果您在文件中發現任何不正確、與您使用特定功能時的經驗不符或需要進一步澄清的地方,請使用此表單來報告文件問題。