Calculer le total de SUM (colonne)

9

J'ai ce code qui résume la quantité d'un certain article ( itemid) et par son code de date de produit ( proddte).

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

Ce que je veux faire, c'est obtenir le total de tous qtyindépendamment de itemid/proddte. J'ai essayé:

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

Mais cela dit que j'aurais dû également le faire qtydans l' group byarticle. Si je l'ai fait, le résultat ne sera pas égal à mon résultat attendu.

Il n'est pas absolument nécessaire de la représenter comme une colonne distincte, avec la même valeur dans chaque ligne. Toute représentation est acceptée tant que je peux afficher le total global.

niq
la source

Réponses:

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;

C'est GROUP BY GROUPING SETSessentiellement un UNION ALL. Les ()moyens prennent simplement le SUMindépendamment du regroupement, tout autre groupe répertorié est agrégé séparément. Essayez GROUP BY GROUPING SETS ((itemid),(itemid,proddte))de voir la différence.

Pour plus de détails, consultez la documentation:

Utilisation de GROUP BY avec ROLLUP, CUBE et GROUPING SETS

Comme Andriy l'a mentionné, la requête ci-dessus peut également être écrite en utilisant:

GROUP BY ROLLUP( (itemid,proddte) )

Notez que les deux colonnes y sont entourées d'une paire supplémentaire de parenthèses, ce qui en fait une seule unité. Andriy a écrit une démo hébergée sur l'explorateur de données Exchange Stack.

Aaron Bertrand
la source
1
@niq: GROUP BY ROLLUP((itemid,proddte))produirait le même résultat et pourrait être moins déroutant.
Andriy M
@AndriyM qui n'est pas équivalent car il inclura un sous-total pour itemidIe Il est équivalent àGROUP BY GROUPING SETS((),(itemid),(itemid,proddte))
Martin Smith
4
@MartinSmith: Non, les colonnes sont enfermées dans une paire supplémentaire de crochets, ce qui en fait une seule unité. GROUP BY ROLLUP(itemid,proddte), d'autre part, produirait en effet des sous-totaux (supplémentaires) sur itemid(les mêmes que GROUP BY ROLLUP((itemid),(proddte))). Démo chez SEDE
Andriy M
3
@AndriyM je me tiens corrigé. Bien que cela sape le point "moins déroutant" car il a réussi à confondre au moins une personne :-)
Martin Smith
2
@AndriyM Je ne trouve pas GROUP BY ROLLUPmoins déroutant, mais c'est plutôt subjectif. Je suis aussi toujours nerveux quand je lis des choses comme The non-ISO compliant WITH ROLLUP, WITH CUBE, and ALL syntax is deprecated- pourquoi j'ai tendance à favoriser GROUPING SETS.
Aaron Bertrand
10

C'est aussi une syntaxe valide:

       sum(sum(qty)) over ()

C'est un peu déroutant quand on le voit au début, mais il suffit de se rappeler que les fonctions de fenêtre - par exemple sum() over ()- sont appliquées après le group byafin que tout ce qui peut apparaître dans la liste de sélection d'un groupe par requête puisse être placé dans un agrégat de fenêtre. Donc (le qtyne peut pas mais) le sum(qty)peut être placé à l'intérieur sum() over ():

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

Cela dit, je préfère la GROUPING SETSrequête fournie par Aaron Bertrand. La somme totale doit être affichée une fois et non sur chaque ligne.

Notez également que si la somme des sommes peut être utilisée pour calculer la somme totale, si vous voulez le nombre total, vous devrez utiliser la somme des comptes (et non le nombre de comptes!):

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

Et si l'on voulait la moyenne sur toute la table, ce serait encore plus compliqué:

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

parce que la moyenne des moyennes n'est pas la même que la moyenne dans l'ensemble. (Si vous essayez, avg(avg(qty)) over ()vous verrez que cela peut donner un résultat différent de la grande moyenne ci-dessus.)

ypercubeᵀᴹ
la source
3

Une solution possible consiste à envelopper le premier GROUP BYdans 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
;
Vladimir Baranov
la source
3
Il n'y a pas besoin de CTE comme l'illustre la réponse d'Ypercube
Martin Smith
1
@MartinSmith, vous avez raison. Tout CTE non récursif peut être réécrit en tant que sous-requête d'une certaine forme. L'optimiseur de SQL Server intègre de toute façon les CTE (par opposition à Postgres, par exemple), donc le plan d'exécution est le même avec CTE ou sans. Très souvent, cependant, il est plus facile de lire et de comprendre les requêtes complexes si elles sont décomposées en parties plus simples à l'aide de CTE. Au moins pour moi.
Vladimir Baranov
3
Je pense que vous avez mal compris le point de Martin, bien que votre point sur les CTE ajoutant la lisibilité peut toujours être valable. La suggestion de ypercube montre que vous pouvez éviter une sous-requête de n'importe quelle forme dans ce cas, qu'il s'agisse d'un CTE, d'une table dérivée ou d'une colonne calculée en tant que sous-requête d'agrégation scalaire.
Andriy M
1
@AndriyM, j'aime la variante de la réponse de ypercube et je n'ai pas pensé à une telle syntaxe avant de la voir ici. Il est toujours bon d'apprendre quelque chose de nouveau. Vous avez raison, mon argument principal se résume à la lisibilité. Dans mes tests, l'optimiseur a généré le même plan d'exécution, avec ou sans CTE.
Vladimir Baranov