ISO / IEC 9075:2016標準(SQL:2016)は、ネストされたウィンドウ関数と呼ばれる機能を定義しています。この機能を使用すると、2種類のウィンドウ関数をウィンドウ集計関数の引数としてネストできます。アイデアは、ウィンドウ要素の戦略的マーカーで、行番号または式の値のいずれかを参照できるようにすることです。マーカーを使用すると、パーティションの最初または最後の行、フレームの最初または最後の行、現在の外側の行、および現在のフレームの行にアクセスできます。このアイデアは非常に強力であり、他の方法では実現が難しい場合があるウィンドウ関数内でフィルタリングやその他の種類の操作を適用できます。ネストされたウィンドウ関数を使用して、RANGEベースのフレームなどの他の機能を簡単にエミュレートすることもできます。この機能は現在、T-SQLでは使用できません。ネストされたウィンドウ関数のサポートを追加することにより、SQLServerを改善するための提案を投稿しました。この機能があなたにとって有益であると思われる場合は、必ず投票を追加してください。
ネストされたウィンドウ関数とは関係ありません
この記事の執筆時点では、真の標準のネストされたウィンドウ関数について入手できる情報はあまりありません。難しいのは、この機能を実装しているプラットフォームをまだ知らないことです。実際、ネストされたウィンドウ関数のWeb検索を実行すると、ウィンドウ化された集計関数内にグループ化された集計関数をネストすることについての説明と説明がほとんど返されます。たとえば、TSQLV5サンプルデータベースのSales.OrderValuesビューにクエリを実行し、各顧客と注文日、注文値の1日の合計、および当日までの現在の合計を返すとします。このようなタスクには、グループ化とウィンドウ処理の両方が含まれます。次のように、顧客IDと注文日で行をグループ化し、注文値のグループ合計の上に現在の合計を適用します。
USE TSQLV5; -- http://tsql.solidq.com/SampleDatabases/TSQLV5.zip SELECT custid, orderdate, SUM(val) AS daytotal, SUM(SUM(val)) OVER(PARTITION BY custid ORDER BY orderdate ROWS UNBOUNDED PRECEDING) AS runningsum FROM Sales.OrderValues GROUP BY custid, orderdate;
このクエリは、次の出力を生成します。ここでは省略形で示されています。
custid orderdate daytotal runningsum ------- ---------- -------- ---------- 1 2018-08-25 814.50 814.50 1 2018-10-03 878.00 1692.50 1 2018-10-13 330.00 2022.50 1 2019-01-15 845.80 2868.30 1 2019-03-16 471.20 3339.50 1 2019-04-09 933.50 4273.00 2 2017-09-18 88.80 88.80 2 2018-08-08 479.75 568.55 2 2018-11-28 320.00 888.55 2 2019-03-04 514.40 1402.95 ...
この手法は非常に優れており、ネストされたウィンドウ関数のWeb検索は主にそのような手法を返しますが、それはSQL標準がネストされたウィンドウ関数によって意味するものではありません。このトピックに関する情報が見つからなかったので、標準自体からそれを理解する必要がありました。うまくいけば、この記事が真のネストされたウィンドウ関数機能の認識を高め、人々がMicrosoftに目を向け、SQLServerでのサポートの追加を求めるようになることを願っています。
ネストされたウィンドウ関数とは
ネストされたウィンドウ関数には、ウィンドウ集計関数の引数としてネストできる2つの関数が含まれています。これらは、ネストされた行番号関数と、行関数でのネストされたvalue_of式です。
ネストされた行番号関数
ネストされた行番号関数を使用すると、ウィンドウ要素の戦略的マーカーの行番号を参照できます。関数の構文は次のとおりです。
指定できる行マーカーは次のとおりです。
- BEGIN_PARTITION
- END_PARTITION
- BEGIN_FRAME
- END_FRAME
- CURRENT_ROW
- FRAME_ROW
最初の4つのマーカーは自明です。最後の2つについては、CURRENT_ROWマーカーは現在の外側の行を表し、FRAME_ROWは現在の内側のフレーム行を表します。
ネストされた行番号関数を使用する例として、次のタスクを検討してください。 Sales.OrderValuesビューにクエリを実行し、注文ごとにその属性の一部と、現在の注文値と顧客の平均の差を返す必要がありますが、平均から最初と最後の顧客の注文は除外します。
>このタスクは、ネストされたウィンドウ関数がなくても実行できますが、ソリューションにはかなりの数の手順が含まれます。
WITH C1 AS ( SELECT custid, val, ROW_NUMBER() OVER( PARTITION BY custid ORDER BY orderdate, orderid ) AS rownumasc, ROW_NUMBER() OVER( PARTITION BY custid ORDER BY orderdate DESC, orderid DESC ) AS rownumdesc FROM Sales.OrderValues ), C2 AS ( SELECT custid, AVG(val) AS avgval FROM C1 WHERE 1 NOT IN (rownumasc, rownumdesc) GROUP BY custid ) SELECT O.orderid, O.custid, O.orderdate, O.val, O.val - C2.avgval AS diff FROM Sales.OrderValues AS O LEFT OUTER JOIN C2 ON O.custid = C2.custid;
このクエリの出力は次のとおりです。ここでは省略形で示されています:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------ 10411 10 2018-01-10 966.80 -570.184166 10743 4 2018-11-17 319.20 -809.813636 11075 68 2019-05-06 498.10 -1546.297500 10388 72 2017-12-19 1228.80 -358.864285 10720 61 2018-10-28 550.00 -144.744285 11052 34 2019-04-27 1332.00 -1164.397500 10457 39 2018-02-25 1584.00 -797.999166 10789 23 2018-12-22 3687.00 1567.833334 10434 24 2018-02-03 321.12 -1329.582352 10766 56 2018-12-05 2310.00 1015.105000 ...
ネストされた行番号関数を使用すると、次のように1つのクエリでタスクを実行できます。
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN ROW_NUMBER(FRAME_ROW) NOT IN ( ROW_NUMBER(BEGIN_PARTITION), ROW_NUMBER(END_PARTITION) ) THEN val END ) OVER( PARTITION BY custid ORDER BY orderdate, orderid ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS diff FROM Sales.OrderValues;
また、現在サポートされているソリューションでは、プランに少なくとも1つの並べ替えが必要であり、データを複数回渡す必要があります。ネストされた行番号関数を使用するソリューションには、インデックスの順序に依存して最適化され、データのパス回数が減る可能性があります。もちろん、これは実装に依存します。
行関数にネストされたvalue_of式
行関数でネストされたvalue_of式を使用すると、ウィンドウ集計関数の引数で前述したのと同じ戦略的な行マーカーで式の値を操作できます。この関数の構文は次のとおりです。
VALUE OF
>)OVER(<仕様>)
ご覧のとおり、行マーカーに対して特定の負または正のデルタを指定でき、オプションで、指定した位置に行が存在しない場合のデフォルト値を指定できます。
この機能は、ウィンドウ要素のさまざまなポイントと対話する必要がある場合に、多くのパワーを提供します。ウィンドウ関数はサブクエリなどの代替ツールと比較できるほど強力であるという事実を考慮してください。ウィンドウ関数がサポートしていないのは、相関関係の基本的な概念です。 CURRENT_ROWマーカーを使用すると、外側の行にアクセスでき、この方法で相関関係をエミュレートします。同時に、ウィンドウ関数がサブクエリと比較して得たすべての利点から利益を得ることができます。
例として、Sales.OrderValuesビューにクエリを実行し、注文ごとにその属性の一部と、現在の注文値と顧客の平均の差を返す必要があるとします。ただし、同じ日に行われた注文は除きます。現在の注文日。これには、相関と同様の機能が必要です。行関数でネストされたvalue_of式を使用し、CURRENT_ROWマーカーを使用すると、次のように簡単に実現できます。
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN orderdate <> VALUE OF orderdate AT CURRENT_ROW THEN val END ) OVER( PARTITION BY custid ) AS diff FROM Sales.OrderValues;
このクエリは、次の出力を生成することになっています:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------ 10248 85 2017-07-04 440.00 180.000000 10249 79 2017-07-05 1863.40 1280.452000 10250 34 2017-07-08 1552.60 -854.228461 10251 84 2017-07-08 654.06 -293.536666 10252 76 2017-07-09 3597.90 1735.092728 10253 34 2017-07-10 1444.80 -970.320769 10254 14 2017-07-11 556.62 -1127.988571 10255 68 2017-07-12 2490.50 617.913334 10256 88 2017-07-15 517.80 -176.000000 10257 35 2017-07-16 1119.90 -153.562352 ...
このタスクは、相関するサブクエリを使用して同じように簡単に実行できると考えている場合、この単純なケースでは正しいでしょう。次のクエリでも同じことができます:
SELECT O1.orderid, O1.custid, O1.orderdate, O1.val, O1.val - ( SELECT AVG(O2.val) FROM Sales.OrderValues AS O2 WHERE O2.custid = O1.custid AND O2.orderdate <> O1.orderdate ) AS diff FROM Sales.OrderValues AS O1;
ただし、サブクエリはデータの独立したビューで動作するのに対し、ウィンドウ関数はSELECT句を処理する論理クエリ処理ステップへの入力として提供されるセットで動作することに注意してください。通常、基になるクエリには、結合、フィルター、グループ化などの追加のロジックがあります。サブクエリでは、予備的なCTEを準備するか、サブクエリでも基になるクエリのロジックを繰り返す必要があります。ウィンドウ関数を使用すると、ロジックを繰り返す必要はありません。
たとえば、従業員3によって処理された出荷済み注文(出荷日がNULLではない)のみを操作することになっているとします。ウィンドウ関数を使用したソリューションでは、次のようにフィルター述語を1回だけ追加する必要があります。
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN orderdate <> VALUE OF orderdate AT CURRENT_ROW THEN val END ) OVER( PARTITION BY custid ) AS diff FROM Sales.OrderValues WHERE empid = 3 AND shippeddate IS NOT NULL;
このクエリは、次の出力を生成することになっています:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------- 10251 84 2017-07-08 654.06 -459.965000 10253 34 2017-07-10 1444.80 531.733334 10256 88 2017-07-15 517.80 -1022.020000 10266 87 2017-07-26 346.56 NULL 10273 63 2017-08-05 2037.28 -3149.075000 10283 46 2017-08-16 1414.80 534.300000 10309 37 2017-09-19 1762.00 -1951.262500 10321 38 2017-10-03 144.00 NULL 10330 46 2017-10-16 1649.00 885.600000 10332 51 2017-10-17 1786.88 495.830000 ...
サブクエリを使用したソリューションでは、次のように、フィルタ述語を2回追加する必要があります。1回は外部クエリに、もう1回はサブクエリに追加します。
SELECT O1.orderid, O1.custid, O1.orderdate, O1.val, O1.val - ( SELECT AVG(O2.val) FROM Sales.OrderValues AS O2 WHERE O2.custid = O1.custid AND O2.orderdate <> O1.orderdate AND empid = 3 AND shippeddate IS NOT NULL) AS diff FROM Sales.OrderValues AS O1 WHERE empid = 3 AND shippeddate IS NOT NULL;
これか、すべてのフィルタリングとその他のロジックを処理する予備的なCTEを追加するかのいずれかです。とにかく、サブクエリを使用すると、より複雑なレイヤーが関係します。
ネストされたウィンドウ関数のもう1つの利点は、T-SQLでサポートされていれば、RANGEウィンドウフレームユニットの完全なサポートが不足していることを簡単にエミュレートできたことです。 RANGEオプションを使用すると、現在の行の順序値からのオフセットに基づく動的フレームを定義できるようになります。たとえば、Sales.OrderValuesビューから顧客の注文ごとに過去14日間の移動平均値を計算する必要があるとします。 SQL標準によれば、これはRANGEオプションとINTERVALタイプを使用して次のように実現できます。
SELECT orderid, custid, orderdate, val, AVG(val) OVER( PARTITION BY custid ORDER BY orderdate RANGE BETWEEN INTERVAL '13' DAY PRECEDING AND CURRENT ROW ) AS movingavg14days FROM Sales.OrderValues;
このクエリは、次の出力を生成することになっています:
orderid custid orderdate val movingavg14days -------- ------- ---------- ------- --------------- 10643 1 2018-08-25 814.50 814.500000 10692 1 2018-10-03 878.00 878.000000 10702 1 2018-10-13 330.00 604.000000 10835 1 2019-01-15 845.80 845.800000 10952 1 2019-03-16 471.20 471.200000 11011 1 2019-04-09 933.50 933.500000 10308 2 2017-09-18 88.80 88.800000 10625 2 2018-08-08 479.75 479.750000 10759 2 2018-11-28 320.00 320.000000 10926 2 2019-03-04 514.40 514.400000 10365 3 2017-11-27 403.20 403.200000 10507 3 2018-04-15 749.06 749.060000 10535 3 2018-05-13 1940.85 1940.850000 10573 3 2018-06-19 2082.00 2082.000000 10677 3 2018-09-22 813.37 813.370000 10682 3 2018-09-25 375.50 594.435000 10856 3 2019-01-28 660.00 660.000000 ...
この記事の執筆時点では、この構文はT-SQLではサポートされていません。 T-SQLでネストされたウィンドウ関数がサポートされていれば、次のコードでこのクエリをエミュレートできたはずです。
SELECT orderid, custid, orderdate, val, AVG( CASE WHEN DATEDIFF(day, orderdate, VALUE OF orderdate AT CURRENT_ROW) BETWEEN 0 AND 13 THEN val END ) OVER( PARTITION BY custid ORDER BY orderdate RANGE UNBOUNDED PRECEDING ) AS movingavg14days FROM Sales.OrderValues;
嫌いなものは何ですか?
投票する
標準のネストされたウィンドウ関数は、ウィンドウ要素のさまざまなポイントと対話する際に多くの柔軟性を可能にする非常に強力な概念のように見えます。標準自体以外に概念の範囲が見つからないこと、およびそれを実装しているプラットフォームがあまりないことに非常に驚いています。この記事がこの機能の認知度を高めることを願っています。 T-SQLで利用できるようにすることが役立つと思われる場合は、必ず投票してください。