33.9. 用戶定義聚集

PostgreSQL裡的聚集是用狀態值狀態轉換函數表達的。 也就是說,一個聚集可以定義為一些狀態, 當一條輸入的條目被處理時,這些狀態被修改。 要定義一個新的聚集函數,我們就要選擇一個表示狀態值的數據類型, 一個狀態初始值,一個狀態轉換函數。 該狀態轉換函數只是一個普通函數,也可以用于聚集的環境之外。 還可以聲明一個終處理函數, 用于對付當期望的聚集結果和需要保留在運行的狀態值裡面的數據不同的情況。

因此,除了被聚集用戶看到的參數和結果數據類型外,還有一種內部狀態值數據類型,這種類型可能和參數與結果類型都不一樣。

如果我們定義了一個不使用終處理函數的聚集, 那我們的聚集就是對每條記錄的字段值進行函數計算。 sum(求和)是這類聚集的例子。 sum從零開始,每次都向運行總和追加當前記錄值。比如,如果我們要把 Sum 聚集用于復數,我們只需要該數據類型的加法函數就行了。 該聚集可以這樣定義:

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

SELECT complex_sum(a) FROM test_complex;

 complex_sum
-------------
 (34,53.9)

(實際上,我們會把這個聚集命名為 sum,然後讓 PostgreSQL 來區分對一個類型為 complex 的列應該使用哪種 sum。)

如果不存在非空輸入值,上面的sum定義將返回零值(初始狀態條件)。 可能我們在那種情況下會希望返回NULL--SQL標準要求 sum 的行為是那樣的。 我們只需要忽略initcond段就可以實現那一點, 這樣初始狀態條件是 NULL。 通常這也意味著sfunc需要檢查 NULL 狀態條件輸入, 不過對于 sum 和一些象MaxMin這樣的簡單聚集來說, 把第一個非空輸入插入到狀態值裡面, 然後從第二個非空輸入狀態值開始使用轉換函數就足夠了。 如果初始條件是NULL並且轉換函數標記為"strict", (也就是說,不能對NULL輸入調用。) PostgreSQL 就會自動處理這些內容。

另外一個"strict"轉換函數的缺省特性是:當碰到一個NULL輸入的時候, 前面一個狀態轉換函數會被保留下來不做改動。 這樣,就忽略了NULL。如果你希望對空值輸入有其它處理,只需要 別把你的轉換函數定義為嚴格的然後編寫代碼的時候 測試NULL並做相應處理即可。

avg(平均)是更復雜一點的聚集的例子。它需要兩個運行時狀態: 輸入的總和以及輸入數量的計數。最終結果是通過把這些量相除得到的。 平均的典型實現是用兩元素數組做狀態值。比如,內建的 avg(float8)實現是這樣的:

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

聚集函數可以使用多態轉換函數或者終處理函數, 這樣,同一個函數可以用于實現多個聚集。 參閱 Section 33.2.5 獲取多態函數的解釋。 再進一步,聚集函數本身可以用多態的基本類型和狀態類型來聲明, 這樣就允許一個聚集定義用于多種輸入數據類型。 下面是一個多態聚集的例子:

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

這裡,任意聚集調用的實際狀態類型是和元素輸入類型相同的數組類型。

下面是使用兩個不同實際數據類型作為元素的輸出:

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

 attrelid |                                 array_accum
----------+-----------------------------------------------------------------------------
 pg_user  | {usename,usesysid,usecreatedb,usesuper,usecatupd,passwd,valuntil,useconfig}
(1 row)

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

 attrelid |         array_accum
----------+------------------------------
 pg_user  | {19,23,16,16,16,25,702,1009}
(1 row)

更詳細的信息請參考 CREATE AGGREGATE 命令。