Background:SQL Server - aggregazione condizionale con correlazione
Il original case era molto semplice. Calcola totale corrente per utente dal più alto al più basso reddito:
CREATE TABLE t(Customer INTEGER NOT NULL PRIMARY KEY
,"User" VARCHAR(5) NOT NULL
,Revenue INTEGER NOT NULL);
INSERT INTO t(Customer,"User",Revenue) VALUES
(001,'James',500),(002,'James',750),(003,'James',450),
(004,'Sarah',100),(005,'Sarah',500),(006,'Sarah',150),
(007,'Sarah',600),(008,'James',150),(009,'James',100);
Query:
SELECT *,
1.0 * Revenue/SUM(Revenue) OVER(PARTITION BY "User") AS percentage,
1.0 * SUM(Revenue) OVER(PARTITION BY "User" ORDER BY Revenue DESC)
/SUM(Revenue) OVER(PARTITION BY "User") AS running_percentage
FROM t;
uscita:
╔════╦═══════╦═════════╦════════════╦════════════════════╗
║ ID ║ User ║ Revenue ║ percentage ║ running_percentage ║
╠════╬═══════╬═════════╬════════════╬════════════════════╣
║ 2 ║ James ║ 750 ║ 0.38 ║ 0.38 ║
║ 1 ║ James ║ 500 ║ 0.26 ║ 0.64 ║
║ 3 ║ James ║ 450 ║ 0.23 ║ 0.87 ║
║ 8 ║ James ║ 150 ║ 0.08 ║ 0.95 ║
║ 9 ║ James ║ 100 ║ 0.05 ║ 1 ║
║ 7 ║ Sarah ║ 600 ║ 0.44 ║ 0.44 ║
║ 5 ║ Sarah ║ 500 ║ 0.37 ║ 0.81 ║
║ 6 ║ Sarah ║ 150 ║ 0.11 ║ 0.93 ║
║ 4 ║ Sarah ║ 100 ║ 0.07 ║ 1 ║
╚════╩═══════╩═════════╩════════════╩════════════════════╝
Potrebbe essere calcolato in modo diverso utilizzando funzioni specifiche della finestra.
Ora supponiamo che non possiamo usare finestrato SUM
e riscriverlo:
SELECT c.Customer, c."User", c."Revenue"
,1.0 * Revenue/NULLIF(c3.s,0) AS percentage
,1.0 * c2.s /NULLIF(c3.s,0) AS running_percentage
FROM t c
CROSS APPLY
(SELECT SUM(Revenue) AS s
FROM t c2
WHERE c."User" = c2."User"
AND c2.Revenue >= c.Revenue) AS c2
CROSS APPLY
(SELECT SUM(Revenue) AS s
FROM t c2
WHERE c."User" = c2."User") AS c3
ORDER BY "User", Revenue DESC;
ho usato CROSS APPLY
perché non mi piace Sottointerrogazioni correlate a SELECT
colonne lista e c3
viene utilizzato due volte.
Tutto funziona come dovrebbe. Ma quando guardiamo più da vicino c2
e c3
sono molto simili. Quindi, perché non combinarli e utilizzare un'aggregazione condizionale semplice:
SELECT c.Customer, c."User", c."Revenue"
,1.0 * Revenue /NULLIF(c2.sum_total,0) AS percentage
,1.0 * c2.sum_running/NULLIF(c2.sum_total,0) AS running_percentage
FROM t c
CROSS APPLY
(SELECT SUM(Revenue) AS sum_total,
SUM(CASE WHEN c2.Revenue >= c.Revenue THEN Revenue ELSE 0 END)
AS sum_running
FROM t c2
WHERE c."User" = c2."User") AS c2
ORDER BY "User", Revenue DESC;
Purtroppo non è possibile.
Più colonne sono specificate in un'espressione aggregata contenente un riferimento esterno. Se un'espressione aggregata contiene un riferimento esterno, allora quel riferimento esterno deve essere l'unica colonna referenziata nell'espressione.
Naturalmente potrei aggirarlo avvolgendo con un altro subquery, ma diventa un po ' "brutto":
SELECT c.Customer, c."User", c."Revenue"
,1.0 * Revenue /NULLIF(c2.sum_total,0) AS percentage
,1.0 * c2.sum_running/NULLIF(c2.sum_total,0) AS running_percentage
FROM t c
CROSS APPLY
( SELECT SUM(Revenue) AS sum_total,
SUM(running_revenue) AS sum_running
FROM (SELECT Revenue,
CASE WHEN c2.Revenue >= c.Revenue THEN Revenue ELSE 0 END
AS running_revenue
FROM t c2
WHERE c."User" = c2."User") AS sub
) AS c2
ORDER BY "User", Revenue DESC
Postgresql
versione. L'unica differenza è LATERAL
anziché CROSS APPLY
.
SELECT c.Customer, c."User", c.Revenue
,1.0 * Revenue /NULLIF(c2.sum_total,0) AS percentage
,1.0 * c2.running_sum/NULLIF(c2.sum_total,0) AS running_percentage
FROM t c
,LATERAL (SELECT SUM(Revenue) AS sum_total,
SUM(CASE WHEN c2.Revenue >= c.Revenue THEN c2.Revenue ELSE 0 END)
AS running_sum
FROM t c2
WHERE c."User" = c2."User") c2
ORDER BY "User", Revenue DESC;
Funziona molto bello.
versioneSQLite
/MySQL
(è per questo che preferisco LATERAL/CROSS APPLY
):
SELECT c.Customer, c."User", c.Revenue,
1.0 * Revenue/(SELECT SUM(Revenue)
FROM t c2
WHERE c."User" = c2."User") AS percentage,
1.0 * (SELECT SUM(CASE WHEN c2.Revenue >= c.Revenue THEN c2.Revenue ELSE 0 END)
FROM t c2
WHERE c."User" = c2."User")/
(SELECT SUM(c2.Revenue)
FROM t c2
WHERE c."User" = c2."User") AS running_percentage
FROM t c
ORDER BY "User", Revenue DESC;
SQLFiddleDemo-SQLite
SQLFiddleDemo-MySQL
Ho letto Aggregates with an Outer Reference:
La fonte per la restrizione è nello standard
SQL-92
, eSQL Server
ereditato dalSybase
codebase. Il problema è che SQL Server deve capire quale query calcolerà l'aggregato.
Non cercare le risposte che solo mostrano come aggirarlo.
Le domande sono:
- Quale parte di respingere standard o interferire con esso?
- Perché altri RDBMS non hanno problemi con questo tipo di dipendenza esterna?
- Si estendono
SQL Standard
eSQL Server
si comporta come dovrebbe oSQL Server
non lo implementa completamente (correttamente?) ?.
sarei molto grato per i riferimenti a:
ISO standard
(92 o più recente)- SQL Server Standards Support
- documenation ufficiale da qualsiasi RDBMS che lo spiega (
SQL Server/Postgresql/Oracle/...
).
EDIT:
so che SQL-92
non hai concetto di LATERAL
. Ma la versione con sottoquery (come in SQLite/MySQL
) non funziona troppo.
EDIT 2:
Per semplificare un po ', cerchiamo di controllare solo subquery solo correlata:
SELECT c.Customer, c."User", c.Revenue,
1.0*(SELECT SUM(CASE WHEN c2.Revenue >= c.Revenue THEN c2.Revenue ELSE 0 END)
FROM t c2
WHERE c."User" = c2."User")
/(SELECT SUM(c2.Revenue)
FROM t c2
WHERE c."User" = c2."User") AS running_percentage
FROM t c
ORDER BY "User", Revenue DESC;
La versione di cui sopra funziona bene in MySQL/SQLite/Postgresql
.
In SQL Server
otteniamo errore. Dopo wraping con sottoquery per "appiattire" ad un livello funziona:
SELECT c.Customer, c."User", c.Revenue,
1.0 * (
SELECT SUM(CASE WHEN r1 >= r2 THEN r1 ELSE 0 END)
FROM (SELECT c2.Revenue AS r1, c.Revenue r2
FROM t c2
WHERE c."User" = c2."User") AS S)/
(SELECT SUM(c2.Revenue)
FROM t c2
WHERE c."User" = c2."User") AS running_percentage
FROM t c
ORDER BY "User", Revenue DESC;
Il punto di questa domanda è come si fa SQL standard
lo regolano.
Sì, oppure utilizzare 'CROSS APPLY' ** [Demo] (http://rextester.com/LOBM67950) **. "Nasconde" la sottoquery. – lad2025
'(VALORI (c.REVENUE)) x (REVENUE)' fa un lavoro migliore, secondo me. –
zucchero sintattico dipende dal gusto :) La prima nota è vera, 'LATERAL' (anche il tipo di zucchero sintattico) è un concetto più recente. – lad2025