2012-06-26 2 views
42

Je suis en train d'écrire ce qui suit afin d'obtenir un total de fonctionnement de numUsers distincts, comme suit:Partition Fonction COUNT() OVER possible en utilisant DISTINCT

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

studio de gestion ne semble pas trop heureux ce. L'erreur disparaît lorsque je supprime le mot clé DISTINCT, mais ce n'est pas un nombre distinct.

DISTINCT ne semble pas être possible dans les fonctions de partition. Comment puis-je trouver le nombre distinct? Dois-je utiliser une méthode plus traditionnelle comme une sous-requête corrélée? En regardant un peu plus loin, peut-être que ces fonctions OVER fonctionnent différemment d'Oracle de telle sorte qu'elles ne peuvent pas être utilisées dans SQL-Server pour calculer les totaux cumulés.

J'ai ajouté un exemple en direct ici sur SQLfiddle où j'essaie d'utiliser une fonction de partition pour calculer un total cumulé.

+2

'' COUNT' avec ORDER BY' au lieu de 'PARTITION BY' est mal défini en 2008. Je suis surpris que cela vous laisse avoir du tout. Selon la [documentation] (http://msdn.microsoft.com/en-us/library/ms189461 (v = sql.105) .aspx), vous n'êtes pas autorisé un 'ORDER BY' pour une fonction d'agrégation. –

+0

yep - pense que je suis confus avec certaines fonctionnalités d'Oracle; ces totaux cumulés et les décomptes en cours seront un peu plus impliqués – whytheq

+0

Voter pour ceci -> https://connect.microsoft.com/SQLServer/feedback/details/254393/over-clause-enhancement-request-distinct-clause-for- fonctions agrégées Itzik Ben-Gan a soulevé ce chemin en 2007. Toujours pas arrivé – Davos

Répondre

90

Il y a une solution très simple à l'aide dense_rank()

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

Cela vous donnera exactement ce que vous demandez: Le nombre de UserAccountKeys distincts dans chaque mois.

+15

Une chose dont il faut faire attention avec 'dense_rank()' est qu'il comptera NULL alors que COUNT (field) OVER ne le sera pas. Je ne peux pas l'employer dans ma solution à cause de cela mais je pense toujours que c'est assez intelligent. – bf2020

+0

Mais je cherche un total cumulé de useraccountkeys distincts au cours des mois de chaque année: ne sais pas comment cela répond à cette question? – whytheq

+0

Incroyable! Très élégant! –

5

Je pense que la seule façon de le faire dans SQL-server 2008R2 est d'utiliser une sous-requête corrélée, ou appliquer une couche extérieure:

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; 

Cela peut être fait en SQL-Server 2012 en utilisant la syntaxe que vous avez suggéré:

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

Cependant, l'utilisation de DISTINCT est toujours pas permis, donc si DISTINCT est nécessaire et/ou si la mise à niveau ne sont pas une option, puis je pense que OUTER APPLY est votre meilleure option

+0

cool merci. J'ai trouvé ceci [réponse SO] (http: // stackoverflow.com/questions/860966/calculer-un-exécution-total-en-sqlserver) qui comporte l'option OUTER APPLY que je vais essayer. Avez-vous vu l'approche UPDATE en boucle dans cette réponse ... c'est assez loin et apparemment rapide. La vie sera plus facile en 2012 - est-ce une copie droite d'Oracle? – whytheq

2

J'utilise une solution similaire à celle de David ci-dessus, mais avec une torsion supplémentaire si certaines lignes doivent être exclues du comptage. Cela suppose que [UserAccountKey] n'est jamais 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 

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

+0

Votre idée peut être utilisée pour faire la formule originale (sans les complexités de '[Include]' dont vous parlez dans votre réponse) avec le travail 'dense_rank()' quand 'UserAccountKey' peut être' NULL'. Ajoutez ce terme à la formule: '-MAX (CASE WHEN UserAccountKey EST NULL ALORS 1 FIN 0 FIN) OVER (PARTITION PAR MTH)'. –

1

Necromancing:

Il est relativiely simple à imiter un COUNT DISTINCT sur PARTITION BY avec MAX via 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 
Questions connexes