Calcola il totale di SUM (colonna)


9

Ho questo codice che riassume la quantità per un determinato articolo ( itemid) e dal suo codice data prodotto ( proddte).

select sum(qty), itemid, proddte 
from testtable where .... 
group by itemid, proddte

Quello che voglio fare è ottenere il totale di tutti qtyindipendentemente da itemid/proddte. Ho provato:

select sum(qty), itemid, proddte, sum(qty) over() as grandtotal 
from testtable 
where .... 
group by itemid, proddte

Ma dice che dovrei avere anche qtynella group byclausola. Se lo facessi, il risultato non sarà uguale al mio risultato previsto.

Non deve assolutamente essere rappresentato come una colonna separata, con lo stesso valore in ogni riga. Qualsiasi rappresentazione è accettata fino a quando posso visualizzare il totale complessivo.

Risposte:


9
CREATE TABLE #foo
(
 itemid int, 
 proddte date,
 qty int
);

INSERT #foo(itemid,proddte,qty) VALUES
(1,'20140101',5),(1,'20140102',7),(2,'20150101',10);


-- if it really needs to be a column with the same value
-- in every row, just calculate once and assign it to a variable

DECLARE @sum int = (SELECT SUM(qty) FROM #foo);

SELECT itemid, proddte, GroupedSum = SUM(qty), GrandTotal = @sum
  FROM #foo
  GROUP BY itemid, proddte;

-- if the grand total can be expressed on its own row, 
-- you can use GROUP BY GROUPING SETS:
SELECT itemid, proddte, SUM(qty)
  FROM #foo GROUP BY GROUPING SETS((),(itemid,proddte));

-- if that syntax is confusing, you can use a less
-- efficient UNION ALL:
SELECT itemid, proddte, SUM(qty)
  FROM #foo GROUP BY itemid,proddte
UNION ALL
SELECT NULL, NULL, SUM(qty) 
  FROM #foo;

GO
DROP TABLE #foo;

L' GROUP BY GROUPING SETSis è fondamentalmente un UNION ALL. I ()mezzi prendono semplicemente SUMindipendentemente dal raggruppamento, qualsiasi altro gruppo elencato viene aggregato separatamente. Prova GROUP BY GROUPING SETS ((itemid),(itemid,proddte))a vedere la differenza.

Per maggiori dettagli consultare la documentazione:

Utilizzo di GROUP BY con ROLLUP, CUBE e GROUPING SETS

Come menzionato da Andriy, la query sopra potrebbe anche essere scritta usando:

GROUP BY ROLLUP( (itemid,proddte) )

Nota che le due colonne sono racchiuse in una coppia aggiuntiva di parentesi, rendendole una singola unità. Andriy ha scritto una demo ospitata su Stack Exchange Data Explorer.


1
@niq: GROUP BY ROLLUP((itemid,proddte))produrrebbe lo stesso risultato e potrebbe essere meno confuso.
Andriy M,

@AndriyM che non è equivalente in quanto includerà un totale parziale per itemidIe È equivalente aGROUP BY GROUPING SETS((),(itemid),(itemid,proddte))
Martin Smith

4
@MartinSmith: No, le colonne sono racchiuse in una coppia aggiuntiva di parentesi, che le rende una singola unità. GROUP BY ROLLUP(itemid,proddte)d'altra parte, produrrebbe effettivamente (ulteriori) subtotali su itemid(uguale a GROUP BY ROLLUP((itemid),(proddte))). Demo al SEDE
Andriy M

3
@AndriyM Sono corretto. Anche se questo mina il punto "meno confuso" perché è riuscito a confondere almeno una persona :-)
Martin Smith

2
@AndriyM Non lo trovo GROUP BY ROLLUPmeno confuso, ma è piuttosto soggettivo. Inoltre mi innervosisco sempre quando leggo cose come The non-ISO compliant WITH ROLLUP, WITH CUBE, and ALL syntax is deprecated- perché tendo a favorire GROUPING SETS.
Aaron Bertrand

10

Anche questa è sintassi valida:

       sum(sum(qty)) over ()

È un po 'confuso quando lo si vede all'inizio, ma si deve solo ricordare che le funzioni della finestra, ad esempio sum() over (), vengono applicate dopo group byche tutto ciò che può apparire nell'elenco di selezione di un gruppo per query può essere inserito in un aggregato di finestre. Quindi (il qtynon può ma) il sum(qty)può essere inserito all'interno sum() over ():

select sum(qty), itemid, proddte, 
       sum(sum(qty)) over () as grandtotal  
from testtable 
where .... 
group by itemid, proddte ;

Detto questo, preferirei l' GROUPING SETSinterrogazione fornita da Aaron Bertrand. La somma totale deve essere mostrata una volta e non in ogni riga.

Si noti inoltre che mentre la somma delle somme può essere utilizzata per calcolare la somma totale, se si desidera il conteggio totale, è necessario utilizzare la somma dei conteggi (e non il conteggio dei conteggi!):

sum(count(*)) over ()  as grand_count

E se si volesse la media su tutto il tavolo, sarebbe ancora più complicato:

sum(sum(qty)) over ()
/ sum(count(qty)) over ()  as grand_average

perché la media delle medie non è la stessa della media. (Se provi il avg(avg(qty)) over (), vedrai che potrebbe produrre un risultato diverso dalla media generale sopra.)


3

Un possibile modo per aggirare è avvolgere il primo GROUP BYin CTE :

WITH
CTE
AS
(
    select
        itemid
        ,proddte
        ,sum(qty) AS SumQty
    from testtable 
    where .... 
    group by itemid, proddte
)
SELECT
    itemid
    ,proddte
    ,SumQty
    ,SUM(SumQty) OVER () AS grandtotal
FROM CTE
;

3
Non è necessario il CTE, come dimostra la risposta di ypercube
Martin Smith,

1
@MartinSmith, hai ragione. Qualsiasi CTE non ricorsivo può essere riscritto come una sottoquery di qualche forma. L'ottimizzatore di SQL Server integra comunque i CTE (al contrario di Postgres, ad esempio), quindi il piano di esecuzione è lo stesso con CTE o senza. Molto spesso, tuttavia, è più facile leggere e comprendere query complesse se vengono suddivise in parti più semplici utilizzando CTE. Almeno per me.
Vladimir Baranov,

3
Penso che tu abbia frainteso il punto di Martin, anche se il tuo punto sui CTE che aggiungono la leggibilità potrebbe ancora essere valido. Ciò che mostra il suggerimento di ypercube è che in questo caso è possibile evitare una sottoquery di qualsiasi forma, che sia un CTE, una tabella derivata o una colonna calcolata come sottoquery di aggregazione scalare.
Andriy M,

1
@AndriyM, mi piace la variante della risposta di ypercube e non ho pensato a tale sintassi prima di vederla qui. È sempre bello imparare qualcosa di nuovo. Hai ragione, il mio punto principale si riduce alla leggibilità. Nei miei test, l'ottimizzatore ha generato lo stesso piano di esecuzione, con CTE o senza.
Vladimir Baranov,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.