2012-06-26 9 views
42

Ich versuche, den folgend zu schreiben, um eine laufende Summe verschiedenen NumUsers zu bekommen, etwa so:Partition Funktion COUNT() OVER möglich mit DISTINCT

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

Management Studio scheint nicht allzu glücklich über Dies. Der Fehler verschwindet, wenn ich das Schlüsselwort DISTINCT entferne, aber dann wird es keine eindeutige Anzahl sein.

DISTINCT scheint nicht innerhalb der Partition Funktionen möglich sein. Wie gehe ich vor, um die eindeutige Anzahl zu finden? Verwenden Sie eine mehr traditionelle Methode wie eine korrelierte Unterabfrage?

Blick in diese etwas weiter, vielleicht diese OVER Funktionen arbeiten anders als Oracle in der Art, dass sie nicht in SQL-Server verwendet werden können, um laufende Summen zu berechnen.

Ich habe ein Live-Beispiel hier auf SQLfiddle hinzugefügt, wo ich versuche, eine Partitionsfunktion zu verwenden, um eine laufende Summe zu berechnen.

+2

' COUNT' BY' statt 'PARTITION BY' ist 2008 nicht definiert. Ich bin überrascht, dass Sie es überhaupt haben. Nach der [Dokumentation] (http://msdn.microsoft.com/en-us/library/ms189461 (v = sql.105) .aspx) ist es Ihnen nicht erlaubt, eine ORDER BY für eine Aggregatfunktion. –

+0

yep - denke ich bin verwirrt mit etwas Orakel Funktionalität; Diese laufenden Summen und laufenden Zahlen werden ein wenig mehr beteiligt – whytheq

+0

Vote für diese -> https://connect.microsoft.com/SQLServer/feedback/details/254393/over-clause-enhancement-request-distinct-klause-for- Aggregat-Funktionen Itzik Ben-Gan hob diesen Weg zurück im Jahr 2007. Noch ist nicht passiert – Davos

Antwort

90

Es ist eine sehr einfache Lösung dense_rank()

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

Dies wird Ihnen genau das, was Sie für die Verwendung fragten: in jedem Monat, um die Anzahl der verschiedenen UserAccountKeys.

+15

Eine Sache, die mit 'demse_rank()' vorsichtig ist, ist, dass es NULL zählt, während "COUNT (Feld) OVER" nicht zählt. Ich kann es deswegen nicht in meiner Lösung verwenden, aber ich denke immer noch, dass es ziemlich clever ist. – bf2020

+0

Aber ich bin auf der Suche nach einer laufenden Summe von verschiedenen Useraccountkeys über die Monate eines jeden Jahres: nicht sicher, wie das beantwortet das? – whytheq

+0

Unglaublich! Sehr elegant! –

5

Ich denke, der einzige Weg, dies 2008R2 in SQL-Server zu tun, eine korrelierte Unterabfrage zu verwenden ist, oder eine äußere Anwendung:

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 Syntax Sie vorgeschlagen haben getan werden:

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

Die Verwendung von DISTINCT ist nach wie vor nicht erlaubt, so dass, wenn DISTINCT erforderlich ist und/oder wenn Upgrade nicht möglich ist, dann denke ich OUTER APPLY ist die beste Wahl

+0

cool danke. Ich fand das [SO Antwort] (http: // stackoverflow.com/questions/860966/calculate-a-running-total-in-sqlserver) mit der Option OUTER APPLY, die ich versuchen werde. Hast du den Looping-UPDATE-Ansatz in dieser Antwort gesehen? Er ist ziemlich weit und scheinbar schnell. Das Leben wird 2012 einfacher - ist das eine direkte Oracle-Kopie? – whytheq

2

Ich benutze eine Lösung, die der obigen David ähnlich ist, aber mit einer zusätzlichen Wendung, wenn einige Zeilen von der Zählung ausgeschlossen werden sollten. 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 

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

+0

Ihre Idee kann verwendet werden, um die ursprüngliche Formel (ohne Komplexitäten von '[Include]' ', über die Sie in Ihrer Antwort sprechen) mit 'dose_rank()' zu machen, wenn 'UserAccountKey'' NULL' sein kann. Fügen Sie diesen Begriff zu der Formel hinzu: '-MAX (FALL WENN UserAccountKey NULL DANN 1 ELSE 0 ENDE) ÜBER (PARTITION BY Mth) '. –

1

Necromancing:

Es ist relativiely einfach eine COUNT DISTINCT über PARTITION BY mit MAX über DENSE_RANK zu emulieren:

mit `ORDER
;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