Как свернуть подсчет количества групп
ROLLUP позволяет агрегировать по нескольким уровням группировки, как будто я UNIONed несколько простых операторов SELECT.
Но я хочу иметь возможность агрегировать результаты более низкого уровня группировки, как если бы я использовал вложенные операторы SELECT или цепочку CTE, зависящих друг от друга.
Например, я хочу иметь возможность подсчитывать количество групп с более низкого уровня группировки или вычислять среднее значение сумм более низкого уровня или минимум максимумов более низкого уровня и т. Д.
Более конкретный пример: если у меня есть запись для каждого автокатастрофы в США, и я хочу получить не только количество несчастных случаев на каждом уровне в ROLLUP(штат, округ, город, почтовый индекс), но и количество людей (очевидно, каждый человек может быть участвует в нескольких несчастных случаях и, следовательно, нескольких записей).
Можно ли добиться этого с ROLLUP? Если возможно, то как?
Пример SQL с результатами:
if object_id('accident') is not null drop table accident
create table accident(
id int identity(1,1)
,state varchar(50)
,city varchar(50)
,zip varchar(50)
,person varchar(50)
)
insert accident(state,city,zip,person)values
('NY','Manhattan',10001,'John')
,('NY','Manhattan',10001,'John')
,('NY','Manhattan',10001,'Barbara')
select
state,city,zip,person
,accidents=count(1)
-- the following line causes error: Windowed functions cannot be used in the context of another windowed function or aggregate.
--,people=sum(case when row_number()over(partition by person order by (select 0))=1 then 1 else 0 end)
from accident
group by rollup(state,city,zip,person)
;with person as (select state,city,zip,person from accident group by state,city,zip,person)
select
state,city,zip
,people=count(1)
from person
group by rollup(state,city,zip)
Результаты:
state city zip person accidents
NY Manhattan 10001 Barbara 1
NY Manhattan 10001 John 2
NY Manhattan 10001 NULL 3
NY Manhattan NULL NULL 3
NY NULL NULL NULL 3
NULL NULL NULL NULL 3
state city zip people
NY Manhattan 10001 2
NY Manhattan NULL 2
NY NULL NULL 2
NULL NULL NULL 2
См. Первый результат возвращает 3 несчастных случая для каждого уровня, а второй возвращает 2. Если вы хотите получить 3 и 2 в одном запросе ROLLUP. Моя проблема в том, что оконные функции не могут быть вложенными.
То, что я только что спросил, может быть достигнуто с помощью этого запроса:
;with person as (select state,city,zip,person,accidents=count(1) from accident group by state,city,zip,person)
select
state,city,zip
,accidents=sum(accidents)
,people=count(1)
from person
group by rollup(state,city,zip)
state city zip accidents people
NY Manhattan 10001 3 2
NY Manhattan NULL 3 2
NY NULL NULL 3 2
NULL NULL NULL 3 2
но такой способ требует написания CTE для каждого уровня в явном виде.
Я хочу иметь возможность написать один запрос, который имеет доступ к результатам более низкого уровня группировки независимо от количества уровней группировки.
Пробовал это:
;with
lvl as (
select *
,lvl = -1
,accidents=1
,people=1
from accident
union all
select accident.*
,lvl = grouping_id(accident.state,accident.city,accident.zip,accident.person)
,accidents=sum(accidents)
,people=count(1)
from accident
join lvl prev on prev.lvl = (grouping_id(accident.state,accident.city,accident.zip,accident.person)+1)/2-1
group by rollup(accident.state,accident.city,accident.zip,accident.person)
)
select * from lvl
Но есть ошибки:
Msg 1015, Level 15, State 1, ...
An aggregate cannot appear in an ON clause unless it is in a subquery contained in a HAVING clause or select list, and the column being aggregated is an outer reference.
Msg 467, Level 16, State 1, ...
GROUP BY, HAVING, or aggregate functions are not allowed in the recursive part of a recursive common table expression 'lvl'.
Связанный вопрос: рекурсивная функция sql с логикой свертки?