Calcule el total de SUMA (columna)

9

Tengo este código que resume la cantidad de un determinado artículo ( itemid) y por su código de fecha del producto ( proddte).

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

Lo que quiero hacer es obtener el total de todos qtyindependientemente de itemid/proddte. Yo he tratado:

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

Pero dice que también debería tener qtyen la group bycláusula. Si hice eso, el resultado no será igual a mi resultado esperado.

No es necesario que se represente como una columna separada, con el mismo valor en cada fila. Se acepta cualquier representación siempre que pueda mostrar el total general.

niq
fuente

Respuestas:

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;

El GROUP BY GROUPING SETSes básicamente un UNION ALL. Los ()medios simplemente toman, SUMindependientemente de la agrupación, cualquier otro grupo enumerado se agrega por separado. Intenta GROUP BY GROUPING SETS ((itemid),(itemid,proddte))ver la diferencia.

Para más detalles ver la documentación:

Uso de GROUP BY con ROLLUP, CUBE y GROUPING SETS

Como mencionó Andriy, la consulta anterior también podría escribirse usando:

GROUP BY ROLLUP( (itemid,proddte) )

Tenga en cuenta que las dos columnas están encerradas entre paréntesis adicionales, lo que las convierte en una sola unidad. Andriy escribió una demostración alojada en Stack Exchange Data Explorer.

Aaron Bertrand
fuente
1
@niq: GROUP BY ROLLUP((itemid,proddte))produciría el mismo resultado y podría ser menos confuso.
Andriy M
@AndriyM que no es equivalente ya que incluirá un subtotal para itemidIe Es equivalente aGROUP BY GROUPING SETS((),(itemid),(itemid,proddte))
Martin Smith
44
@MartinSmith: No, las columnas están encerradas en un par de corchetes adicionales, lo que las convierte en una sola unidad. GROUP BY ROLLUP(itemid,proddte), por otro lado, de hecho produciría subtotales (adicionales) en itemid(igual que GROUP BY ROLLUP((itemid),(proddte))). Demo en SEDE
Andriy M
3
@AndriyM Estoy corregido. Aunque eso socava el punto "menos confuso" porque logró confundir al menos a una persona :-)
Martin Smith
2
@AndriyM No me parece GROUP BY ROLLUPmenos confuso, pero es bastante subjetivo. También siempre me pongo nervioso cuando leo cosas como The non-ISO compliant WITH ROLLUP, WITH CUBE, and ALL syntax is deprecated: por qué tiendo a favorecer GROUPING SETS.
Aaron Bertrand
10

Esta también es una sintaxis válida:

       sum(sum(qty)) over ()

Es un poco confuso cuando uno lo ve al principio, pero solo debe recordar que las funciones de la ventana, por ejemplo sum() over (), se aplican después, group bypor lo que todo lo que puede aparecer en la lista de selección de un grupo por consulta se puede colocar dentro de un agregado de ventana. Entonces (el qtyno puede pero) el sum(qty)puede colocarse dentro sum() over ():

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

Dicho esto, prefiero la GROUPING SETSconsulta proporcionada por Aaron Bertrand. La suma total debe mostrarse una vez y no en cada fila.

También tenga en cuenta que, si bien la suma de sumas se puede usar para calcular la suma total, si desea el recuento total, debe usar la suma de los recuentos (¡y no el recuento de los recuentos!):

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

Y si uno quisiera el promedio sobre toda la tabla, sería aún más complicado:

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

porque el promedio de los promedios no es igual al promedio de todos. (Si lo prueba avg(avg(qty)) over (), verá que puede producir un resultado diferente al gran promedio anterior).

ypercubeᵀᴹ
fuente
3

Una posible forma es envolver el primero GROUP BYen 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
fuente
3
No hay necesidad de CTE como lo ilustra la respuesta de ypercube
Martin Smith
1
@ MartinSmith, tienes razón. Cualquier CTE no recursivo puede reescribirse como una subconsulta de alguna forma. El optimizador de SQL Server incorpora CTE de todos modos (a diferencia de Postgres, por ejemplo), por lo que el plan de ejecución es el mismo con CTE o sin él. Sin embargo, a menudo es más fácil leer y comprender consultas complejas si se dividen en partes más simples utilizando CTE. Al menos para mi.
Vladimir Baranov
3
Creo que entendió mal el punto de Martin, aunque su punto sobre los CTE que agregan legibilidad aún puede mantenerse. Lo que muestra la sugerencia de ypercube es que puede evitar una subconsulta de cualquier forma en este caso, ya sea un CTE, una tabla derivada o una columna calculada como una subconsulta de agregación escalar.
Andriy M
1
@AndriyM, me gusta la variante de la respuesta de ypercube y no pensé en esa sintaxis antes de verla aquí. Siempre es bueno aprender algo nuevo. Tienes razón, mi punto principal se reduce a la legibilidad. En mis pruebas, el optimizador generó el mismo plan de ejecución, con CTE o sin él.
Vladimir Baranov