Fonction de partition COUNT () OVER possible avec DISTINCT

88

J'essaye d'écrire ce qui suit afin d'obtenir un total courant de NumUsers distincts, comme ceci:

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

Le studio de gestion ne semble pas trop heureux à ce sujet. L'erreur disparaît lorsque je supprime le DISTINCTmot - clé, mais ce ne sera pas un décompte distinct.

DISTINCTne semble pas possible dans les fonctions de partition. Comment puis-je trouver le décompte distinct? Dois-je utiliser une méthode plus traditionnelle telle qu'une sous-requête corrélée?

En examinant cela un peu plus loin, peut-être que ces OVERfonctions fonctionnent différemment d'Oracle dans la mesure où elles ne peuvent pas être utilisées SQL-Serverpour calculer les totaux cumulés.

J'ai ajouté un exemple en direct ici sur SQLfiddlej'essaie d'utiliser une fonction de partition pour calculer un total en cours.

pourquoi leq
la source
2
COUNTavec ORDER BYau lieu de PARTITION BYest mal défini en 2008. Je suis surpris que cela vous laisse du tout. Selon la documentation , vous n'êtes pas autorisé à utiliser une ORDER BYfonction d'agrégation.
Damien_The_Unbeliever
oui - je pense que je me trompe avec certaines fonctionnalités d'Oracle; ces totaux et décomptes en cours seront un peu plus impliqués
pourquoi leq

Réponses:

177

Il existe une solution très simple utilisant 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 demandiez: le nombre de UserAccountKeys distinctes dans chaque mois.

David
la source
23
Une chose à laquelle il faut faire attention dense_rank()est qu'il comptera les NULL alors que ce COUNT(field) OVERn'est pas le cas. Je ne peux pas l'utiliser dans ma solution à cause de cela, mais je pense toujours que c'est assez intelligent.
bf2020
1
Mais je recherche un total cumulé de clés de compte utilisateur distinctes au cours des mois de chaque année: vous ne savez pas comment cela répond à cela?
whytheq
4
@ bf2020, s'il peut y avoir des NULLvaleurs dans le UserAccountKey, alors vous devez ajouter ce terme: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth). L'idée est tirée de la réponse de LarsRönnbäck ci-dessous. Essentiellement, si UserAccountKeya des NULLvaleurs, vous devez soustraire un supplément 1du résultat, car DENSE_RANKcompte NULL.
Vladimir Baranov
1
@ahsteele merci mec, vous m'avez époustouflé et avez résolu mon problème
Henrique Donati
Voici une discussion sur l'utilisation de cette dense_ranksolution lorsque la fonction de fenêtre a un cadre. SQL Server n'autorise pas l' dense_rankutilisation avec un cadre de fenêtre: stackoverflow.com/questions/63527035/…
K4M
6

Nécromancie:

Il est relativement simple d'émuler 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

Remarque:
cela suppose que les champs en question sont des champs NON Nullables.
S'il y a une ou plusieurs entrées NULL dans les champs, vous devez soustraire 1.

Stefan Steiger
la source
5

J'utilise une solution similaire à celle de David ci-dessus, mais avec une torsion supplémentaire si certaines lignes doivent être exclues du décompte. Cela suppose que [UserAccountKey] n'est jamais nul.

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

Un SQL Fiddle avec un exemple étendu peut être trouvé ici.

Lars Rönnbäck
la source
1
Votre idée peut être utilisée pour faire la formule originale (sans complexités de [Include]ce dont vous parlez dans votre réponse) avec le dense_rank()travail quand UserAccountKeypeut être NULL. Ajouter ce terme à la formule: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth).
Vladimir Baranov
5

Je pense que la seule façon de faire cela dans SQL-Server 2008R2 est d'utiliser une sous-requête corrélée, ou une application externe:

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 dans SQL-Server 2012 en utilisant la syntaxe que vous avez suggérée:

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

Cependant, l'utilisation de DISTINCTn'est toujours pas autorisée, donc si DISTINCT est requis et / ou si la mise à niveau n'est pas une option, je pense que OUTER APPLYc'est votre meilleure option

GarethD
la source
cool merci. J'ai trouvé cette réponse SO qui comporte l'option APPLIQUER EXTERNE 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 simple copie d'Oracle?
whytheq