ウィンドウ関数はSQL2003標準で定義された新機能であり、SQL2011およびSQL2016で改善され、いくつかの拡張機能が追加されました。ウィンドウ関数は、私たちがよく知っている一般的な関数や集計関数とは異なり、データの各行に対して計算を実行します。複数の行(ウィンドウ)を入力し、値を返します。レポートなどの分析クエリでは、ウィンドウ関数は特定の要件をエレガントに表現し、かけがえのない役割を果たすことができます。
この記事では、最初にウィンドウ関数の定義と基本構文を紹介し、次にウィンドウ関数の最適化、実行、並列実行など、DBMSおよびビッグデータシステムでウィンドウ関数の効率的な計算を実現する方法を紹介します。
ウィンドウ関数とは何ですか?
SELECT句式のリストにウィンドウ関数が表示されOVER
ます。その最も注目すべき機能はキーワードです。構文は次のように定義されています。
|
次のオプションが含まれています。
- PARTITION BYは、データ
part_list
パーティションを押すと述べました - ORDER BYは、
order_list
並べ替えによって各パーティションのデータを表します
最後の項目は、フレームの定義を表します。つまり、現在のウィンドウにはどのデータが含まれていますか?
- たとえば、ROWSの最前線の選択は、
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
次の3行3行、合計7行(境界に遭遇した場合は行7以下)に進むことを表します。 - たとえば、RANGEで選択されたデータ範囲
RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING
は、[c-3、c +3 ]のすべての値を表します。
この範囲の行、c
- 現在の行の値です
論理的に言えば、ウィンドウ関数の計算「プロセス」は次のとおりです。
- ウィンドウの定義に従って、すべての入力データを分割およびソートします(必要な場合)
- データの各行について、そのフレーム範囲を計算します
- フレームの行のセットをウィンドウ関数に入力し、計算結果を現在の行に入力します
例えば:
|
上記のクエリでは、rank
列avgsales
は現在のディーラーの下の従業員の売上ランクを表します。これは、現在のディーラーの下のすべての従業員の平均売上を表します。クエリ結果は次のとおりです。
|
注:文法の各部分はオプションです。
- 指定しない場合
PARTITION BY
、データはパーティション化されません。つまり、すべてのデータが同じパーティションとして扱われます。- 指定しない場合
ORDER BY
、パーティションはソートされず、通常、次のような順序のないウィンドウ関数に使用されます。SUM()
- Frame句が指定されていない場合、デフォルトで次のフレーム定義が使用されます。
- 指定しない場合
ORDER BY
、パーティション内のすべての行がデフォルトで使用されますRANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
- 指定した場合
ORDER BY
、パーティションの現在の値の最初の行がデフォルトで使用されますRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
最後に、ウィンドウ関数は次の3つのカテゴリに分類できます。
- 重合(集約):、
AVG()
、COUNT()
、、MIN()
...MAX()
SUM()
- バリュー(価値):、
FIRST_VALUE()
、、LAST_VALUE()
...LEAD()
LAG()
- ソート(ランキング):、
RANK()
、、DENSE_RANK()
...ROW_NUMBER()
NTILE()
スペースの制限により、この記事では各ウィンドウ関数の意味については説明しません。興味のある読者はこのドキュメントを参照してください。
注:フレームウィンドウ関数が定義されている、すべてではないような、適しており
ROW_NUMBER()
、RANK()
、LEAD()
とのようにします。これらの関数は、現在のフレームではなく、常にパーティション全体に適用されます。
ウィンドウ関数VS.集計関数
集計の観点からは、ウィンドウ関数とGroupBy集計関数は同じことを実行できるようです。しかし、それらの間の類似点はこれに限定されています!主な違いは、ウィンドウ関数は結果を現在の結果に追加するだけで、既存の行または列に変更を加えないことです。Group Byのアプローチは完全に異なります。グループごとに、集計された結果の1行のみが保持されます。
一部の読者は、ウィンドウ関数を追加した後、返される結果の順序が明らかに変更されたと尋ねる場合がありますが、これは変更ではありませんか?SQLと関係代数はマルチセットに基づいて定義されているため、結果セット自体にORDER BY
は順序はなく、結果が最終的に表示される順序のみがあります。
一方、論理的および意味的には、SELECTステートメントのさまざまな部分は次の順序で「実行された」と見なすことができます。
評価によると、ウィンドウ関数はORDER BY
SQLの大部分の前、後、および大部分にのみ配置されています。これは、ウィンドウ関数を追加するだけで変更しないというセマンティクスも反映しています。この時点で結果セットが決定され、それに応じてウィンドウ関数が計算されます。
ウィンドウ関数の実行
ウィンドウ関数の従来の実行方法は、並べ替えと関数評価の2つのステップに分かれています。
ウィンドウの定義PARTITION BY
とは、ORDER BY
簡単にシークエンシングによって達成されます。たとえば、ウィンドウのPARTITION BY a, b ORDER BY c, d
場合、入力データに対して(a、b、c、d)を押すことができます。
または(b、a、c、d)
並べ替え後、データは図1に示すように配置されます。
次に考えてみましょう:フレームの扱い方は?
- パーティション全体のフレーム(たとえば
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)の場合、パーティション全体に対して1回だけ計算する必要があり、言うことはありません。 - 徐々に成長するフレーム(たとえば
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)の場合、アグリゲーターを使用して累積状態を維持できます。これも実装が非常に簡単です。 - フレームをスライドさせることは
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
比較的困難です(たとえば)。古典的なアプローチは、増加した要件をサポートするだけでなく、アグリゲーターが削除(リムーバブル)をサポートすることです。これは、たとえば、MAX()
実装を検討するよりも複雑になる可能性があります。
ウィンドウ関数の最適化
ウィンドウ関数の場合、オプティマイザは限定的な最適化を実行できます。これは、テキストの完全性についての簡単な説明です。
通常、最初にプロジェクトからウィンドウ関数を抽出し、ウィンドウと呼ばれる独立した演算子になります。
SELECTステートメントに複数のウィンドウ関数が含まれている場合があり、それらのウィンドウ定義(OVER
句)は同じでも異なっていてもかまいません。明らかに、同じウィンドウの場合、パーティションを作成して再度並べ替える必要はありません。それらをウィンドウ演算子にマージできます。
上の図に示すように、さまざまなウィンドウについて、最も簡単な方法で、すべてをさまざまなウィンドウに分割できます。実際の実行では、各ウィンドウを最初にソートする必要があり、コストは小さくありません。
1つのソートを使用して複数のウィンドウ関数を計算することは可能ですか?場合によっては、これが可能です。たとえば、この記事の例の2つのウィンドウ関数は次のとおりです。
|
これらの2つのウィンドウはAVG(sales)
完全に同じではありませんが、サブリージョンの順序は気にしませんが、ROW_NUMBER()
ウィンドウ全体を再利用できます。このホワイトペーパーでは、可能な限り再利用できる機会を活用できるヒューリスティックアルゴリズムを提供します。
ウィンドウ関数の並列実行*
最新のDBMSのほとんどは、並列実行をサポートしています。ウィンドウ関数の場合、パーティション間の計算は完全に無関係であるため、各パーティションを異なるノード(スレッド)に簡単に割り当てて、パーティション間の並列処理を実現できます。
ただし、グローバルウィンドウ関数パーティションが1つしかない場合(no PARTITION BY
、またはpartitions句の数)、完全に並列化するのに十分な時間がない場合は、どうすればよいですか?前述のリムーバブルアグリゲーターテクノロジーは、明らかにもう使用できません。単一のアグリゲーターの内部状態に依存しているため、効果的に並列化することは困難です。
TUMの論文は、効率的な並列パーティション化のためにツリーライン(セグメントツリー)を使用することを提案しています。線分ツリーはN項ツリーのデータ構造であり、各ノードには現在のノードの下の集計結果の一部が含まれています。
図の次のセグメントはSUM()
、例を使用して計算された二分木である。たとえば、下の図の3行目の12
、リーフノード5 + 7の集計結果を表し、その上の25はリーフノード5 + 7 + 3 +10を表します。
集計の結果。
現在のフレームが2行目から8行目、つまり7 + 3 + 10 + ... +4であると仮定して計算する必要があります
間隔の合計。線分ツリーを使用すると、7 + 13 +20を直接使用できます。
(写真の赤いフォント)集計結果を計算します。
線分ツリーはO(nlogn)にすることができます
時間内に構築され、O(logn)時間の任意の間隔の集計結果を照会できます。さらに優れているのは、クエリを相互に干渉することなく同時にマルチスレッド化できるだけでなく、線分ツリーの構築プロセスも十分に並列化できることです。