2012-06-26 2 views
42

Я пытаюсь написать следующее, чтобы получить текущую сумму различных NumUsers, например, так:Partition Функция COUNT() OVER можно с помощью DISTINCT

NumUsers = COUNT(DISTINCT [UserAccountKey]) OVER (PARTITION BY [Mth]) 

студии управления, кажется, не слишком доволен это. Ошибка исчезает, когда я удаляю ключевое слово DISTINCT, но тогда это не будет отчетливым.

DISTINCT не представляется возможным в рамках функций разбиения. Как мне найти отчетный счет? Использую ли я более метод, такой как коррелированный подзапрос?

Заглянув в это немного дальше, возможно, эти функции OVER работают по-разному с Oracle, так как они не могут использоваться в SQL-Server для расчета текущих итогов.

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

+2

'' COUNT' с ORDER BY' вместо «PARTITION BY» в 2008 году не определен. Я удивлен, что это позволяет вам иметь его вообще. В соответствии с [документацией] (http://msdn.microsoft.com/en-us/library/ms189461 (v = sql.105) .aspx) вам не разрешено «ЗАКАЗАТЬ» для агрегатной функции. –

+0

yep - думаю, что меня путают с некоторыми функциями оракула; эти текущие итоги и подсчеты будут немного больше задействованы – whytheq

+0

Проголосуйте за это -> https://connect.microsoft.com/SQLServer/feedback/details/254393/over-clause-enhancement-request-distinct-clause-for- совокупные функции Itzik Ben-Gan подняли этот путь еще в 2007 году. Еще не произошло – Davos

ответ

90

Существует очень простое решение с использованием dense_rank()

dense_rank() over (partition by [Mth] order by [UserAccountKey]) 
+ dense_rank() over (partition by [Mth] order by [UserAccountKey] desc) 
- 1 

Это даст вам именно то, что вы просили: число различных UserAccountKeys в течение каждого месяца.

+15

Осторожно, что с 'dense_rank()' состоит в том, что он будет считать NULL, тогда как 'COUNT (поле) OVER' не делает. Из-за этого я не могу использовать его в своем решении, но я все еще думаю, что это довольно умно. – bf2020

+0

Но я ищу текущее общее количество отдельных учетных записей в месяцах каждого года: не знаете, как это на это отвечает? – whytheq

+0

Невероятно! Очень элегантно! –

5

Я думаю, что единственный способ сделать это в SQL-сервер 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 является лучшим вариантом

+0

круто спасибо. Я нашел это [SO ответ] (http: // stackoverflow.com/questions/860966/calculate-a-running-total-in-sqlserver), в котором есть опция OUTER APPLY, которую я попытаюсь выполнить. Вы видели в этом ответе подход UPDATE, связанный с циклом ... это довольно далеко и, по-видимому, быстро. В 2012 году жизнь станет проще - это прямая копия Oracle? – whytheq

2

Я использую решение, аналогичное решению David выше, но с дополнительным завихрением, если некоторые строки должны быть исключены из счета. Это предполагает, что [UserAccountKey] никогда не является нулевым.

-- 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 

An SQL Fiddle with an extended example can be found here.

+0

Ваша идея может быть использована для создания исходной формулы (без сложностей '[Include]', о которой вы говорите в своем ответе) с 'dense_rank()' работают, когда 'UserAccountKey' может быть' NULL'. Добавьте этот термин в формулу: '-MAX (CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)'. –

1

Necromancing:

Это relativiely просто эмулировать 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 
Смежные вопросы