無料スクリプト配布のPHP.TO   PHPの実用的なtips PHPマニュアル MySQLマニュアル Apacheマニュアル PostgreSQLマニュアル マニュアル検索    

35.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 や、その他 max min のような単純な集約にとっては、最初の非NULL入力値を状態変数に挿入し、2番目の非NULL入力値で状態遷移関数を当てはめ始めれば十分です。 PostgreSQL は、もし初期状態がNULLで状態遷移関数が "厳密(strict)" と設定されている場合、自動的にそれを実行します(つまりNULL入力では呼び出されないようになります)。

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

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

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

float8_accum は、入力の総和と個数だけではなく二乗和も蓄積しますので、2要素ではなく、3要素の配列を必要とします。 avg 以外の何らかの集約でも使用できるようにするためです。)

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

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

ここで、集約を行う呼び出し用の実際の状態型は、その実際の入力型がその要素となる、配列型です。 集約の動作はその型の配列に全ての入力を連結することです。 (組み込みの集約関数 array_agg は、この定義での動作よりもより良い性能で、類似の機能を提供します。)

以下に異なる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,spcacl,spcoptions}
(1 row)

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

   attrelid    |        array_accum        
---------------+---------------------------
 pg_tablespace | {name,oid,aclitem[],text[]}
(1 row)

C言語で作成された関数は、例えば以下のように AggCheckCallContext を呼び出して、集約の遷移関数または最終関数が呼び出されたかを検出することができます。

if (AggCheckCallContext(fcinfo, NULL))

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

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


powered by SEO.CUG.NET