Подсчитать общую сумму (столбец)

9

У меня есть этот код, который суммирует количество для определенного элемента ( itemid) и по коду даты продукта ( proddte).

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

То, что я хочу сделать, это получить общее количество qtyнезависимо от itemid/proddte. Я пытался:

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

Но это говорит, что я должен также иметь qtyв group byпункте. Если я это сделаю, результат не будет равен моему ожидаемому результату.

Он не обязательно должен быть представлен в виде отдельного столбца с одинаковым значением в каждой строке. Любое представление принимается, пока я могу отображать общую сумму.

niq
источник

Ответы:

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;

Это GROUP BY GROUPING SETSв основном UNION ALL. Эти ()средства просто взять SUMнезависимо от группировки, любая другая группа в списке получает агрегируются отдельно. Попробуйте GROUP BY GROUPING SETS ((itemid),(itemid,proddte))увидеть разницу.

Для более подробной информации смотрите документацию:

Использование GROUP BY с ROLLUP, CUBE и GROUPING SETS

Как упомянул Андрей, запрос выше также может быть написан с использованием:

GROUP BY ROLLUP( (itemid,proddte) )

Обратите внимание, что два столбца заключены в дополнительную пару скобок, что делает их единым целым. Андрей написал демоверсию, размещенную в Stack Exchange Data Explorer.

Аарон Бертран
источник
1
@niq: GROUP BY ROLLUP((itemid,proddte))даст тот же результат и может быть менее запутанным.
Андрей М
@AndriyM, который не эквивалентен, так как будет включать в себя промежуточный итог для itemidIe. Это эквивалентноGROUP BY GROUPING SETS((),(itemid),(itemid,proddte))
Martin Smith
4
@MartinSmith: Нет, столбцы заключены в дополнительную пару скобок, что делает их единым целым. GROUP BY ROLLUP(itemid,proddte)с другой стороны, действительно будет производить (дополнительные) промежуточные итоги по itemid(так же, как GROUP BY ROLLUP((itemid),(proddte))). Демонстрация на SEDE
Андрей М
3
@AndriyM Я исправлен. Хотя это подрывает «менее запутанный» момент, потому что он сумел запутать хотя бы одного человека :-)
Martin Smith
2
@AndriyM Я не нахожу GROUP BY ROLLUPменее запутанным, но это довольно субъективно. Я также всегда нервничаю , когда я читаю такие вещи , как The non-ISO compliant WITH ROLLUP, WITH CUBE, and ALL syntax is deprecated- почему я склоняюсь в пользу GROUPING SETS.
Аарон Бертран
10

Это также правильный синтаксис:

       sum(sum(qty)) over ()

Это немного сбивает с толку, когда вы видите это сначала, но вам нужно только помнить, что оконные функции - например, sum() over ()- применяются после того, как group byвсе, что может появиться в списке выбора группы по запросу, может быть помещено в агрегат окна. Так ( qtyне может, но) sum(qty)может быть размещен внутри sum() over ():

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

Сказав это, я бы предпочел GROUPING SETSзапрос, предоставленный Аароном Бертраном. Общая сумма должна быть показана один раз, а не в каждой строке.

Также обратите внимание, что хотя сумму сумм можно использовать для вычисления общей суммы, если вы хотите получить общее количество, вам придется использовать сумму подсчетов (а не подсчет подсчетов!):

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

И если бы кто-то хотел получить среднее значение по всей таблице, это было бы еще сложнее:

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

потому что среднее значение средних не совпадает со средним по всем. (Если вы попробуете, avg(avg(qty)) over ()вы увидите, что он может дать результат, отличный от указанного выше среднего показателя.)

ypercubeᵀᴹ
источник
3

Один из возможных способов - обернуть первый GROUP BYв 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
Нет необходимости в CTE, как показывает ответ ypercube
Martin Smith
1
@MartinSmith, ты прав. Любой нерекурсивный CTE может быть переписан как подзапрос некоторой формы. Оптимизатор SQL Server в любом случае включает CTE (в отличие от Postgres), поэтому план выполнения такой же, как с CTE, так и без него. Однако довольно часто проще читать и понимать сложные запросы, если они разбиты на более простые части с помощью CTE. По крайней мере для меня.
Владимир Баранов
3
Я думаю, что вы неправильно поняли точку зрения Мартина, хотя ваша точка зрения о том, что CTE добавляет удобочитаемость, все еще остается в силе. Предложение ypercube показывает, что в этом случае вы можете избежать подзапроса любой формы, будь то CTE, производная таблица или вычисляемый столбец в качестве подзапроса скалярной агрегации.
Андрей М
1
@AndriyM, мне нравится вариант из ответа ypercube, и я не думал о таком синтаксисе, пока не увидел его здесь. Всегда хорошо узнавать что-то новое. Вы правы, моя главная мысль сводится к удобочитаемости. В моих тестах оптимизатор генерировал тот же план выполнения, с CTE или без.
Владимир Баранов