У меня есть таблица, как показано ниже:
Region Country Manufacturer Brand Period Spend
R1 C1 M1 B1 2016 5
R1 C1 M1 B1 2017 10
R1 C1 M1 B1 2017 20
R1 C1 M1 B2 2016 15
R1 C1 M1 B3 2017 20
R1 C2 M1 B1 2017 5
R1 C2 M2 B4 2017 25
R1 C2 M2 B5 2017 30
R2 C3 M1 B1 2017 35
R2 C3 M2 B4 2017 40
R2 C3 M2 B5 2017 45
...
Я написал запрос ниже, чтобы их объединить:
SELECT [Region]
,[Country]
,[Manufacturer]
,[Brand]
,Period
,SUM([Spend]) AS [Spend]
FROM myTable
GROUP BY [Region]
,[Country]
,[Manufacturer]
,[Brand]
,[Period]
ORDER BY 1,2,3,4
что дает что-то вроде ниже:
Region Country Manufacturer Brand Period Spend
R1 C1 M1 B1 2016 5
R1 C1 M1 B1 2017 30 -- this row is an aggregate from raw table above
R1 C1 M1 B2 2016 15
R1 C1 M1 B3 2017 20
R1 C2 M1 B1 2017 4 -- aggregated result
R1 C2 M2 B4 2017 25
R1 C2 M2 B5 2017 30
R2 C3 M2 B4 2017 40
R2 C3 M2 B5 2017 45
Я хотел бы добавить еще один столбец к приведенной выше таблице, который показывает DISTINCT COUNT из Brand, сгруппированных по Region, Country, Manufacturer и Period. Таким образом, финальный стол будет выглядеть следующим образом:
Region Country Manufacturer Brand Period Spend UniqBrandCount
R1 C1 M1 B1 2016 5 2 -- two brands by R1, C1, M1 in 2016
R1 C1 M1 B1 2017 30 1
R1 C1 M1 B2 2016 15 2 -- same as first row's result
R1 C1 M1 B3 2017 20 1
R1 C2 M1 B1 2017 4 1
R1 C2 M2 B4 2017 25 2
R1 C2 M2 B5 2017 30 2
R2 C3 M2 B4 2017 40 2
R2 C3 M2 B5 2017 45 2
Я знаю, как достичь конечного результата за три шага.
Выполните этот запрос (запрос №1):
ВЫБРАТЬ [Регион] ,[Страна] , [Производитель] ,[Период] , COUNT (DISTINCT [Brand]) AS [BrandCount] INTO Temp1 ИЗ myTable ГРУППА ПО [региону] ,[Страна] , [Производитель] ,[Период]
Запустите этот запрос (запрос №2)
ВЫБРАТЬ [Регион] ,[Страна] , [Производитель] , [Бренд] , ГОД ([Период]) КАК Период , SUM ([Spend]) AS [Spend] INTO Temp2 ИЗ myTable ГРУППА ПО [региону] ,[Страна] , [Производитель] , [Бренд] ,[Период]
Затем LEFT JOIN, Temp2 и Temp1, чтобы получить [BrandCount] из последнего, как показано ниже:
Выберите.* , Б. * ОТ Temp2 AS a LEFT JOIN Temp1 AS b ON a. [Region] = b. [Region] И a. [Страна] = b. [Страна] И а. [Рекламодатель] = б. [Рекламодатель] И a. [Период] = b. [Период]
Я почти уверен, что есть более эффективный способ сделать это, не так ли? Заранее благодарим вас за ваши предложения / ответы!


Тег к вашему вопросу;
window-functions
предполагает, что у вас есть неплохая идея.
Для РАЗЛИЧНОЕ КОЛИЧЕСТВО брендов, сгруппированных по региону, стране, производителю и периоду: вы можете написать:
Select Region
,Country
,Manufacturer
,Brand
,Period
,Spend
,DENSE_RANK() Over (Partition By Region, Country, Manufacturer, Period Order By Brand asc)
+ DENSE_RANK() Over (Partition By Region, Country, Manufacturer, Period Order By Brand desc)
-1 UniqBrandCount
From myTable T1
Order By 1,2,3,4
Сильно заимствовано из этого вопроса: https://dba.stackexchange.com/questions/89031/using-distinct-in-window-function-with-over
Счетчик Distinct не работает, поэтому требуется density_rank. Ранжирование брендов в прямом и обратном порядке с последующим вычитанием 1 дает различный счет.
Функцию суммы также можно переписать с использованием логики PARTITION BY. Таким образом, вы можете использовать разные уровни группировки для каждой агрегации:
SELECT
[Region]
,[Country]
,[Manufacturer]
,[Brand]
,[Period]
,dense_rank() OVER
(PARTITION BY
[Region]
,[Country]
,[Manufacturer]
,[Period] Order by Brand)
+ dense_rank() OVER
(PARTITION BY
[Region]
,[Country]
,[Manufacturer]
,[Period] Order by Brand Desc)
- 1
AS [BrandCount]
,SUM([Spend]) OVER
(PARTITION BY
[Region]
,[Country]
,[Manufacturer]
,[Brand]
,[Period]) as [Spend]
from
myTable
ORDER BY 1,2,3,4
Затем вам может потребоваться уменьшить количество строк в вашем выводе, поскольку этот синтаксис дает то же количество строк, что и myTable, но с итоговыми значениями агрегирования, отображаемыми в каждой строке, к которой они применяются:
R1 C1 M1 B1 2016 2 5
R1 C1 M1 B1 2017 2 30 --dup1
R1 C1 M1 B1 2017 2 30 --dup1
R1 C1 M1 B2 2016 2 15
R1 C1 M1 B3 2017 2 20
R1 C2 M1 B1 2017 1 5
R1 C2 M2 B4 2017 2 25
R1 C2 M2 B5 2017 2 30
R2 C3 M1 B1 2017 1 35
R2 C3 M2 B4 2017 2 40
R2 C3 M2 B5 2017 2 45
Выбор отдельных строк из этого вывода дает вам то, что вам нужно.
Рассмотрим эти данные:
Col1 Col2
B 1
B 1
B 3
B 5
B 7
B 9
density_rank () ранжирует данные по количеству отдельных элементов перед текущим плюс 1. Итак:
1->1, 3->2, 5->3, 7->4, 9->5.
В обратном порядке (с использованием desc) получается обратная картина:
1->5, 3->4, 5->3, 7->2, 9->1:
Сложение этих рангов дает одно и то же значение:
1+5 = 2+4 = 3+3 = 4+2 = 5+1 = 6
Формулировка здесь полезна,
(number of distinct items before + 1) + (number of distinct items after + 1)
= number of distinct OTHER items before AND after + 2
= Total number of distinct items + 1
Итак, чтобы получить общее количество различных элементов, сложите ascending и descending density_ranks вместе и вычтите 1.
Большое спасибо за очень четкое объяснение !! Теперь мне понятно, почему DENSE_RANK должен работать. Хотя приведенный ниже ответ Мартина также хорош (и не требует впоследствии дедупликации строк), я приму этот. Тем, кто ищет ответы, также можно прочитать ответ Мартина ниже. Большое спасибо, @mjsqu!
Нет проблем, я сначала этого не понял, поэтому мне пришлось создать рабочий пример, чтобы понять, почему!
Идея двойного dense_rank означает, что вам нужно две сортировки (при условии, что не существует индекса, обеспечивающего порядок сортировки). Предполагая, что нет брендов NULL (как эта идея), вы можете использовать один dense_rank и оконный MAX, как показано ниже (демонстрация)
WITH T1
AS (SELECT *,
DENSE_RANK() OVER (PARTITION BY [Region], [Country], [Manufacturer], [Period] ORDER BY Brand) AS [dr]
FROM myTable),
T2
AS (SELECT *,
MAX([dr]) OVER (PARTITION BY [Region], [Country], [Manufacturer], [Period]) AS UniqBrandCount
FROM T1)
SELECT [Region],
[Country],
[Manufacturer],
[Brand],
Period,
SUM([Spend]) AS [Spend],
MAX(UniqBrandCount) AS UniqBrandCount
FROM T2
GROUP BY [Region],
[Country],
[Manufacturer],
[Brand],
[Period]
ORDER BY [Region],
[Country],
[Manufacturer],
[Period],
Brand
Вышеупомянутое имеет некоторую неизбежную буферизацию (невозможно сделать это 100% потоковым способом), но только один вид.
Как ни странно, последнее предложение order by необходимо, чтобы количество сортировок было меньше одного (или нуля, если существует подходящий индекс).
Большое спасибо за то, что поделились альтернативным (и, предположительно, более эффективным) подходом! Основываясь на вашем объяснении, я понимаю, что использование DENSE_RANK влечет за собой одну сортировку, а MAX - нет (я имею в виду, я вижу это в выходных данных анализатора запросов, но просто интересно, как без сортировки были найдены значения MAX)? Я все еще новичок в изучении SQL, поэтому, возможно, однажды я смогу лучше понять внутреннюю работу. :) Еще один вопрос, спулинг из-за того, что часть MAX должна ждать завершения работы части DENSE_RANK? Большое спасибо!
@ user1330974 - справа налево строки считываются из таблицы и сортируются в порядке [Region], [Country], [Manufacturer], [Period], Brand. Следующие два оператора проекта сегмента и последовательности вычисляют плотный_ранк. Строки с этим рассчитанным значением density_rank считываются в катушку. Как только достигается новая группа [Region], [Country], [Manufacturer], [Period], для строк в катушке для предыдущей группы вычисляется MAX, а затем строки из катушки воспроизводятся с добавленным к ним значением MAX.
Подробнее о density_rank sqlblog.com/blogs/paul_white/archive/2010/07/28/… и оконных агрегатах sqlblog.com/blogs/paul_white/archive/2010/07/28/…
Большое спасибо за подробное объяснение того, как читать план запроса, а также за то, что вы поделились действительно хорошим ресурсом / блогом, чтобы узнать больше о функциях окна! Благодаря вашему объяснению план запроса теперь имеет для меня частичный смысл, и я могу подтвердить, что предложенное вами решение работает и занимает всего 6-8 секунд! Боковое примечание: я принял другое решение, указанное выше, потому что оно появилось немного раньше, чем ваше, и оно также сработало. Надеюсь, ты поймешь. :)
Спасибо за исчерпывающий ответ! Немного подумав, чтобы сразу понять, почему
DESNSE_RANKвперед и назад - 1 может оказаться тем же самым, что иDISTINCT COUNTBrand. Если есть какой-либо ресурс, объясняющий, почему это работает, не могли бы вы поделиться? Я проверю результат и выберу ваш ответ в качестве ответа (потому что он наиболее исчерпывающий). Я также попытаюсь написать здесь в качестве комментария то, что я узнал о том, почему трюк сDENSE_RANKработает после того, как подумал над этим сегодня вечером. Большое спасибо!