Berechnen Sie die Summe von SUM (Spalte)

9

Ich habe diesen Code, der die Menge für einen bestimmten Artikel ( itemid) und den Produktdatumscode ( proddte) zusammenfasst.

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

Was ich tun möchte, ist, die Summe aller zu erhalten, qtyunabhängig davon itemid/proddte. Ich habe versucht:

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

Aber es heißt, ich sollte auch qtyin der group byKlausel haben. Wenn ich das getan habe, entspricht das Ergebnis nicht meinem erwarteten Ergebnis.

Es muss nicht unbedingt als separate Spalte mit demselben Wert in jeder Zeile dargestellt werden. Jede Darstellung wird akzeptiert, solange ich die Gesamtsumme anzeigen kann.

niq
quelle

Antworten:

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;

Das GROUP BY GROUPING SETSist im Grunde ein UNION ALL. Die ()Mittel nehmen einfach die SUMunabhängig von der Gruppierung, jede andere aufgelistete Gruppe wird separat aggregiert. Versuchen Sie GROUP BY GROUPING SETS ((itemid),(itemid,proddte)), den Unterschied zu erkennen.

Weitere Details finden Sie in der Dokumentation:

Verwenden von GROUP BY mit ROLLUP-, CUBE- und GROUPING-SETS

Wie Andriy erwähnte, könnte die obige Abfrage auch geschrieben werden mit:

GROUP BY ROLLUP( (itemid,proddte) )

Beachten Sie, dass die beiden dortigen Spalten in einem zusätzlichen Klammerpaar eingeschlossen sind, sodass sie eine Einheit bilden. Andriy hat eine Demo geschrieben, die im Stack Exchange Data Explorer gehostet wird.

Aaron Bertrand
quelle
1
@niq: GROUP BY ROLLUP((itemid,proddte))würde das gleiche Ergebnis liefern und könnte weniger verwirrend sein.
Andriy M
@AndriyM, das nicht äquivalent ist, da es eine Zwischensumme für itemidIe enthält. Es ist äquivalent zuGROUP BY GROUPING SETS((),(itemid),(itemid,proddte))
Martin Smith
4
@MartinSmith: Nein, die Spalten sind in einem zusätzlichen Klammerpaar eingeschlossen, wodurch sie zu einer Einheit werden. GROUP BY ROLLUP(itemid,proddte)Andererseits würde in der Tat (zusätzliche) Zwischensummen auf itemid(wie GROUP BY ROLLUP((itemid),(proddte))) erzeugt. Demo bei SEDE
Andriy M
3
@AndriyM Ich stehe korrigiert. Dies untergräbt jedoch den "weniger verwirrenden" Punkt, da es gelungen ist, mindestens eine Person zu verwirren :-)
Martin Smith
2
@AndriyM Ich finde nicht GROUP BY ROLLUPweniger verwirrend, aber es ist eher subjektiv. Ich werde auch immer nervös, wenn ich Dinge lese wie The non-ISO compliant WITH ROLLUP, WITH CUBE, and ALL syntax is deprecated- warum ich dazu neige, zu bevorzugen GROUPING SETS.
Aaron Bertrand
10

Dies ist auch eine gültige Syntax:

       sum(sum(qty)) over ()

Es ist etwas verwirrend, wenn man es zuerst sieht, aber man muss nur daran denken, dass die Fensterfunktionen - z. B. sum() over ()- nach dem angewendet werden, group bydamit alles, was in der Auswahlliste einer Gruppe nach Abfrage erscheinen kann, in einem Fensteraggregat platziert werden kann. Also (das qtykann aber nicht) das sum(qty)kann drinnen platziert werden sum() over ():

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

Trotzdem würde ich die GROUPING SETSAnfrage von Aaron Bertrand vorziehen . Die Gesamtsumme muss einmal und nicht in jeder Zeile angezeigt werden.

Beachten Sie auch, dass die Summe der Summen zwar zur Berechnung der Gesamtsumme verwendet werden kann, Sie jedoch die Summe der Zählungen (und nicht die Anzahl der Zählungen!) Verwenden müssen, wenn Sie die Gesamtanzahl wünschen.

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

Und wenn man den Durchschnitt über die gesamte Tabelle hinweg haben möchte, wäre das noch komplizierter:

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

weil der Durchschnitt der Durchschnittswerte nicht der Durchschnitt aller ist. (Wenn Sie das versuchen, werden avg(avg(qty)) over ()Sie feststellen, dass es möglicherweise zu einem anderen Ergebnis als dem oben genannten Durchschnittswert führt.)

ypercubeᵀᴹ
quelle
3

Eine Möglichkeit, dies zu umgehen, besteht darin, den ersten GROUP BYin CTE zu verpacken :

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
;
Vladimir Baranov
quelle
3
Der CTE ist nicht erforderlich, wie die Antwort von ypercube zeigt
Martin Smith,
1
@ MartinSmith, du hast recht. Jeder nicht rekursive CTE kann als Unterabfrage in irgendeiner Form neu geschrieben werden. Das Optimierungsprogramm von SQL Server integriert CTEs ohnehin (im Gegensatz zu beispielsweise Postgres), sodass der Ausführungsplan mit oder ohne CTE identisch ist. Sehr oft ist es jedoch einfacher, komplexe Abfragen zu lesen und zu verstehen, wenn sie mit CTE in einfachere Teile zerlegt werden. Zumindest für mich.
Vladimir Baranov
3
Ich denke, Sie haben Martins Argument missverstanden, obwohl Ihr Argument, dass CTEs die Lesbarkeit verbessern, möglicherweise immer noch Bestand hat. Der Vorschlag von ypercube zeigt, dass Sie in diesem Fall eine Unterabfrage in beliebiger Form vermeiden können , sei es ein CTE, eine abgeleitete Tabelle oder eine berechnete Spalte als Unterabfrage für die skalare Aggregation.
Andriy M
1
@AndriyM, ich mag die Variante von ypercubes Antwort und ich habe nicht an eine solche Syntax gedacht, bevor ich sie hier gesehen habe. Es ist immer gut, etwas Neues zu lernen. Sie haben Recht, mein Hauptpunkt ist die Lesbarkeit. In meinen Tests hat der Optimierer den gleichen Ausführungsplan mit oder ohne CTE generiert.
Vladimir Baranov