Database
 sql >> Baza danych >  >> RDS >> Database

Zagnieżdżone funkcje okien w SQL

Norma ISO/IEC 9075:2016 (SQL:2016) definiuje funkcję zwaną zagnieżdżonymi funkcjami okna. Ta funkcja umożliwia zagnieżdżanie dwóch rodzajów funkcji okna jako argumentu funkcji agregującej okna. Pomysł polega na umożliwieniu odwoływania się do numeru wiersza lub wartości wyrażenia w strategicznych znacznikach w elementach okienek. Znaczniki zapewniają dostęp do pierwszego lub ostatniego wiersza w partycji, pierwszego lub ostatniego wiersza w ramce, bieżącego wiersza zewnętrznego i bieżącego wiersza ramki. Ta idea jest bardzo potężna, umożliwiając zastosowanie filtrowania i innych rodzajów manipulacji w funkcji okna, które czasami są trudne do osiągnięcia w inny sposób. Możesz także użyć zagnieżdżonych funkcji okna, aby łatwo emulować inne funkcje, takie jak ramki oparte na ZAKRESIE. Ta funkcja jest obecnie niedostępna w T-SQL. Zamieściłem sugestię ulepszenia SQL Server poprzez dodanie obsługi zagnieżdżonych funkcji okna. Pamiętaj, aby oddać swój głos, jeśli uważasz, że ta funkcja może być dla Ciebie korzystna.

Czego nie dotyczą zagnieżdżone funkcje okien

W chwili pisania tego tekstu nie ma zbyt wielu informacji na temat prawdziwych standardowych funkcji zagnieżdżonych okien. Trudniejsze jest to, że nie znam jeszcze żadnej platformy, która zaimplementowała tę funkcję. W rzeczywistości uruchomienie wyszukiwania w sieci WWW dla zagnieżdżonych funkcji okien zwraca głównie pokrycie i dyskusje na temat zagnieżdżania zgrupowanych funkcji agregujących w oknach funkcji agregujących. Załóżmy na przykład, że chcesz wysłać zapytanie do widoku Sales.OrderValues ​​w przykładowej bazie danych TSQLV5 i zwrócić dla każdego klienta i daty zamówienia, dzienną sumę wartości zamówienia oraz sumę bieżącą do bieżącego dnia. Takie zadanie obejmuje zarówno grupowanie, jak i okienkowanie. Grupujesz wiersze według identyfikatora klienta i daty zamówienia, a następnie dodajesz sumę bieżącą do sumy grupowej wartości zamówienia, na przykład:

  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;

To zapytanie generuje następujące dane wyjściowe, pokazane tutaj w skróconej formie:

  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
  ...

Mimo że ta technika jest całkiem fajna i chociaż wyszukiwanie w Internecie zagnieżdżonych funkcji okien zwraca głównie takie techniki, standard SQL nie ma na myśli tego, co oznacza zagnieżdżone funkcje okien. Ponieważ nie mogłem znaleźć żadnych informacji na ten temat, po prostu musiałem to rozgryźć z samego standardu. Mamy nadzieję, że ten artykuł zwiększy świadomość prawdziwej funkcji zagnieżdżonych funkcji okien i spowoduje, że ludzie zwrócą się do firmy Microsoft i poproszą o dodanie obsługi w SQL Server.

O czym są zagnieżdżone funkcje okien

Zagnieżdżone funkcje okna obejmują dwie funkcje, które można zagnieździć jako argument funkcji agregującej okna. Są to zagnieżdżona funkcja numeru wiersza i zagnieżdżona funkcja wartość_wyrażenia w funkcji wiersza.

Zagnieżdżona funkcja numeru wiersza

Zagnieżdżona funkcja numeru wiersza umożliwia odwołanie się do numeru wiersza znaczników strategicznych w elementach okienek. Oto składnia funkcji:

(ROW_NUMBER()>) OVER()

Znaczniki wierszy, które możesz określić to:

  • BEGIN_PARTITION
  • END_PARTITION
  • BEGIN_RAMKA
  • END_FRAME
  • CURRENT_ROW
  • FRAME_ROW

Pierwsze cztery znaczniki są oczywiste. Jeśli chodzi o ostatnie dwa, znacznik CURRENT_ROW reprezentuje bieżący wiersz zewnętrzny, a FRAME_ROW reprezentuje bieżący wiersz ramki wewnętrznej.

Jako przykład użycia zagnieżdżonej funkcji numeru wiersza rozważ następujące zadanie. Musisz wysłać zapytanie do widoku Sales.OrderValues ​​i zwrócić dla każdego zamówienia niektóre jego atrybuty, a także różnicę między bieżącą wartością zamówienia a średnią klienta, ale z wyłączeniem pierwszego i ostatniego zamówienia klienta ze średniej.

To zadanie można osiągnąć bez zagnieżdżonych funkcji okien, ale rozwiązanie obejmuje kilka kroków:

  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;

Oto wynik tego zapytania, pokazany tutaj w skróconej formie:

  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
  ...

Używając zagnieżdżonych funkcji numerów wierszy, zadanie jest osiągalne za pomocą jednego zapytania, na przykład:

  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;

Ponadto obecnie obsługiwane rozwiązanie wymaga co najmniej jednego sortowania w planie i wielokrotnego przekazywania danych. Rozwiązanie wykorzystujące zagnieżdżone funkcje numerów wierszy ma cały potencjał optymalizacji w oparciu o kolejność indeksów i zmniejszoną liczbę przejść przez dane. To oczywiście zależy od implementacji.

Zagnieżdżona wartość_wyrażenia w funkcji wiersza

Zagnieżdżona funkcja wartość_wyrażenia w wierszu umożliwia interakcję z wartością wyrażenia w tych samych strategicznych znacznikach wierszy, o których wspomniano wcześniej w argumencie funkcji agregującej okna. Oto składnia tej funkcji:

( VALUE OF AT [] [, ]
>) OVER()

Jak widać, możesz określić pewną ujemną lub dodatnią deltę w odniesieniu do znacznika wiersza i opcjonalnie podać wartość domyślną w przypadku, gdy wiersz nie istnieje w określonej pozycji.

Ta funkcja zapewnia dużą moc, gdy zachodzi potrzeba interakcji z różnymi punktami elementów okienek. Rozważ fakt, że tak potężne, jak funkcje okien mogą być porównywane do alternatywnych narzędzi, takich jak podzapytania, to to, czego funkcje okien nie obsługują, jest podstawową koncepcją korelacji. Używając znacznika CURRENT_ROW uzyskujesz dostęp do zewnętrznego wiersza iw ten sposób emulujesz korelacje. Jednocześnie możesz korzystać ze wszystkich zalet funkcji okien w porównaniu z podzapytaniami.

Na przykład załóżmy, że trzeba wykonać zapytanie do widoku Sales.OrderValues ​​i zwrócić dla każdego zamówienia niektóre jego atrybuty, a także różnicę między bieżącą wartością zamówienia a średnią klienta, ale z wyłączeniem zamówień złożonych w tym samym dniu co aktualna data zamówienia. Wymaga to zdolności podobnej do korelacji. Dzięki zagnieżdżonej funkcji value_of expression at row, używając znacznika CURRENT_ROW, można to łatwo osiągnąć, tak jak:

  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;

To zapytanie ma wygenerować następujący wynik:

  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
  ...

Jeśli myślisz, że to zadanie można równie łatwo wykonać za pomocą skorelowanych podzapytań, w tym uproszczonym przypadku miałbyś rację. To samo można osiągnąć za pomocą następującego zapytania:

  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;

Należy jednak pamiętać, że podzapytanie działa na niezależnym widoku danych, podczas gdy funkcja okna działa na zestawie, który jest dostarczany jako dane wejściowe do logicznego kroku przetwarzania zapytania, który obsługuje klauzulę SELECT. Zwykle podstawowe zapytanie ma dodatkową logikę, taką jak sprzężenia, filtry, grupowanie i tym podobne. W przypadku podzapytań musisz albo przygotować wstępne CTE, albo powtórzyć logikę podstawowego zapytania również w podzapytaniu. Dzięki funkcjom okien nie ma potrzeby powtarzania żadnej logiki.

Załóżmy na przykład, że miałeś operować tylko na wysłanych zamówieniach (gdzie data wysyłki nie jest NULL), które były obsługiwane przez pracownika 3. Rozwiązanie z funkcją okna wymaga dodania predykatów filtrujących tylko raz, tak jak:

   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;

To zapytanie ma wygenerować następujący wynik:

  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
  ...

Rozwiązanie z podzapytaniem wymaga dwukrotnego dodania predykatów filtrujących — raz w zapytaniu zewnętrznym i raz w podzapytaniu — w ten sposób:

  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;

Jest to albo to, albo dodanie wstępnego CTE, które zajmuje się całym filtrowaniem i wszelką inną logiką. W każdym razie, gdy na to spojrzysz, w przypadku podzapytań jest zaangażowanych więcej warstw złożoności.

Inną zaletą zagnieżdżonych funkcji okien jest to, że gdybyśmy mieli wsparcie dla tych w T-SQL, łatwo byłoby emulować brakujące pełne wsparcie dla jednostki ramy okna RANGE. Opcja RANGE ma umożliwić zdefiniowanie ramek dynamicznych, które bazują na przesunięciu względem wartości porządkowania w bieżącym wierszu. Załóżmy na przykład, że musisz obliczyć dla każdego zamówienia klienta z Sales.OrderValues ​​wyświetlić średnią ruchomą wartość z ostatnich 14 dni. Zgodnie ze standardem SQL można to osiągnąć za pomocą opcji RANGE i typu INTERVAL, na przykład:

  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;

To zapytanie ma wygenerować następujący wynik:

  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
  ...

W chwili pisania tego tekstu ta składnia nie jest obsługiwana w języku T-SQL. Gdybyśmy mieli obsługę zagnieżdżonych funkcji okien w T-SQL, bylibyśmy w stanie emulować to zapytanie za pomocą następującego kodu:

  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;

Czego nie lubić?

Oddaj swój głos

Standardowe zagnieżdżone funkcje okien wydają się być bardzo potężną koncepcją, która zapewnia dużą elastyczność w interakcji z różnymi punktami elementów okienek. Jestem dość zaskoczony, że nie mogę znaleźć żadnego opisu koncepcji poza samym standardem i że nie widzę wielu platform, które go implementują. Mam nadzieję, że ten artykuł zwiększy świadomość tej funkcji. Jeśli uważasz, że przydatne byłoby udostępnienie go w T-SQL, oddaj swój głos!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Polecenia SQL

  2. Dell Boomi

  3. Co to jest niestandardowy obraz środowiska wykonawczego w Javie 9?

  4. Korzystanie z Kreatora wykrywania metadanych

  5. AKTUALIZACJA SQL