Partitionsfunktion COUNT () OVER mit DISTINCT möglich

85

Ich versuche Folgendes zu schreiben, um eine laufende Summe unterschiedlicher NumUsers zu erhalten:

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

Das Management Studio scheint darüber nicht allzu glücklich zu sein. Der Fehler verschwindet, wenn ich das DISTINCTSchlüsselwort entferne , aber dann wird es nicht eindeutig gezählt.

DISTINCTscheint innerhalb der Partitionsfunktionen nicht möglich zu sein. Wie finde ich die eindeutige Anzahl? Benutze ich eine traditionellere Methode wie eine korrelierte Unterabfrage?

Wenn Sie dies etwas OVERgenauer untersuchen, funktionieren diese Funktionen möglicherweise anders als Oracle, da sie nicht SQL-Serverzur Berechnung der laufenden Summen verwendet werden können.

Ich habe hier auf SQLfiddle ein Live-Beispiel hinzugefügt, in dem ich versuche, mithilfe einer Partitionsfunktion eine laufende Summe zu berechnen.

warum theq
quelle
2
COUNTmit ORDER BYstatt PARTITION BYschlecht definiert ist , im Jahr 2008. Ich bin überrascht , es ist so dass Sie es überhaupt haben. Gemäß der Dokumentation ist es Ihnen nicht gestattet, ORDER BYeine Aggregatfunktion zu verwenden.
Damien_The_Unbeliever
yep - denke ich werde mit einigen Orakelfunktionen verwirrt; Diese laufenden Summen und laufenden Zählungen werden etwas
komplizierter sein

Antworten:

171

Es gibt eine sehr einfache Lösung mit dense_rank()

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

Auf diese Weise erhalten Sie genau das, wonach Sie gefragt haben: Die Anzahl der unterschiedlichen UserAccountKeys pro Monat.

David
quelle
22
Eine Sache, bei der Sie vorsichtig sein sollten, dense_rank()ist, dass NULL-Werte gezählt werden, während COUNT(field) OVERdies nicht der Fall ist. Ich kann es aus diesem Grund nicht in meiner Lösung verwenden, aber ich denke immer noch, dass es ziemlich klug ist.
bf2020
1
Aber ich suche nach einer laufenden Anzahl unterschiedlicher Benutzerkontenschlüssel über die Monate eines jeden Jahres: Sie sind sich nicht sicher, wie dies darauf reagiert?
Whytheq
4
@ bf2020, wenn es NULLWerte in der geben UserAccountKeykann, müssen Sie diesen Begriff hinzufügen : -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth). Die Idee stammt aus der Antwort von LarsRönnbäck unten. Im Wesentlichen , wenn UserAccountKeyhat NULLWerte, müssen Sie zusätzlich subtrahieren 1aus dem Ergebnis, da DENSE_RANKzählt NULLs.
Vladimir Baranov
1
@ahsteele danke Mann, Sie haben mich umgehauen und mein Problem gelöst
Henrique Donati
Hier eine Diskussion über die Verwendung dieser dense_rankLösung, wenn die Fensterfunktion einen Rahmen hat. SQL Server erlaubt die dense_rankVerwendung mit einem Fensterrahmen nicht: stackoverflow.com/questions/63527035/…
K4M
6

Nekromantie:

Es ist relativ einfach, einen COUNT DISTINCT über PARTITION BY mit MAX über DENSE_RANK zu emulieren:

;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

Hinweis:
Dies setzt voraus, dass die fraglichen Felder NICHT nullfähige Felder sind.
Wenn die Felder einen oder mehrere NULL-Einträge enthalten, müssen Sie 1 subtrahieren.

Stefan Steiger
quelle
5

Ich denke, die einzige Möglichkeit, dies in SQL-Server 2008R2 zu tun, besteht darin, eine korrelierte Unterabfrage oder eine äußere Anwendung zu verwenden:

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;

Dies kann in SQL-Server 2012 mit der von Ihnen vorgeschlagenen Syntax erfolgen:

SELECT  datekey,
        SUM(Amount) OVER(ORDER BY DateKey) AS RunningTotal
FROM    document

Die Verwendung von DISTINCTist jedoch immer noch nicht zulässig. Wenn also DISTINCT erforderlich ist und / oder wenn ein Upgrade nicht möglich ist, OUTER APPLYist dies meiner Meinung nach die beste Option

GarethD
quelle
cool Danke. Ich habe diese SO-Antwort gefunden, die die Option OUTER APPLY enthält, die ich versuchen werde. Haben Sie den UPDATE-Ansatz in dieser Antwort gesehen? Es ist ziemlich weit weg und anscheinend schnell. 2012 wird das Leben einfacher - ist das eine reine Oracle-Kopie?
Whytheq
4

Ich verwende eine Lösung, die der von David oben ähnlich ist , aber mit einer zusätzlichen Wendung, wenn einige Zeilen von der Zählung ausgeschlossen werden sollen. Dies setzt voraus, dass [UserAccountKey] niemals null ist.

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

Eine SQL-Geige mit einem erweiterten Beispiel finden Sie hier.

Lars Rönnbäck
quelle
1
Ihre Idee kann verwendet werden, um die ursprüngliche Formel (ohne die Komplexität, über [Include]die Sie in Ihrer Antwort sprechen) mit dense_rank()Arbeit zu UserAccountKeyerstellen, wenn dies möglich ist NULL. Fügen Sie diesen Begriff der Formel hinzu : -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth).
Vladimir Baranov