Функція розділу COUNT () НАД можлива за допомогою DISTINCT


88

Я намагаюся написати наступне, щоб отримати загальну кількість різних NumUsers, наприклад так:

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

Студія управління здається не надто задоволеною цим. Помилка зникає, коли я вилучаю DISTINCTключове слово, але тоді це не буде вираженим підрахунком.

DISTINCTздається неможливим у межах функцій розділу. Як мені знайти пошук чіткого рахунку? Чи використовую я більш традиційний метод, такий як корельований підзапит?

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

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


2
COUNTз, ORDER BYа не PARTITION BYє погано визначеним у 2008 році. Я здивований, що це взагалі дозволяє вам це мати. Згідно з документацією , вам не дозволено ORDER BYвикористовувати сукупну функцію.
Damien_The_Unbeliever

так - думаю, я плутаюся з якоюсь функціональністю оракула; ці загальні підсумки та підрахунки будуть трохи більше задіяні
чомуtheq

Відповіді:


177

Існує дуже просте рішення dense_rank()

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

Це дасть вам саме те, про що ви просили: кількість різних UserAccountKeys протягом кожного місяця.


23
Одне, з чим слід бути обережним, dense_rank()- це те, що він буде рахувати NULL, тоді як COUNT(field) OVERні. Я не можу застосувати це у своєму рішенні через це, але я все ще думаю, що це досить розумно.
bf2020

1
Але я шукаю загальну кількість різних облікових записів користувачів протягом місяців кожного року: не впевнений, як це відповідає на це?
whytheq

4
@ Bf2020, якщо може бути NULLзначення в UserAccountKey, то вам потрібно додати цей термін: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth). Ідея взята з відповіді Ларса Роннбека нижче. По суті, якщо UserAccountKeyмає NULLзначення, вам потрібно відняти зайве 1з результату, оскільки DENSE_RANKпідраховує NULL.
Володимир Баранов

1
@ahsteele спасибі, чоловіче, ти здув мене з голови і вирішив мою проблему
Енріке Донаті

Тут обговорення використання цього dense_rankрішення, коли функція вікна має фрейм. SQL Server не дозволяє dense_rankвикористовувати з
віконною

6

Некромансинг:

Емуляція COUNT DISTINCT over 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-записів, потрібно відняти 1.


5

Я використовую рішення, подібне до рішення Давида вище, але з додатковим поворотом, якщо деякі рядки слід виключити з підрахунку. Це передбачає, що [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 із розширеним прикладом.


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

5

Я думаю, що єдиним способом зробити це в 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це ваш найкращий варіант


круто дякую Я знайшов цю ТАКУ відповідь, яка містить опцію ВНЕШНЕ ЗАСТОСУВАННЯ, яку я спробую. Ви бачили циклічний підхід UPDATE у цій відповіді ... це досить далеко і очевидно швидко. Життя стане легшим у 2012 році - чи це пряма копія Oracle?
whytheq
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.