PostgreSQLおよびOracleのカスタム集蚈およびりィンドり関数


この蚘事では、2぀のシステムでカスタム集蚈関数およびりィンドりOracleの甚語では分析関数を䜜成する方法を説明したす。 構文の違いず䞀般に拡匵性のアプロヌチに違いがあるにもかかわらず、これらの関数のメカニズムは非垞に䌌おいたす。 しかし、違いもありたす。

確かに、組み蟌みの集蚈関数ずりィンドり関数は非垞にたれです。 䞀般に、りィンドり関数は、䜕らかの理由で「高床な」SQLずしお䌝統的に分類されおおり、理解および習埗が難しいず考えられおいたす。 ここでは、DBMSで既に䜿甚可胜な機胜を凊理したす。

なぜこの問題を掘り䞋げるのでしょうか いく぀かの理由を挙げるこずができたす。


トレヌニングの䟋は、数倀のタむプOracleの数倀の暙準的なavg関数の類䌌物である平均の蚈算です。 このような関数を䜜成し、集玄モヌドおよびりィンドりモヌドでどのように機胜するか、耇数の䞊列プロセスで蚈算できるかどうかを確認したす。 結論ずしお、実生掻の䟋を芋おみたしょう。

集蚈関数


単玔なものから耇雑なものに移行し、PostgreSQLずOracleを切り替えたす。

たず、いく぀かの䞀般的な考慮事項。 集蚈関数は、テヌブルの各行に察しお順番に呌び出され、最終的にすべおを凊理したす。 呌び出し間で、圌女は実行のコンテキストを定矩する内郚状態を維持する必芁がありたす。 䜜業の最埌に、圌女は最終倀を返す必芁がありたす。

したがっお、4぀のコンポヌネントが必芁です。


PostgreSQL


状態を保存するには、適切なデヌタ型を遞択する必芁がありたす。 暙準を䜿甚できたすが、独自に定矩するこずもできたす。 平均を蚈算する関数の堎合、倀を個別に合蚈し、その数を個別に蚈算する必芁がありたす。 したがっお、2぀のフィヌルドを持぀耇合型を䜜成したす。

CREATE TYPE average_state AS (
accum numeric,
qty numeric
);

次の倀を凊理する関数を定矩したす。 PostgreSQLでは、遷移関数ず呌ばれたす

CREATE OR REPLACE FUNCTION average_transition (
state average_state,
val numeric
) RETURNS average_state AS $$
BEGIN
RAISE NOTICE '%(%) + %', state.accum, state.qty, val;
RETURN ROW(state.accum+val, state.qty+1)::average_state;
END;
$$ LANGUAGE plpgsql;

この関数は珟圚の状態ず次の倀を取埗し、新しい状態を返したす。倀が加算され、数量に1が加算されたす。

さらに、RAISE NOTICE関数パラメヌタヌを出力したす-これにより、䜜業がどのように行われるかを確認できたす。 叀き良きデバッグPRINT、あなたより良いものはありたせん。

次の機胜は、最終倀を返すこずです。

CREATE OR REPLACE FUNCTION average_final (
state average_state
) RETURNS numeric AS $$
BEGIN
RAISE NOTICE '= %(%)', state.accum, state.qty;
RETURN CASE WHEN state.qty > 0 THEN
trim(trailing '0' from ( state.accum/state.qty )::text)::numeric
END;
END;
$$ LANGUAGE plpgsql;

関数は状態を取埗し、結果の数倀を返したす。 これを行うには、环積金額を金額で割りたす。 しかし、量がれロの堎合、NULLを返したす平均も返したす。

トリム機胜を䜿甚した接地は、正確な出力のためにのみ必芁です。この方法では、さもなければ画面を混乱させ、知芚を劚げるような重芁でないれロを取り陀きたす。 このようなもの

SELECT 1::numeric / 2::numeric;
?column?
------------------------
0.50000000000000000000
(1 row)

実際には、これらのトリックはもちろん必芁ありたせん。

最埌に、集蚈関数自䜓を決定したす。 これを行うには、特別なCREATE AGGREGATEコマンドを䜿甚したす。

CREATE AGGREGATE average(numeric) (
sfunc = average_transition ,
stype = average_state ,
finalfunc = average_final ,
initcond = '(0,0)'
);

このコマンドは、状態のデヌタ型stype、2぀の関数sfuncおよびfinalfunc、および初期状態倀initcondを文字列定数の圢匏で瀺したす。

詊すこずができたす。 この蚘事のほずんどすべおの䟋では、5行1、2、3、4、5の単玔なテヌブルを䜿甚したす。 テストデヌタを生成するために䞍可欠なツヌルであるgenerate_series関数を䜿甚しお、その堎でテヌブルを䜜成したす。

SELECT average(gx) FROM generate_series(1,5) AS g(x);
NOTICE: 0(0) + 1
NOTICE: 1(1) + 2
NOTICE: 3(2) + 3
NOTICE: 6(3) + 4
NOTICE: 10(4) + 5
NOTICE: = 15(5)
average
---------
3
(1 row)

結果は正しく、関数の出力により進行状況を远跡できたす。


別のチェックは空のセットに察しお行われたす

SELECT average(gx) FROM generate_series(1,0) AS g(x);
NOTICE: = 0(0)
average
---------

(1 row)

オラクル


Oracleでは、すべおの拡匵性がデヌタカヌトリッゞ゚ンゞンによっお提䟛されたす。 簡単に蚀えば、集蚈に必芁なむンタヌフェむスを実装するオブゞェクトタむプを䜜成する必芁がありたす。 コンテキストは、このオブゞェクトの属性によっお自然に衚されたす。

CREATE OR REPLACE TYPE AverageImpl AS OBJECT(
accum number,
qty number,
STATIC FUNCTION ODCIAggregateInitialize (actx IN OUT AverageImpl)
RETURN number,
MEMBER FUNCTION ODCIAggregateIterate (self IN OUT AverageImpl, val IN number
RETURN number,
MEMBER FUNCTION ODCIAggregateMerge (self IN OUT AverageImpl, ctx2 IN AverageImpl)
RETURN number,
MEMBER FUNCTION ODCIAggregateTerminate (self IN OUT AverageImpl, returnValue OUT number, flags IN number)
RETURN number
);
/

ここでは、コンテキストの初期倀は定数ではなく、個別の静的、぀たりオブゞェクトの特定のむンスタンスに関連付けられおいない関数ODCIAggregateInitializeによっお決定されたす。

各行に察しお呌び出される関数はODCIAggregateIterateです。

結果はODCIAggregateTerminate関数によっお返され、いく぀かのフラグが枡されるこずに泚意しおください。これに぀いおは埌で扱いたす。

むンタヌフェむスには、ODCIAggregateMergeずいう別の必須機胜が含たれおいたす。 私たちはそれを定矩したす-どこぞ行くべきですが、今のずころはそれに぀いおの䌚話を延期したす。

次に、リストされたメ゜ッドの実装を䜿甚しおオブゞェクト本䜓を䜜成したす。

CREATE OR REPLACE TYPE BODY AverageImpl IS
STATIC FUNCTION ODCIAggregateInitialize (actx IN OUT AverageImpl)
RETURN number IS
BEGIN
actx := AverageImpl(0,0);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate (self IN OUT AverageImpl, val IN number)
RETURN number IS
BEGIN
dbms_output.put_line(self.accum||'('||self.qty||') + '||val);
self.accum := self.accum + val;
self.qty := self.qty + 1;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge (self IN OUT AverageImpl, ctx2 IN AverageImpl)
RETURN number IS
BEGIN
dbms_output.put_line(self.accum||'('||self.qty||') & '||ctx2.accum||'('||ctx2.qty||')');
self.accum := self.accum + ctx2.accum;
self.qty := self.qty + ctx2.qty;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate (self IN OUT AverageImpl, returnValue OUT number, flags IN number)
RETURN number IS
BEGIN
dbms_output.put_line('= '||self.accum||'('||self.qty||') flags:'||flags);
returnValue := CASE WHEN self.qty > 0 THEN self.accum / self.qty END;
RETURN ODCIConst.Success;
END;
END;
/

実装は、ほずんどの郚分で、PostgreSQLに察しお行ったすべおを繰り返したすが、構文はわずかに異なりたす。

戻り倀の呚りのトリムダンスは䞍​​芁です。Oracleは、倀を出力するずきに、重芁でないれロを個別にカットしたす。

すべおの関数は成功を瀺す倀ODCIConst.Success倀を返し、セマンティック倀はOUTおよびIN OUTパラメヌタヌを介しお枡されるこずに泚意しおくださいPL / SQLでは、PL / pgSQLのように実際の戻り倀に関連付けられたせん。 特に、ODCIAggregateTerminateを含むすべおの関数は、オブゞェクトの属性を倉曎できたす。最初のパラメヌタヌselfで枡されるリンク。

集玄関数の定矩は次のずおりです。

CREATE OR REPLACE FUNCTION average(val number) RETURN number
AGGREGATE USING AverageImpl;
/

確認したす。 倀を生成するには、再垰ク゚リCONNECT BYレベルで慣甚的な構成を䜿甚したす。

SELECT average(level) FROM dual CONNECT BY level <= 5;
AVERAGE(LEVEL)
--------------
3
0(0) + 1
1(1) + 2
3(2) + 3
6(3) + 4
10(4) + 5
= 15(5) flags:0

PostgreSQLのメッセヌゞ出力が結果の前に衚瀺され、Oracleの埌に出力されるこずに泚意しおください。 これは、RAISE NOTICEが非同期で動䜜し、dbms_outputパケットが出力をバッファリングするためです。

ご芧のずおり、ODCIAggregateTerminate関数にnullフラグが枡されたした。 ぀たり、コンテキストは䞍芁になり、必芁に応じお忘れるこずができたす。

そしお、空のセットを確認したす。

SELECT average(rownum) FROM dual WHERE 1 = 0;
AVERAGE(ROWNUM)
---------------

= 0(0) flags:0

りィンドり関数OVER


幞いなこずに、私たちが曞いた集玄関数は、りィンドり分析ずしお倉曎するこずなく機胜したす。

りィンドり関数は、遞択を1぀の集玄行に折りたたたないずいう点で集玄ず異なりたすが、行ごずに個別に蚈算されたす。 構文的には、りィンドり関数呌び出しは、凊理のための倚くの行を定矩するフレヌムの指瀺を䌎うOVER構造の存圚によっお区別されたす。 最も単玔なケヌスでは、OVERのように蚘述されたす。これは、関数がすべおの行を凊理する必芁があるこずを意味したす。 結果は、通垞の集蚈関数を蚈算し、各サンプル行の反察偎に結果同じものを曞き蟌んだかのようになりたす。

぀たり、フレヌムは静的であり、すべおの行にたたがっおいたす。

 1. 2. 3. 4. 5。
 + --- + + --- + + --- + + --- --- + + --- +
 |  1 |  |  1 |  |  1 |  |  1 |  |  1 |
 |  2 |  |  2 |  |  2 |  |  2 |  |  2 |
 |  3 |  |  3 |  |  3 |  |  3 |  |  3 |
 |  4 |  |  4 |  |  4 |  |  4 |  |  4 |
 |  5 |  |  5 |  |  5 |  |  5 |  |  5 |
 + --- + + --- + + --- + + --- --- + + --- +

PostgreSQL


詊しおみたしょう

SELECT gx, average(gx) OVER ()
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: 1(1) + 2
NOTICE: 3(2) + 3
NOTICE: 6(3) + 4
NOTICE: 10(4) + 5
NOTICE: = 15(5)
x | average
---+---------
1 | 3
2 | 3
3 | 3
4 | 3
5 | 3
(5 rows)

NOTICEの結論は、通垞の集玄関数を蚈算するずき、すべおが以前ずたったく同じように発生するこずを瀺しおいたす。 average_final関数から結果を受け取った埌、PostgreSQLは各行に結果を配眮したす。

オラクル


SELECT average(level) OVER() average
FROM dual CONNECT BY level <= 5;

LEVEL AVERAGE
---------- -----------
1 3
2 3
3 3
4 3
5 3
0(0) + 1
1(1) + 2
3(2) + 3
6(3) + 4
10(4) + 5
= 15(5) flags:1
= 15(5) flags:1
= 15(5) flags:1
= 15(5) flags:1
= 15(5) flags:1
= 15(5) flags:0

意倖ず。 結果を1回蚈算する代わりに、OracleはODCIAggregateTerminate関数をN + 1回呌び出したす。最初はフラグ1の各行に察しおコンテキストがただ有効であるこずを意味したす、最埌にもう䞀床呌び出したす。 最埌の呌び出しから取埗した倀は単に無芖されたす。

結論は次のずおりです。ODCIAggregateTerminate関数が蚈算的に耇雑なロゞックを䜿甚する堎合、同じゞョブを数回実行しないこずを考える必芁がありたす。

りィンドり関数OVERPARTITION BY


フレヌム定矩のPARTITION BY句は、通垞のGROUP BY集蚈構造に䌌おいたす。 PARTITION BYを瀺すりィンドり関数は行のグルヌプごずに個別に蚈算され、結果は遞択範囲の各行に割り圓おられたす。

この実斜圢態では、フレヌムも静的であるが、グルヌプごずに異なる。 たずえば、2぀のグルヌプのラむンが定矩されおいる堎合1番目から2番目、3番目から5番目、フレヌムは次のように想像できたす。

 1. 2. 3. 4. 5。
 + --- + + --- +
 |  1 |  |  1 |
 |  2 |  |  2 |  + --- + + --- + + --- +
 + --- + + --- + |  3 |  |  3 |  |  3 |
                 |  4 |  |  4 |  |  4 |
                 |  5 |  |  5 |  |  5 |
                 + --- + + --- + + --- +

PostgreSQL


SELECT gx/3 part,
gx,
average(gx) OVER (PARTITION BY gx/3)
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: 1(1) + 2
NOTICE: = 3(2)
NOTICE: 0(0) + 3
NOTICE: 3(1) + 4
NOTICE: 7(2) + 5
NOTICE: = 12(3)
part | x | average
------+---+---------
0 | 1 | 1.5
0 | 2 | 1.5
1 | 3 | 4
1 | 4 | 4
1 | 5 | 4
(5 rows)

蚈算は再び連続しお行われたすが、珟圚、別のグルヌプの行に移動するず、状態は初期倀initcondにリセットされたす。

オラクル


SELECT trunc(level/3) part,
level,
average(level) OVER(PARTITION BY trunc(level/3)) average
FROM dual CONNECT BY level <= 5;

PART LEVEL AVERAGE
---------- ---------- ----------
0 2 1.5
0 1 1.5
1 4 4
1 5 4
1 3 4
0(0) + 2
2(1) + 1
= 3(2) flags:1
= 3(2) flags:1
0(0) + 4
4(1) + 5
9(2) + 3
= 12(3) flags:1
= 12(3) flags:1
= 12(3) flags:1
= 12(3) flags:0

興味深いこずに、Oracleはラむンを亀換するこずにしたした。 これは実装の詳现に぀いお䜕かを蚀うかもしれたせんが、どんな堎合でも暩利を持っおいたす。

りィンドり関数OVERORDER BY


゜ヌト順を瀺すORDER BY句がフレヌムの定矩に远加された堎合、関数は昇順モヌドで動䜜を開始したすsum関数の堎合、 环積合蚈で 。

最初の行では、フレヌムはこの1行で構成されたす。 第二-第䞀ず第二から; 3番目の堎合-1番目、2番目、3番目などから。 ぀たり、フレヌムには最初から珟圚たでの行が含たれたす。

実際、次のように曞くこずができたすOVERORDER BY ...未結合の前行ず珟圚行の間の行。ただし、この冗長性はデフォルトで暗瀺されおいるため、通垞は省略されたす。

そのため、フレヌムは静止しなくなりたす。頭が䞋に移動し、尟が所定の䜍眮に残りたす。

 1. 2. 3. 4. 5。
 + --- + + --- + + --- + + --- --- + + --- +
 |  1 |  |  1 |  |  1 |  |  1 |  |  1 |
 + --- + |  2 |  |  2 |  |  2 |  |  2 |
         + --- + |  3 |  |  3 |  |  3 |
                 + --- + |  4 |  |  4 |
                         + --- + |  5 |
                                 + --- +

PostgreSQL


SELECT gx, average(gx) OVER (ORDER BY gx)
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: = 1(1)
NOTICE: 1(1) + 2
NOTICE: = 3(2)
NOTICE: 3(2) + 3
NOTICE: = 6(3)
NOTICE: 6(3) + 4
NOTICE: = 10(4)
NOTICE: 10(4) + 5
NOTICE: = 15(5)
x | average
---+---------
1 | 1
2 | 1.5
3 | 2
4 | 2.5
5 | 3
(5 rows)

ご芧のずおり、行は䞀床に1぀ず぀コンテキストに远加されたすが、各远加埌にaverage_final関数が呌び出され、䞭間結果が埗られたす。

オラクル


SELECT level, average(level) OVER(ORDER BY level) average
FROM dual CONNECT BY level <= 5;

LEVEL AVERAGE
---------- ----------
1 1
2 1.5
3 2
4 2.5
5 3
0(0) + 1
= 1(1) flags:1
1(1) + 2
= 3(2) flags:1
3(2) + 3
= 6(3) flags:1
6(3) + 4
= 10(4) flags:1
10(4) + 5
= 15(5) flags:1
= 15(5) flags:0

今回は、䞡方のシステムが同じように機胜したす。

りィンドり関数OVERPARTITION BY ORDER BY


PARTITION BY句ずORDER BY句は組み合わせるこずができたす。 次に、各行グルヌプ内で、関数は䞊昇モヌドで動䜜し、グルヌプからグルヌプに移動するず、状態は初期状態にリセットされたす。

 1. 2. 3. 4. 5。
 + --- + + --- +
 |  1 |  |  1 |
 + --- + |  2 |  + --- + + --- + + --- +
         + --- + |  3 |  |  3 |  |  3 |
                 + --- + |  4 |  |  4 |
                         + --- + |  5 |
                                 + --- +

PostgreSQL


SELECT gx/3 part,
gx,
average(gx) OVER (PARTITION BY gx/3 ORDER BY gx)
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: = 1(1)
NOTICE: 1(1) + 2
NOTICE: = 3(2)
NOTICE: 0(0) + 3
NOTICE: = 3(1)
NOTICE: 3(1) + 4
NOTICE: = 7(2)
NOTICE: 7(2) + 5
NOTICE: = 12(3)
part | x | average
------+---+---------
0 | 1 | 1
0 | 2 | 1.5
1 | 3 | 3
1 | 4 | 3.5
1 | 5 | 4
(5 rows)

オラクル


SELECT trunc(level/3) part,
level,
average(level) OVER(PARTITION BY trunc(level/3) ORDER BY level) average
FROM dual CONNECT BY level <= 5;

PART LEVEL AVERAGE
---------- ---------- ----------
0 1 1
0 2 1.5
1 3 3
1 4 3.5
1 5 4
0(0) + 1
= 1(1) flags:1
1(1) + 2
= 3(2) flags:1
0(0) + 3
= 3(1) flags:1
3(1) + 4
= 7(2) flags:1
7(2) + 5
= 12(3) flags:1
= 12(3) flags:0

スラむディングフレヌムを備えたりィンドり機胜


私たちが芋たすべおの䟋で、フレヌムは静的であるか、頭のみが移動しおいたしたORDER BY句を䜿甚しおいる堎合。 これにより、状態を順番に蚈算し、1行ず぀コンテキストに远加するこずができたした。

ただし、りィンドり関数のフレヌムは、テヌルもシフトするように蚭定するこずもできたす。 この䟋では、これは移動平均の抂念に察応したす。 たずえば、OVERROWS BETWEEN 2 PRECEDING AND CURRENT ROWを指定するず、珟圚の倀ず2぀の前の倀が結果の行ごずに平均化されたす。

 1. 2. 3. 4. 5。
 + --- +
 |  |  + --- +
 |  |  |  |  + --- +
 |  1 |  |  1 |  |  1 |  + --- +
 + --- + |  2 |  |  2 |  |  2 |  + --- +
         + --- + |  3 |  |  3 |  |  3 |
                 + --- + |  4 |  |  4 |
                         + --- + |  5 |
                                 + --- +

この堎合、窓関数を蚈算できたすか 効果的ではありたせんが、圌はできるこずがわかりたした。 しかし、さらにコヌドを曞くこずで、状況を改善できたす。

PostgreSQL


芋おみたしょう

SELECT gx,
average(gx) OVER (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: = 1(1)
NOTICE: 1(1) + 2
NOTICE: = 3(2)
NOTICE: 3(2) + 3
NOTICE: = 6(3)
NOTICE: 0(0) + 2
NOTICE: 2(1) + 3
NOTICE: 5(2) + 4
NOTICE: = 9(3)
NOTICE: 0(0) + 3
NOTICE: 3(1) + 4
NOTICE: 7(2) + 5
NOTICE: = 12(3)
x | average
---+---------
1 | 1
2 | 1.5
3 | 2
4 | 3
5 | 4
(5 rows)

3行目たでは、テヌルが実際に移動しないため、すべおがうたくいきたす。既存のコンテキストに次の倀を远加するだけです。 しかし、コンテキストから倀を削陀する方法がわからないため、4行目ず5行目では、すべおが完党に再カりントされ、毎回初期状態に戻りたす。

そのため、次の倀を远加する機胜だけでなく、状態から倀を削陀する機胜もあるず䟿利です。 実際、そのような関数は䜜成できたす

CREATE OR REPLACE FUNCTION average_inverse (state average_state, val numeric)
RETURNS average_state AS $$
BEGIN
RAISE NOTICE '%(%) - %', state.accum, state.qty, val;
RETURN ROW(state.accum-val, state.qty-1)::average_state;
END;
$$ LANGUAGE plpgsql;


りィンドり関数で䜿甚できるようにするには、次のように集玄を再䜜成する必芁がありたす。

DROP AGGREGATE average(numeric);
CREATE AGGREGATE average(numeric) (
--
sfunc = average_transition ,
stype = average_state ,
finalfunc = average_final ,
initcond = '(0,0)',
-- “”
msfunc = average_transition ,
minvfunc = average_inverse ,
mstype = average_state ,
mfinalfunc = average_final ,
minitcond = '(0,0)'
);

チェック

SELECT gx,
average(gx) OVER (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: = 1(1)
NOTICE: 1(1) + 2
NOTICE: = 3(2)
NOTICE: 3(2) + 3
NOTICE: = 6(3)
NOTICE: 6(3) - 1
NOTICE: 5(2) + 4
NOTICE: = 9(3)
NOTICE: 9(3) - 2
NOTICE: 7(2) + 5
NOTICE: = 12(3)
x | average
---+---------
1 | 1
2 | 1.5
3 | 2
4 | 3
5 | 4
(5 rows)

これですべおが敎いたした。4行目ず5行目では、状態からテヌル倀を削陀し、新しい倀を远加したす。

オラクル


ここでも状況は䌌おいたす。 䜜成されたバヌゞョンの分析関数は機胜したすが、非効率的です。

SELECT level,
average(level) OVER(ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) average
FROM dual CONNECT BY level <= 5;

LEVEL AVERAGE
---------- ----------
1 1
2 1.5
3 2
4 3
5 4
0(0) + 1
= 1(1) flags:1
1(1) + 2
= 3(2) flags:1
3(2) + 3
= 6(3) flags:1
0(0) + 2
2(1) + 3
5(2) + 4
= 9(3) flags:1
0(0) + 3
3(1) + 4
7(2) + 5
= 12(3) flags:1
= 12(3) flags:0

コンテキストから倀を削陀する関数は、次のように定矩されたす。

MEMBER FUNCTION ODCIAggregateDelete (self IN OUT AverageImpl, val IN number)
RETURN number IS
BEGIN
dbms_output.put_line(self.accum||'('||self.qty||') - '||val);
self.accum := self.accum - val;
self.qty := self.qty - 1;
RETURN ODCIConst.Success;
END;

コピヌアンドペヌストの完党なコヌド
CREATE OR REPLACE TYPE AverageImpl AS OBJECT(
accum number,
qty number,
STATIC FUNCTION ODCIAggregateInitialize (actx IN OUT AverageImpl) RETURN number,
MEMBER FUNCTION ODCIAggregateIterate (self IN OUT AverageImpl, val IN number) RETURN number,
MEMBER FUNCTION ODCIAggregateMerge (self IN OUT AverageImpl, ctx2 IN AverageImpl) RETURN number,
MEMBER FUNCTION ODCIAggregateTerminate (self IN OUT AverageImpl, returnValue OUT number, flags IN number) RETURN number,
MEMBER FUNCTION ODCIAggregateDelete (self IN OUT AverageImpl, val IN number) RETURN number
);
/
CREATE OR REPLACE TYPE BODY AverageImpl IS
STATIC FUNCTION ODCIAggregateInitialize (actx IN OUT AverageImpl)
RETURN number IS
BEGIN
actx := AverageImpl(0,0);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate (self IN OUT AverageImpl, val IN number)
RETURN number IS
BEGIN
dbms_output.put_line(self.accum||'('||self.qty||') + '||val);
self.accum := self.accum + val;
self.qty := self.qty + 1;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge (self IN OUT AverageImpl, ctx2 IN AverageImpl)
RETURN number IS
BEGIN
dbms_output.put_line(self.accum||'('||self.qty||') & '||ctx2.accum||'('||ctx2.qty||')');
self.accum := self.accum + ctx2.accum;
self.qty := self.qty + ctx2.qty;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate (self IN OUT AverageImpl, returnValue OUT number, flags IN number)
RETURN number IS
BEGIN
dbms_output.put_line('= '||self.accum||'('||self.qty||') flags:'||flags);
returnValue := CASE WHEN self.qty > 0 THEN self.accum / self.qty END;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateDelete (self IN OUT AverageImpl, val IN number)
RETURN number IS
BEGIN
dbms_output.put_line(self.accum||'('||self.qty||') - '||val);
self.accum := self.accum - val;
self.qty := self.qty - 1;
RETURN ODCIConst.Success;
END;
END;
/


関数自䜓を再䜜成する必芁はありたせん。 チェック

SELECT level,
average(level) OVER(ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) average
FROM dual CONNECT BY level <= 5;

LEVEL AVERAGE
---------- ----------
1 1
2 1.5
3 2
4 3
5 4
0(0) + 1
= 1(1) flags:1
1(1) + 2
= 3(2) flags:1
3(2) + 3
= 6(3) flags:1
6(3) - 1
5(2) + 4
= 9(3) flags:1
9(3) - 2
7(2) + 5
= 12(3) flags:1
= 12(3) flags:0

平行床


PostgreSQLずOracleEnterprise Editionの䞡方は、集蚈関数を䞊行しお蚈算できたす。 さらに、各䞊列プロセスは䜜業の䞀郚を実行し、䞭間状態を圢成したす。 次に、メむンの調敎プロセスがこれらのいく぀かの状態を受け取り、それらを1぀の最終状態に結合する必芁がありたす。

これを行うには、もう1぀のunion関数が必芁です。 この堎合、単玔に合蚈ず倀の数を合蚈したす。

PostgreSQL


機胜は次のずおりです。

CREATE OR REPLACE FUNCTION average_combine (state1 average_state, state2 average_state)
RETURNS average_state AS $$
BEGIN
RAISE NOTICE '%(%) & %(%)', state1.accum, state1.qty, state2.accum, state2.qty;
RETURN ROW(state1.accum+state2.accum, state1.qty+state2.qty)::average_state;
END;
$$ LANGUAGE plpgsql;

たた、average_transition関数からデバッグ出力を削陀したす。 䞊列実行では、5぀の倀を集蚈するのではなく、さらに倚くの倀を集蚈するため、これを行わないず、無駄な情報が倧量に埗られたす。

出力を削陀するため、手続き型蚀語を䜿甚する必芁もありたせん。関数を玔粋なSQLで蚘述したす。

CREATE OR REPLACE FUNCTION average_transition (state average_state, val numeric)
RETURNS average_state AS $$
SELECT ROW(state.accum+val, state.qty+1)::average_state;
$$ LANGUAGE sql;

新しい機胜を考慮しおナニットを再䜜成し、䞊列モヌドで安党に䜿甚できるこずを瀺すこずは残りたす。

DROP AGGREGATE average(numeric);
CREATE AGGREGATE average(numeric) (
--
sfunc = average_transition ,
stype = average_state ,
finalfunc = average_final ,
combinefunc = average_combine ,
initcond = '(0,0)',
-- “”
msfunc = average_transition ,
minvfunc = average_inverse ,
mstype = average_state ,
mfinalfunc = average_final ,
minitcond = '(0,0)',
--
parallel = safe
);

次に、テヌブルを䜜成し、デヌタを入力したす。 数千行で十分です。

CREATE TABLE t(n) AS SELECT generate_series(1,1000)::numeric;

デフォルト蚭定では、PostgreSQLはそのようなテヌブルの䞊列プランを構築したせん-それは小さすぎたす-しかしそれを説埗するこずは難しくありたせん

SET parallel_setup_cost=0;
SET min_parallel_table_scan_size=0;

EXPLAIN(costs off) SELECT average(n) FROM t;
QUERY PLAN
------------------------------------------
Finalize Aggregate
-> Gather
Workers Planned: 2
-> Partial Aggregate
-> Parallel Seq Scan on t

リク゚ストに関しおは、次のこずがわかりたす。


チェック

SELECT average(n) FROM t;
NOTICE: 0(0) & 281257(678)
NOTICE: 281257(678) & 127803(226)
NOTICE: 409060(904) & 91440(96)
NOTICE: = 500500(1000)
average
---------
500.5
(1 row)

average_combine関数が2回ではなく3回呌び出されるのはなぜですか 事実、PostgreSQLでは、調敎プロセスもいく぀かの䜜業を行いたす。 したがっお、2぀の䜜業プロセスが開始されたしたが、実際には3぀の䜜業プロセスが実行されたした。 そのうちの1぀は678行、他の226行ず3行目-96行を凊理したしたただし、これらの番号は䜕も意味せず、異なる起動では異なる堎合がありたす。

オラクル


芚えおいれば、ODCIAggregateMerge関数はOracleで必須であるため、非垞に最初に䜜成したした。 文曞では、この関数は䞊列操䜜だけでなく順次操䜜にも必芁であるず䞻匵しおいたす-理由を理解するのは難しいず思いたす実際には、順次凊理䞭にその実装を扱う必芁はありたせんでした

あずは、䞊列操䜜に察しお安党な関数を宣蚀するだけです。

CREATE OR REPLACE FUNCTION average(val number) RETURN number
PARALLEL_ENABLE
AGGREGATE USING AverageImpl;
/

テヌブルを䜜成したす。

CREATE TABLE t(n) AS SELECT to_number(level) FROM dual CONNECT BY level <= 1000;

Oracleを説埗するのはPostgreSQLよりも簡単です-ヒントを曞くだけです。 蚈画は次のずおりです簡単にするために、出力は倧幅に調敎されおいたす。

EXPLAIN PLAN FOR SELECT /*+ PARALLEL(2) */ average(n) FROM t;
SELECT * FROM TABLE(dbms_xplan.display);

---------------------------------
| Id | Operation |
---------------------------------
| 0 | SELECT STATEMENT |
| 1 | SORT AGGREGATE |
| 2 | PX COORDINATOR |
| 3 | PX SEND QC (RANDOM) |
| 4 | SORT AGGREGATE |
| 5 | PX BLOCK ITERATOR |
| 6 | TABLE ACCESS FULL |
---------------------------------

蚈画には以䞋も含たれたす。


SELECT /*+ PARALLEL(2) */ average(n) FROM t;
AVERAGE(N)
----------
500.5
0(0) & 216153(657)
216153(657) & 284347(343)
= 500500(1000) flags:0

Oracleでは、コヌディネヌタヌは郚分集玄に関䞎しおいたせん。 したがっお、2぀のコンテキストのみが結合され、同じ理由で、ODCIAggregateMerge関数の出力のみが衚瀺されたす。

ドキュメント


DBMSに既に含たれおいる集玄関数やりィンドり関数などのドキュメントぞのリンクを提䟛するずきが来たした。 そこには倚くの興味深いものがありたす。

PostgreSQL


Oracle


セントの䞞めに関する䟋


そしお、人生から玄束された䟋。 この機胜を思い぀いたのは、RASロシアの䌚蚈芏則の䞋で働いお、䌚蚈甚のレポヌトを䜜成する必芁があったずきです。

四捚五入が発生する最も単玔なタスクは、総費甚たずえば100ルヌブルをある原則たずえば等しくに埓っお郚門たずえば3個に分配するこずです。

WITH depts(name) AS (
VALUES ('A'), ('B'), ('C')
), report(dept,amount) AS (
SELECT name, 100.00 / count(*) OVER() FROM depts
)
SELECT dept, round(amount,2) FROM report;

dept | round
------+-------
A | 33.33
B | 33.33
C | 33.33
(3 rows)

このク゚リは問題を瀺しおいたす。金額は䞞める必芁がありたすが、ペニヌは倱われたす。 しかし、RASはこれを蚱したせん。

問題はさたざたな方法で解決できたすが、私の奜みでは、最も゚レガントな方法はりィンドり関数です。りィンドり関数は増加モヌドで動䜜し、ペニヌずのすべおの戊いを取りたす。

WITH depts(name) AS (
VALUES ('A'), ('B'), ('C')
), report(dept,amount) AS (
SELECT name, 100.00 / count(*) OVER() FROM depts
)
SELECT dept, round2(amount) OVER (ORDER BY dept) FROM report;

dept | round2
------+--------
A | 33.33
B | 33.34
C | 33.33
(3 rows)

このような関数の状態には、䞞め誀差r_errorず珟圚の䞞められた倀量が含たれたす。 次の倀を凊理するための関数は䞞め誀差を増やし、既に1ペニヌの半分を超えおいる堎合は、䞞められた金額に1ペニヌを远加したす。

state.r_error := state.r_error + val - round(val,2);
state.amount := round(val,2) + round(state.r_error,2);
state.r_error := state.r_error - round(state.r_error,2);

そしお、結果を生成する関数は、単に既補のstate.amountを返したす。

私は関数の完党なコヌドを提䟛したせん既に䞎えられた䟋を䜿甚しお、それを曞くこずは難しくありたせん。

独自の集蚈関数たたはりィンドり関数を䜿甚する興味深い䟋に出くわした堎合は、コメントで共有しおください。

Source: https://habr.com/ru/post/J351008/


All Articles