8.11. 復合類型

復合類型描述一行或者一條記錄的結構; 它實際上只是一個字串名和它們的資料類型的列資料表。 PostgreSQL 允許像簡單資料類型那樣使用復合類型。 比如,一個資料表的某個字串可以聲明為一個復合類型。

8.11.1. 聲明復合類型

下面是兩個定義復合類型的簡單例子:

CREATE TYPE complex AS (
    r       double precision,
    i       double precision
);

CREATE TYPE inventory_item AS (
    name            text,
    supplier_id     integer,
    price           numeric
);

語法類似與 CREATE TABLE,只是這裡只可以聲明字串名字和類型; 目前不能聲明約束(比如 NOT NULL 這樣的)。請注意 AS 關鍵字是很重要的; 沒有它,系統會認為這是完全不同的 CREATE TYPE 命令,因此您會看到奇怪的語法錯誤。

定義了類型,我們就可以用它建立資料表:

CREATE TABLE on_hand (
    item      inventory_item,
    count     integer
);

INSERT INTO on_hand VALUES (ROW('fuzzy dice', 42, 1.99), 1000);

或者函數:

CREATE FUNCTION price_extension(inventory_item, integer) RETURNS numeric
AS 'SELECT $1.price * $2' LANGUAGE SQL;

SELECT price_extension(item, 10) FROM on_hand;

在您建立資料表的時候,也會自動建立一個復合類型,名字與資料表名字相同,資料表示該資料表的復合類型。 比如,如果我們說過

CREATE TABLE inventory_item (
    name            text,
    supplier_id     integer REFERENCES suppliers,
    price           numeric CHECK (price > 0)
);

然後,和上面顯示的相同的 inventory_item 復合類型也會作為副產品建立, 並且可以和上面一樣使用。不過,需要注意目前的實現的一個重要限制:因為現在還沒有約束和復合類型聯結, 所以在資料表定義中顯示的約束並不適用於資料表之外的復合類型。 (一個部分繞開的辦法是使用域類型作為復合類型的成員。)

8.11.2. 復合類型值輸入

要以文本常量書寫復合類型值,在圓括弧裡包圍字串值並且用逗號分隔他們。 您可以在任何字串值周圍放上雙引號,如果值本身包含逗號或者圓括弧,您必須用雙引號括起。 (更多細節見下面。)因此,復合類型常量的一般格式如下:

'( val1 , val2 , ... )'

一個例子是

'("fuzzy dice",42,1.99)'

如果 inventory_item 類型在前面已經定義了,那麼這是一個合法的數值。 要讓一個字串是空,那麼在列資料表裡它的位置上不要寫任何字元。比如,下面這個常量在第三個字串聲明一個 NULL:

'("fuzzy dice",42,)'

如果您想要一個空字串,而不是 NULL,寫一對雙引號:

'("",42,)'

這裡的第一個字串是一個非 NULL 空字串,第三個字串是 NULL。

(這些常量實際上只是我們在 Section 4.1.2.5 討論的一般類型常量的一個特殊例子。 這些常量一開始只是當作字串,然後傳遞給復合類型輸入轉換過程。一個明確的類型聲明可能是必須的。)

我們也可以用 ROW 資料表達式語法來構造復合類型值。 在大多數場合下,這種方法都比用字串文本的語法更簡單,因為您不用操心多重引號。 我們已經在上面使用了這種方法了:

ROW('fuzzy dice', 42, 1.99)
ROW('', 42, NULL)

只要您在資料表達式裡有超過一個字串,那麼關鍵字 ROW 就實際上是可選的, 所以可以簡化為

('fuzzy dice', 42, 1.99)
('', 42, NULL)

ROW 資料表達式語法在Section 4.2.11 裡有更詳細的討論。

8.11.3. 訪問復合類型

要訪問復合類型字串的一個域,我們寫出一個點以及域的名字,非常類似從一個資料表名字裡選出一個字串。 實際上,因為實在太像從資料表名字中選取字串,所以我們經常需要用圓括弧來避免分析器混淆。 比如,您可能需要從 on_hand 例子資料表中選取一些子域,像下面這樣:

SELECT item.name FROM on_hand WHERE item.price > 9.99;

這樣將不能工作,因為根據 SQL 語法, item 是從一個資料表名字選取的,而不是一個域名字。 您必須像下面這樣寫:

SELECT (item).name FROM on_hand WHERE (item).price > 9.99;

或者如果您也需要使用資料表名字(比如,在一個多資料表查詢裡),這麼寫:

SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9.99;

現在圓括弧對像正確地解析為一個指向 item 字串的引用,然後就可以從中選取子域。

類似的語法問題適用於在任何地點從一個復合類型值中查詢一個域。 比如,要從一個返回復合類型值的函數中只選許一個字串,您需要寫像下面這樣的東西

SELECT (my_func(...)).field FROM ...

如果沒有額外的圓括弧,會產生一個語法錯誤。

8.11.4. 修改復合類型

下面是一些插入和更新復合類型字串的正確語法。首先,插入或者更新整個字串:

INSERT INTO mytab (complex_col) VALUES((1.1,2.2));

UPDATE mytab SET complex_col = ROW(1.1,2.2) WHERE ...;

第一個例子省略了 ROW,第二個使用它;我們用哪種方法都行。

我們可以更新一個復合字串的獨立子域:

UPDATE mytab SET complex_col.r = (complex_col).r + 1 WHERE ...;

請注意,這裡我們不需要(實際上是不能)在 SET 後面出現的字串名周圍放上圓括弧, 但是我們在等號右邊的資料表達式裡引用同一個字串的時候卻需要圓括弧。

我們也可以聲明子域是 INSERT 的目標:

INSERT INTO mytab (complex_col.r, complex_col.i) VALUES(1.1, 2.2);

如果我們沒有為字串的所有子域提供數值,那麼剩下的子域將用空值填充。

8.11.5. 復合類型輸入和輸出語法

一個復合類型的文本資料表現形式包含那些根據獨立的子域類型各自 I/O 轉換規則解析的項, 加上一些資料表明這是復合結構的修飾。這些修飾包括整個數值周圍的圓括弧(()), 加上相鄰域之間的逗號(,)。圓括弧外面的空白被忽略,但是在圓括弧裡面, 它被當作子域數值的一部分,根據該子域的資料類型,這些空白可能有用,也可能沒用。 比如,在

'(  42)'

裡,如果子域類型是整數,那麼空白將被忽略,但是如果是文本,那麼就不會忽略。

如前面顯示的那樣,在給一個復合類型寫數值的時候,您可以在獨立的子域數值周圍用雙引號包圍。 如果子域數值會導致復合數值分析器歧義,那麼您必須這麼做。 特別是子域包含圓括弧,逗號,雙引號,或者反斜槓的場合,必須用雙引號括起來。 要想在雙引號括起來的子域數值裡面放雙引號,那麼您需要在它前面放一個反斜槓。 (同樣,在一個雙引號括起的子域數值裡面的一對雙引號資料表示一個雙引號字元,就像 SQL 字串文本的單引號規則一樣。) 另外,您可以用反斜槓逃逸的方法保護所有可能會當作復合類型語法的資料字元。

一個完全空的子域數值(在逗號或者逗號與圓括弧之間沒有字元)資料表示一個 NULL。 要寫一個空字串,而不是一個 NULL,寫 ""

假如子域數值是空字串或者包含圓括弧,逗號,雙引號,反斜槓或者空白,復合類型輸出過程會在子域數值周圍放上雙引號。 (為空白這麼處理不是必須的,但是可以增強易讀性。)在一個子域數值裡面嵌入的雙引號和反斜槓將會寫成兩份。

注意: 請注意您寫得任何 SQL 命令都首先被當作字串文本解析,然後才當作復合類型。 這就加倍了您需要的反斜槓數目。比如,要插入一個包含雙引號和一個反斜槓的 text 子域到一個復合類型數值裡, 您需要寫

INSERT ... VALUES ('("\\"\\\\")');

字串文本處理器先吃掉一層反斜槓,這樣到大復合類型分析器的東西看起來像 ("\"\\")。 然後,字串填給 text 資料類型的輸入過程,變成 "\。 (如果我們面對的資料類型還會對反斜槓另眼相看,比如 bytea, 那麼我們可能需要在命令裡多達八個反斜槓以獲取在儲存的復合類型子域中有一個反斜槓。) 美元符包圍(參閱 Section 4.1.2.2)可以用於避免雙份反斜槓的問題。

提示: 在 SQL 命令裡寫復合類型值的時候,ROW 構造器通常比復合文本語法更容易使用。 在 ROW 裡,獨立的子域數值的寫法和並非作為復合類型的成員書寫的方法一樣。