テーブルから始める
支払い
+ ------------------------------ +
| customer_id | 金額| アイテム|
| 5 | 10 | 本|
| 5 | 71 | マウス|
| 7 | 13 | カバー|
| 7 | 22 | ケーブル|
| 7 | 19 | 本|
+ ------------------------------ +
SELECT customer_id,
AVG(amount) OVER (PARTITION BY customer_id) AS avg_amount,
item,
FROM payments`
我々が得る
+ ---------------------------------- +
| customer_id | avg_amount | アイテム|
| 5 | 40.5 | 本|
| 5 | 40.5 | マウス|
| 7 | 18 | カバー|
| 7 | 18 | ケーブル|
| 7 | 18 | 本|
+ ---------------------------------- +
AVG
集計関数であるため、ウィンドウ関数として機能できます。ただし、すべてのウィンドウ関数が集計関数であるとは限りません。集計関数は、洗練されていないウィンドウ関数です。
上記のクエリでは、組み込みAVG
関数を使用せず、独自の実装を使用しましょう。同じことを行いますが、ユーザーが実装しただけです。上記のクエリは次のようになります。
SELECT customer_id,
my_avg(amount) OVER (PARTITION BY customer_id) AS avg_amount,
item,
FROM payments`
以前のクエリとの唯一の違いは、AVG
に置き換えられていることmy_avg
です。次に、カスタム関数を実装する必要があります。
平均の計算方法について
すべての要素を合計してから、要素の数で割ります。customer_id
7の場合、それはになります(13 + 22 + 19) / 3 = 18
。私たちはそれを次のように考案することができます:
- ステップバイステップの累積-合計。
- 最終操作-除算。
集計関数が結果に到達する方法について
平均は段階的に計算されます。最後の値のみが必要です。初期値0から始めます。
- フィード13.中間/累積合計を計算します。これは13です。
- フィード22.累積合計を計算します。これには、前の合計とこの要素が必要です。
13 + 22 = 35
- フィード19.前の合計とこの要素を必要とする累積合計を計算します
35 + 19 = 54
。これは、要素の数(3)で割る必要がある合計です。
- 手順3の結果は、累積合計を要素数で除算する方法を知っている別の関数に送られます。
ここで起こったことは、状態が初期値0から始まり、ステップごとに変更されてから、次のステップに渡されることです。
データがある限り、状態はステップ間を移動します。すべてのデータが消費されると、状態は最終機能(端末操作)になります。状態には、アキュムレータと端末操作に必要なすべての情報が含まれている必要があります。
平均を計算する特定のケースでは、端末操作は、アキュムレータで除算する必要があるため、アキュムレータが処理した要素の数を知る必要があります。そのため、状態には累積合計と要素数の両方を含める必要があります。
両方を含むタプルが必要です。事前定義されPOINT
たPostgreSQLタイプが役に立ちます。POINT(5、89)は、89の値を持つ5つの要素の累積合計を意味します。初期状態はPOINT(0,0)になります。
アキュムレータは、いわゆる状態関数で実装されます。ターミナル操作は、いわゆる最終関数で実装されます。
カスタム集計関数を定義するときは、以下を指定する必要があります。
- 集計関数名と戻り値のタイプ
- 初期状態
- インフラストラクチャがステップ間および最終機能に渡される状態のタイプ
- 状態関数-累積ステップを実行する方法を知っています
- 最後の機能-端末操作の実行方法を知っています。常に必要なわけではありません(たとえば、SUMのカスタム実装では、累積合計の最終値が結果になります)。
カスタム集計関数の定義は次のとおりです。
CREATE AGGREGATE my_avg (NUMERIC) ( -- NUMERIC is what the function returns
initcond = '(0,0)', -- this is the initial state of type POINT
stype = POINT, -- this is the type of the state that will be passed between steps
sfunc = my_acc, -- this is the function that knows how to compute a new average from existing average and new element. Takes in the state (type POINT) and an element for the step (type NUMERIC)
finalfunc my_final_func -- returns the result for the aggregate function. Takes in the state of type POINT (like all other steps) and returns the result as what the aggregate function returns - NUMERIC
);
残っているのは、2つの関数my_acc
とを定義することだけですmy_final_func
。
CREATE FUNCTION my_acc (state POINT, elem_for_step NUMERIC) -- performs accumulated sum
RETURNS POINT
LANGUAGE SQL
AS $$
-- state[0] is the number of elements, state[1] is the accumulated sum
SELECT POINT(state[0]+1, state[1] + elem_for_step);
$$;
CREATE FUNCTION my_final_func (POINT) -- performs devision and returns final value
RETURNS NUMERIC
LANGUAGE SQL
AS $$
-- $1[1] is the sum, $1[0] is the number of elements
SELECT ($1[1]/$1[0])::NUMERIC;
$$;
CREATE AGGREGATE
これで、上記で定義された関数が使用可能になり、正常に実行されます。my_avg
集計が定義されたので、組み込みではなくに基づくクエリをAVG
実行できます。
SELECT customer_id,
my_avg(amount) OVER (PARTITION BY customer_id) AS avg_amount,
item,
FROM payments`
結果は、組み込みを使用した場合と同じですAVG
。
PostgreSQLのドキュメントでは、ユーザーはユーザー定義の集計関数の実装に限定されているとされています。
これらの[事前定義されたウィンドウ]関数に加えて、組み込みまたはユーザー定義の汎用または統計集計(つまり、順序集合または仮想集合ではない)をウィンドウ関数として使用できます。
私が疑うordered-set or hypothetical-set aggregates
ことは:
- 返される値は他のすべての行と同じです(たとえば
AVG
、およびSUM
。対照的RANK
に、より高度な基準に応じて、グループ内のすべての行に対して異なる値を返します)
- いずれにせよ、値はすべての行で同じであるため、PARTITIONing時にORDERBYを使用しても意味がありません。対照的に、私たちは
ORDER BY
使用するときにしたいRANK()
クエリ:
SELECT customer_id, item, rank() OVER (PARTITION BY customer_id ORDER BY amount desc) FROM payments;
幾何平均
以下は、組み込みの集計が見つからなかったユーザー定義の集計関数であり、一部のユーザーにとって役立つ可能性があります。
状態関数は、項の自然対数の平均を計算します。
e
最後の関数は、アキュムレータが提供するものに定数を上げます。
CREATE OR REPLACE FUNCTION sum_of_log(state POINT, curr_val NUMERIC)
RETURNS POINT
LANGUAGE SQL
AS $$
SELECT POINT(state[0] + 1,
(state[1] * state[0]+ LN(curr_val))/(state[0] + 1));
$$;
CREATE OR REPLACE FUNCTION e_to_avg_of_log(POINT)
RETURNS NUMERIC
LANGUAGE SQL
AS $$
select exp($1[1])::NUMERIC;
$$;
CREATE AGGREGATE geo_mean (NUMBER)
(
stype = NUMBER,
initcond = '(0,0)', -- represent POINT value
sfunc = sum_of_log,
finalfunc = e_to_avg_of_log
);