Database
 sql >> Database >  >> RDS >> Database

Funzioni della finestra annidata in SQL

Lo standard ISO/IEC 9075:2016 (SQL:2016) definisce una funzionalità denominata funzioni della finestra nidificata. Questa funzione consente di annidare due tipi di funzioni di finestra come argomento di una funzione di aggregazione di finestre. L'idea è di consentire di fare riferimento a un numero di riga, oa un valore di un'espressione, a indicatori strategici negli elementi di finestra. I marcatori danno accesso alla prima o all'ultima riga nella partizione, alla prima o all'ultima riga nel frame, alla riga esterna corrente e alla riga del frame corrente. Questa idea è molto potente e ti consente di applicare filtri e altri tipi di manipolazioni all'interno della funzione della tua finestra che a volte sono difficili da ottenere altrimenti. Puoi anche utilizzare le funzioni della finestra nidificata per emulare facilmente altre funzioni, come i frame basati su RANGE. Questa funzionalità non è attualmente disponibile in T-SQL. Ho pubblicato un suggerimento per migliorare SQL Server aggiungendo il supporto per le funzioni della finestra nidificata. Assicurati di aggiungere il tuo voto se ritieni che questa funzione possa esserti utile.

Di cosa non trattano le funzioni della finestra nidificata

Alla data in cui scrivo, non ci sono molte informazioni disponibili sulle vere funzioni standard della finestra nidificata. Ciò che lo rende più difficile è che non conosco ancora nessuna piattaforma che abbia implementato questa funzionalità. In effetti, l'esecuzione di una ricerca sul Web per le funzioni di finestra nidificate restituisce principalmente la copertura e le discussioni sull'annidamento di funzioni aggregate raggruppate all'interno di funzioni di aggregazione in finestra. Si supponga, ad esempio, di voler eseguire una query sulla vista Sales.OrderValues ​​nel database di esempio TSQLV5 e restituire per ogni cliente e data dell'ordine, il totale giornaliero dei valori dell'ordine e il totale parziale fino al giorno corrente. Tale attività coinvolge sia il raggruppamento che il windowing. Raggruppi le righe in base all'ID cliente e alla data dell'ordine e applichi una somma parziale sopra la somma del gruppo dei valori dell'ordine, in questo modo:

  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;

Questa query genera il seguente output, mostrato qui in forma abbreviata:

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

Anche se questa tecnica è piuttosto interessante, e anche se le ricerche sul Web di funzioni di finestra nidificate restituiscono principalmente tali tecniche, non è ciò che lo standard SQL intende per funzioni di finestra nidificate. Dal momento che non riuscivo a trovare alcuna informazione sull'argomento, dovevo solo capirlo dallo standard stesso. Si spera che questo articolo aumenterà la consapevolezza della vera funzionalità delle funzioni della finestra nidificata e indurrà le persone a rivolgersi a Microsoft e chiedere di aggiungere il supporto per esso in SQL Server.

Di cosa trattano le funzioni della finestra nidificata

Le funzioni della finestra nidificata includono due funzioni che è possibile nidificare come argomento di una funzione di aggregazione della finestra. Queste sono la funzione nidificata del numero di riga e la funzione nidificata value_of nella riga.

Funzione numero riga nidificata

La funzione del numero di riga nidificata consente di fare riferimento al numero di riga degli indicatori strategici negli elementi della finestra. Ecco la sintassi della funzione:

(ROW_NUMBER()>) OVER()

Gli indicatori di riga che puoi specificare sono:

  • BEGIN_PARTITION
  • END_PARTITION
  • BEGIN_FRAME
  • END_FRAME
  • RIGA_CORRENTE
  • FRAME_ROW

I primi quattro indicatori sono autoesplicativi. Come per gli ultimi due, il marker CURRENT_ROW rappresenta la riga esterna corrente e FRAME_ROW rappresenta la riga interna del frame corrente.

Come esempio per l'utilizzo della funzione del numero di riga nidificata, considerare l'attività seguente. È necessario eseguire una query nella vista Sales.OrderValues ​​e restituire per ogni ordine alcuni dei suoi attributi, nonché la differenza tra il valore dell'ordine corrente e la media del cliente, ma escludendo dalla media il primo e l'ultimo ordine del cliente.

Questo compito è realizzabile senza funzioni di finestra nidificate, ma la soluzione prevede alcuni passaggi:

  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;

Ecco l'output di questa query, mostrato qui in forma abbreviata:

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

Utilizzando le funzioni nidificate dei numeri di riga, l'attività è realizzabile con una singola query, in questo modo:

  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;

Inoltre, la soluzione attualmente supportata richiede almeno un ordinamento nel piano e più passaggi sui dati. La soluzione che utilizza le funzioni dei numeri di riga nidificate ha tutte le potenzialità per essere ottimizzata facendo affidamento sull'ordine dell'indice e un numero ridotto di passaggi sui dati. Questo, ovviamente, dipende dall'implementazione.

Valore_di espressione nidificato nella funzione riga

La funzione nidificata value_of expression at row consente di interagire con un valore di un'espressione negli stessi indicatori di riga strategici menzionati in precedenza in un argomento di una funzione di aggregazione della finestra. Ecco la sintassi di questa funzione:

( VALORE DI AT [] [, ]
>) OLTRE()

Come puoi vedere, puoi specificare un certo delta negativo o positivo rispetto all'indicatore di riga e, facoltativamente, fornire un valore predefinito nel caso in cui una riga non esista nella posizione specificata.

Questa capacità ti dà molta potenza quando devi interagire con diversi punti negli elementi della finestra. Considera il fatto che, per quanto potenti funzioni di finestra possano essere paragonate a strumenti alternativi come le sottoquery, ciò che le funzioni di finestra non supportano è un concetto di base di correlazione. Usando il marcatore CURRENT_ROW si ottiene l'accesso alla riga esterna e in questo modo si emulano le correlazioni. Allo stesso tempo, puoi beneficiare di tutti i vantaggi offerti dalle funzioni della finestra rispetto alle sottoquery.

Ad esempio, supponiamo di dover interrogare la vista Sales.OrderValues ​​e restituire per ogni ordine alcuni dei suoi attributi, nonché la differenza tra il valore dell'ordine corrente e la media del cliente, ma escludendo gli ordini effettuati nella stessa data di la data dell'ordine corrente. Ciò richiede una capacità simile a una correlazione. Con la funzione nidificata value_of nella riga, utilizzando l'indicatore CURRENT_ROW, questo è facilmente realizzabile in questo modo:

  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;

Questa query dovrebbe generare il seguente output:

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

Se stai pensando che questo compito sia realizzabile altrettanto facilmente con le sottoquery correlate, in questo caso semplicistico avresti ragione. Lo stesso può essere ottenuto con la seguente query:

  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;

Tuttavia, ricorda che una sottoquery opera su una vista indipendente dei dati, mentre una funzione di finestra opera sull'insieme fornito come input per la fase di elaborazione della query logica che gestisce la clausola SELECT. Di solito, la query sottostante ha una logica aggiuntiva come join, filtri, raggruppamenti e così via. Con le sottoquery, è necessario preparare un CTE preliminare o ripetere la logica della query sottostante anche nella sottoquery. Con le funzioni della finestra, non è necessario ripetere la logica.

Ad esempio, supponiamo che dovresti operare solo su ordini spediti (dove la data di spedizione non è NULL) che sono stati gestiti dal dipendente 3. La soluzione con la funzione finestra deve aggiungere i predicati del filtro solo una volta, in questo modo:

   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;

Questa query dovrebbe generare il seguente output:

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

La soluzione con la sottoquery deve aggiungere i predicati del filtro due volte, una nella query esterna e una nella sottoquery, in questo modo:

  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;

O questo, o l'aggiunta di un CTE preliminare che si occupi di tutto il filtraggio e di qualsiasi altra logica. Ad ogni modo, con le sottoquery, sono coinvolti più livelli di complessità.

L'altro vantaggio nelle funzioni della finestra nidificata è che se avessimo il supporto per quelle in T-SQL, sarebbe stato facile emulare il supporto completo mancante per l'unità frame della finestra RANGE. L'opzione RANGE dovrebbe consentire di definire frame dinamici basati su un offset dal valore di ordinamento nella riga corrente. Si supponga, ad esempio, di dover calcolare per ogni ordine cliente dalla visualizzazione Sales.OrderValues ​​il valore della media mobile degli ultimi 14 giorni. Secondo lo standard SQL, puoi ottenere questo risultato utilizzando l'opzione RANGE e il tipo INTERVAL, in questo modo:

  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;

Questa query dovrebbe generare il seguente output:

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

Alla data in cui scrivo, questa sintassi non è supportata in T-SQL. Se avessimo avuto il supporto per le funzioni della finestra nidificata in T-SQL, saresti stato in grado di emulare questa query con il codice seguente:

  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;

Cosa non va?

Esprimi il tuo voto

Le funzioni standard della finestra nidificata sembrano un concetto molto potente che consente molta flessibilità nell'interazione con diversi punti negli elementi della finestra. Sono piuttosto sorpreso di non riuscire a trovare alcuna copertura del concetto se non nello standard stesso e di non vedere molte piattaforme implementarlo. Speriamo che questo articolo aumenterà la consapevolezza per questa funzione. Se ritieni che possa essere utile per te averlo disponibile in T-SQL, assicurati di esprimere il tuo voto!