Calcular o total de SUM (coluna)

9

Eu tenho esse código que resume a quantidade de um determinado item ( itemid) e pelo código de data do produto ( proddte).

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

O que eu quero fazer é obter o total de todos, qtyindependentemente de itemid/proddte. Eu tentei:

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

Mas diz que eu também deveria ter qtyna group bycláusula. Se eu fiz isso, o resultado não será igual ao meu resultado esperado.

Não precisa absolutamente ser representado como uma coluna separada, com o mesmo valor em cada linha. Qualquer representação é aceita, desde que eu possa exibir o total geral.

niq
fonte

Respostas:

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;

O GROUP BY GROUPING SETSé é basicamente um UNION ALL. Os ()meios apenas levam em consideração o SUMagrupamento, qualquer outro grupo listado é agregado separadamente. Tente GROUP BY GROUPING SETS ((itemid),(itemid,proddte))ver a diferença.

Para mais detalhes, consulte a documentação:

Usando GROUP BY com ROLLUP, CUBE e GROUPING SETS

Como Andriy mencionou, a consulta acima também pode ser escrita usando:

GROUP BY ROLLUP( (itemid,proddte) )

Observe as duas colunas que estão entre parênteses adicionais, tornando-as uma única unidade. Andriy escreveu uma demonstração hospedada no Stack Exchange Data Explorer.

Aaron Bertrand
fonte
11
@niq: GROUP BY ROLLUP((itemid,proddte))produziria o mesmo resultado e poderia ser menos confuso.
Andriy M
@AndriyM que não é equivalente, uma vez que irá incluir uma sub total para itemidie é equivalente aGROUP BY GROUPING SETS((),(itemid),(itemid,proddte))
Martin Smith
4
@ MartinSmith: Não, as colunas são colocadas em um par adicional de colchetes, o que os torna uma única unidade. GROUP BY ROLLUP(itemid,proddte), por outro lado, produziria subtotais (adicionais) em itemid(o mesmo que GROUP BY ROLLUP((itemid),(proddte))). Demo em SEDE
Andriy M
3
@AndriyM Estou corrigido. No entanto, que põe em causa o ponto "menos confuso" porque conseguiu confundir pelo menos uma pessoa :-)
Martin Smith
2
@AndriyM Não acho GROUP BY ROLLUPmenos confuso, mas é bastante subjetivo. Também fico sempre nervoso quando leio coisas como The non-ISO compliant WITH ROLLUP, WITH CUBE, and ALL syntax is deprecated- por que tenho tendência a favorecer GROUPING SETS.
Aaron Bertrand
10

Essa sintaxe também é válida:

       sum(sum(qty)) over ()

É um pouco confuso quando a gente vê pela primeira vez, mas você só precisa se lembrar de que as funções da janela - por exemplo sum() over ()- são aplicadas após as funções, group byentão tudo o que pode aparecer na lista de seleção de um grupo por consulta pode ser colocado dentro de uma janela agregada. Então (o qtynão pode mas) o sum(qty)pode ser colocado dentro sum() over ():

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

Dito isto, prefiro a GROUPING SETSconsulta fornecida por Aaron Bertrand. A soma total precisa ser mostrada uma vez e não em todas as linhas.

Observe também que, embora a soma das somas possa ser usada para calcular a soma total, se você quiser a contagem total, será necessário usar a soma das contagens (e não a contagem de contagens!):

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

E se alguém quisesse a média sobre toda a mesa, seria ainda mais complicado:

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

porque a média das médias não é a mesma que a média geral. (Se você tentar, avg(avg(qty)) over ()verá que pode resultar em um resultado diferente da média geral acima.)

ypercubeᵀᴹ
fonte
3

Uma maneira possível é envolver o primeiro GROUP BYno 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
fonte
3
Não há nenhuma necessidade para o CTE como a resposta da ypercube ilustra
Martin Smith
11
@ MartinSmith, você está certo. Qualquer CTE não recursiva pode ser reescrita como uma subconsulta de alguma forma. O otimizador do SQL Server alinha os CTEs de qualquer maneira (em oposição ao Postgres, por exemplo); portanto, o plano de execução é o mesmo com o CTE ou sem. Muitas vezes, porém, é mais fácil ler e entender consultas complexas se elas forem divididas em partes mais simples usando o CTE. Pelo menos para mim.
Vladimir Baranov
3
Acho que você não entendeu o ponto de vista de Martin, embora seu argumento sobre as CTEs acrescentando legibilidade ainda possa permanecer. O que a sugestão do ypercube mostra é que você pode evitar uma subconsulta de qualquer forma nesse caso, seja uma CTE, uma tabela derivada ou uma coluna calculada como uma subconsulta de agregação escalar.
Andriy M
11
@AndriyM, eu gosto da variante da resposta do ypercube e não pensei nessa sintaxe antes de vê-la aqui. É sempre bom aprender algo novo. Você está certo, meu ponto principal se resume à legibilidade. Nos meus testes, o otimizador gerou o mesmo plano de execução, com ou sem CTE.
Vladimir Baranov