Я пытаюсь написать следующее, чтобы получить общее количество различных NumUsers, например:
NumUsers = COUNT(DISTINCT [UserAccountKey]) OVER (PARTITION BY [Mth])
Студия менеджмента, похоже, не слишком довольна этим. Ошибка исчезает, когда я удаляю DISTINCT
ключевое слово, но тогда это не будет отдельного подсчета.
DISTINCT
не представляется возможным в рамках функций секционирования. Как мне найти точное количество? Могу ли я использовать более традиционный метод, такой как коррелированный подзапрос?
Если посмотреть на это немного подробнее, возможно, эти OVER
функции работают иначе, чем Oracle, в том смысле, что их нельзя использовать SQL-Server
для расчета промежуточных итогов.
Я добавил живой пример здесь, в SQLfiddle, где я пытаюсь использовать функцию секционирования для вычисления промежуточной суммы.
COUNT
withORDER BY
вместоPARTITION BY
is нечетко определено в 2008 году. Я удивлен, что он вообще позволяет вам его иметь. Согласно документации , вам не разрешеноORDER BY
использовать агрегатную функцию.Ответы:
Есть очень простое решение, использующее
dense_rank()
Это даст вам именно то, о чем вы просили: количество различных UserAccountKeys в течение каждого месяца.
источник
dense_rank()
, что он будет считать NULL, тогда какCOUNT(field) OVER
нет. Из-за этого я не могу использовать его в своем решении, но все же думаю, что это довольно умно.NULL
значение вUserAccountKey
, то вам нужно добавить этот термин:-MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)
. Идея взята из ответа Ларса Рённбека ниже. По сути, еслиUserAccountKey
естьNULL
значения, вам нужно вычесть лишнее1
из результата, потому чтоDENSE_RANK
учитываются NULL.dense_rank
решения, когда оконная функция имеет фрейм. SQL Server не позволяетdense_rank
использовать с оконным фреймом: stackoverflow.com/questions/63527035/…Некромантинг:
Относительно просто эмулировать COUNT DISTINCT над PARTITION BY с MAX через DENSE_RANK:
;WITH baseTable AS ( SELECT 'RM1' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM1' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR3' AS ADR UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR UNION ALL SELECT 'RM3' AS RM, 'ADR2' AS ADR ) ,CTE AS ( SELECT RM, ADR, DENSE_RANK() OVER(PARTITION BY RM ORDER BY ADR) AS dr FROM baseTable ) SELECT RM ,ADR ,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY ADR) AS cnt1 ,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM) AS cnt2 -- Not supported --,COUNT(DISTINCT CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY CTE.ADR) AS cntDist ,MAX(CTE.dr) OVER (PARTITION BY CTE.RM ORDER BY CTE.RM) AS cntDistEmu FROM CTE
Примечание.
Предполагается, что рассматриваемые поля являются полями, которые НЕ допускают значения NULL.
Если в полях есть одна или несколько NULL-записей, нужно вычесть 1.
источник
Я использую решение, аналогичное тому, что было у Дэвида выше, но с дополнительным поворотом, если некоторые строки следует исключить из подсчета. Это предполагает, что [UserAccountKey] никогда не имеет значения NULL.
-- subtract an extra 1 if null was ranked within the partition, -- which only happens if there were rows where [Include] <> 'Y' dense_rank() over ( partition by [Mth] order by case when [Include] = 'Y' then [UserAccountKey] else null end asc ) + dense_rank() over ( partition by [Mth] order by case when [Include] = 'Y' then [UserAccountKey] else null end desc ) - max(case when [Include] = 'Y' then 0 else 1 end) over (partition by [Mth]) - 1
SQL Fiddle с расширенным примером можно найти здесь.
источник
[Include]
которых вы говорите в своем ответе) сdense_rank()
работой, когдаUserAccountKey
это возможноNULL
. Добавьте этот термин к формуле:-MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)
.Я думаю, что единственный способ сделать это в SQL-Server 2008R2 - использовать коррелированный подзапрос или внешнее применение:
SELECT datekey, COALESCE(RunningTotal, 0) AS RunningTotal, COALESCE(RunningCount, 0) AS RunningCount, COALESCE(RunningDistinctCount, 0) AS RunningDistinctCount FROM document OUTER APPLY ( SELECT SUM(Amount) AS RunningTotal, COUNT(1) AS RunningCount, COUNT(DISTINCT d2.dateKey) AS RunningDistinctCount FROM Document d2 WHERE d2.DateKey <= document.DateKey ) rt;
Это можно сделать в SQL-Server 2012, используя предложенный вами синтаксис:
SELECT datekey, SUM(Amount) OVER(ORDER BY DateKey) AS RunningTotal FROM document
Однако использование по-
DISTINCT
прежнему запрещено, поэтому, если требуется DISTINCT и / или если обновление не является вариантом, я думаю, чтоOUTER APPLY
это ваш лучший вариантисточник