sql >> データベース >  >> RDS >> Database

SQLのネストされたウィンドウ関数

    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式です。

    ネストされた行番号関数

    ネストされた行番号関数を使用すると、ウィンドウ要素の戦略的マーカーの行番号を参照できます。関数の構文は次のとおりです。

    (< ROW_NUMBER(<行マーカー>)の引数>)OVER(<仕様>)

    指定できる行マーカーは次のとおりです。

    • 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 AT<行マーカー>[][、]
    >)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で利用できるようにすることが役立つと思われる場合は、必ず投票してください。


    1. 集計関数またはGROUPBY句のいずれにも含まれていないため、選択リストの列が無効です

    2. OracleのLISTAGG関数を一意のフィルターで使用するにはどうすればよいですか?

    3. MySQLのBLOB列に入れることができるデータの最大長はどれくらいですか?

    4. OracleのNEW_TIME()関数