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


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пункте. Если я это сделаю, результат не будет равен моему ожидаемому результату.

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

Ответы:


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 ()вы увидите, что он может дать результат, отличный от указанного выше среднего показателя.)


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 или без.
Владимир Баранов
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.