33.10. ユーザ定義の集約

PostgreSQLにおける集約関数は、状態値状態遷移関数で表現されています。 つまり、1つの集約は入力行が処理される度に変更される状態値を使用して演算されます。 新しい集約関数を定義するためには、状態値のデータ型、状態の初期値、そして状態遷移関数を選択します。 状態遷移関数はただの普通の関数で、集約以外の文脈でも使うことができます。 求められる集約の結果が動いている状態値の中に保持しなければならないデータと違う場合は、最終関数も指定することができます。

したがって、集約のユーザに見える引数と結果のデータ型に加え、引数と結果の型のどちらとも違う内部状態値のデータ型があります。

もし最終関数を使わない集約を定義したとすると、それぞれの行の列値で変動する関数で計算された結果の集約ができます。 sumはそのような集約の一例です。 sumは0から始まり、常に現在の行の値をその時点までの総和に追加します。 例えば、もしsum集約を複素数のデータ型で動作するようにしたければ、そのデータ型の加算関数だけが必要になります。 集約定義は以下のようになります。

CREATE AGGREGATE sum (complex)
(
    sfunc = complex_add,
    stype = complex,
    initcond = '(0,0)'
);

SELECT sum(a) FROM test_complex;

   sum
-----------
 (34,53.9)

(関数のオーバーライド機能に依存していることに注意してください。 sumという名前の集約関数は複数存在しますが、PostgreSQLは列の型complexに適用できるsum関数を見つけ出すことができます。)

上記のsumの定義は、もし非NULLの入力値がなければ0(初期状態)を返します。 その場合本来は代わりにNULLを返したいのではないかと思いますし、標準SQLではsumがそのように動作することを期待しています。 そうするためには単にinitcond句を省略すれば、初期状態がNULLになります。 通常はそうすると、sfuncがNULL状態の入力をチェックしなければいけないということを意味しますが、sumや、その他maxminのような単純な集約にとっては、最初の非NULL入力値を状態変数に挿入し、2番目の非NULL入力値で状態遷移関数を当てはめ始めれば十分です。 PostgreSQLは、もし初期状態がNULLで状態遷移関数が"厳密(strict)"と設定されている場合、自動的にそれを実行します(つまりNULL入力では呼び出されないようになります)。

もう1つの"厳密"な状態遷移関数のデフォルト動作としては、NULL入力値が現れると前の状態値が変わらずに維持されるということがあります。 したがって、NULL値は無視されます。 もしNULL入力に対し他の動作が必要な場合は、状態遷移関数を厳密として定義しないで、NULL入力の検査を行うようにコーディングし、必要なことをすればよいのです。

avg(平均値計算)はもっと複雑な集約の一例です。 それには2つの変動する状態が必要になります。入力の合計と入力数のカウントです。 最終的な結果はこれらの値を割算することによって得られます。 平均値計算は典型的に2要素の配列を状態遷移値として使って実装されます。 例えば、avg(float8)の組み込みの実装は以下のようになっています。

CREATE AGGREGATE avg (float8)
(
    sfunc = float8_accum,
    stype = float8[],
    finalfunc = float8_avg,
    initcond = '{0,0}'
);

集約関数は多様状態遷移関数や多様最終関数を使用することができます。 これにより、同じ関数を使用して複数の集約を実装することができます。 項33.2.5に多様関数の説明があります。 もう少し細かく言うと、集約関数自身は、単一の集約定義で複数の入力データ型を扱うことができるように、多様入力型と多様状態型を指定することができるということです。 以下に多様集約の例を示します。

CREATE AGGREGATE array_accum (anyelement)
(
    sfunc = array_append,
    stype = anyarray,
    initcond = '{}'
);

ここで、集約を行う呼び出し用の実際の状態は、その実際の入力型がその要素となる、配列型です。

以下に異なる2つの実データ型を引数として使用した出力例を示します。

SELECT attrelid::regclass, array_accum(attname)
    FROM pg_attribute
    WHERE attnum > 0 AND attrelid = 'pg_tablespace'::regclass
    GROUP BY attrelid;

   attrelid    |              array_accum              
---------------+---------------------------------------
 pg_tablespace | {spcname,spcowner,spclocation,spcacl}
(1 row)

SELECT attrelid::regclass, array_accum(atttypid)
    FROM pg_attribute
    WHERE attnum > 0 AND attrelid = 'pg_tablespace'::regclass
    GROUP BY attrelid;

   attrelid    |   array_accum   
---------------+-----------------
 pg_tablespace | {19,26,25,1034}
(1 row)

C言語で作成された関数は、例えば以下のように関数呼び出し"コンテキスト"としてAggStateノードとして渡されたかどうかを確認することで、集約の遷移関数として呼び出されたか最終関数として呼び出されたかを検出することができます。

        if (fcinfo->context && IsA(fcinfo->context, AggState))

この点検を行う理由の1つとして、これが真の場合に先頭の入力は一時的な遷移中の値であるはずであり、新規に割り当ててコピーを持つことなくそのまま変更しても安全であることが分かることがあります(これは関数内で参照渡しの入力を安全に変更できる唯一の場合です)。 例としては、int8inc()を参照してください。

さらに詳しい情報は、CREATE AGGREGATEコマンドを参照してください。