8.11. 複合型

複合型は行もしくはレコードの構造を記述します。 基本的には、これは単なるフィールド名とそのデータ型の列挙です。 PostgreSQLでは、単純な型が使用できる方法と同じ方法で複合型の値を使用できます。 例えば、テーブル列は複合型の型のものとして宣言することができます。

8.11.1. 複合型の宣言

複合型の宣言の例を以下に2つ示します。

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にするには、リスト中の該当位置を空にします。 例えば、以下の定数は3番目のフィールドにNULLを指定しています。

'("fuzzy dice",42,)'

NULLではなく空文字列にしたいのであれば、以下のように引用符を二重に記述します。

'("",42,)'

これにより、最初のフィールドは非NULLの空文字列に、3番目のフィールドはNULLになります。

(実際には、こうした定数は項4.1.2.5で説明した一般的な型の定数の特殊な場合に過ぎません。 定数はまず、文字列として扱われ、複合型の入力変換処理に渡されます。 明示的な型指定が必要になるかもしれません。)

また、ROW式構文も複合値を生成する際に使用されます。 おそらくほとんどの場合、これは文字列リテラル構文よりも簡単に使用できます。 引用符付けの複数層について考慮する必要がないからです。 もう既にこの方法を使用しています。

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

式の中に2つ以上のフィールドがあれば、ROWキーワードは実際には省略することができます。 ですので、以下のように簡略化することができます。

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

ROW式構文については項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列への参照として解釈され、サブフィールドはそこから選択できるようになります。

似たような構文上の問題は、複合型からフィールドを選択する時常に発生します。 例えば、複合型の値を返す関数の結果から1つだけフィールドを選択する場合、以下のように記述しなければなりません。

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を省略し、2番目の例では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);

列のサブフィールド全ての値を与えていなければ、残りのサブフィールドはNULL値になります。

8.11.5. 複合型の入出力構文

複合型の外部テキスト表現は、個々のフィールド用のI/O変換規則に従って解釈される項目群と複合構造を意味する飾りから構成されます。 この飾りは、値全体を括る括弧((および))と隣接した項目間のカンマ(,)で構成されます。 括弧の外側の空白文字は無視されますが、括弧の内部ではフィールド値の一部とみなされます。 ただし、意味があるかないかについては、そのフィールドのデータ型用の入力変換規則に従います。 例えば、

'(  42)'

内の空白文字は、そのフィールド型が整数の場合は無視されますが、テキストの場合は無視されません。

前述の通り、複合型の値を記述する時に個々のフィールド値を二重引用符で括ることができます。 もし、フィールド値が複合型値用のパーサを混乱させる場合には、これは必須です。 具体的には、括弧、カンマ、二重引用符、バックスラッシュを含むフィールドの場合、二重引用符で括る必要があります。 引用符で括った複合型のフィールド値内に二重引用符やバックスラッシュが存在する場合、その前にバックスラッシュを付けてください (また、引用符で括った複合型のフィールド値内に二重の引用符の組み合わせがあると、これは二重引用符を表す文字として解釈されます。 これは、SQLリテラル文字列内の単一引用符の規則と同じです)。 そのままでは複合型に対する構文として解釈されてしまう、全てのデータ文字を保護する他の方法として、バックスラッシュによるエスケープを使用することができます。

完全な空フィールド値(カンマや括弧の間にまったく文字がないもの)はNULLを表します。 NULLではなく空文字列を値として記述するには ""と記述してください。

複合型の出力処理では、もしフィールド値が空文字列の場合や括弧、カンマ、二重引用符、バックスラッシュ、空白文字を含む場合には、そのフィールド値を二重引用符で括って出力します (空白文字に対するこの処理は重要ではありませんが、可読性を高めます)。 フィールド値内に埋め込まれた二重引用符やバックスラッシュは二重化されます。

注意: SQLコマンド内部に記述したものは、まず文字列リテラルとして、その後複合型として解釈されることは覚えてください。 これは必要なバックスラッシュの数を倍にします(エスケープ文字列構文を仮定)。 例えば、複合型の値の中に二重引用符とバックスラッシュを持つtextフィールドに挿入するには、以下のように書かなければなりません。

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

文字列リテラルプロセッサで第1レベルのバックスラッシュを取り除きます。 そのため、複合型値のパーサに渡されるものは("\"\\")のようになります。 そして、textデータ型の入力関数に渡される文字列は"\になります (もし、例えばbyteaといった、その入力関数もバックスラッシュを特別に扱うデータ型を扱っている場合、1つのバックスラッシュを複合型のフィールドに格納するためにコマンド内に8個ものバックスラッシュが必要になります)。 ドル引用符付け(項4.1.2.2を参照)を使用して、このバックスラッシュの二重化を防ぐことができます。

ティップ: SQLコマンド内に複合型の値を書く時、通常、ROW生成構文の方が複合型のリテラル構文より作業が簡単です。 ROWでは、複合型のメンバ以外の記述方法と同じ方法で個々のフィールド値を記述することができます。