Utilisation de DISTINCT dans la fonction de fenêtre avec OVER

18

J'essaie de migrer une requête d'Oracle vers SQL Server 2014.

Voici ma requête qui fonctionne très bien dans Oracle:

select
count(distinct A) over (partition by B) / count(*) over() as A_B
from MyTable 

Voici l'erreur que j'ai reçue après avoir essayé d'exécuter cette requête dans SQL Server 2014.

Use of DISTINCT is not allowed with the OVER clause

Quelqu'un sait quel est le problème? Un tel type de requête est-il possible dans SQL Server? S'il vous plaît donnez votre avis.

Omri
la source
Avez-vous réellement besoin d'une ligne dans le résultat pour chaque ligne MyTable? Ou les lignes distinctes sont-elles suffisantes? Et vous n'avez pas besoin de considérer la division par zéro erreur s'il n'y a pas de lignes MyTable?
Erwin Brandstetter

Réponses:

12

Quelqu'un sait quel est le problème? Un tel type de requête est-il possible dans SQL Server?

Non, il n'est pas actuellement implémenté. Voir la demande d'élément de connexion suivante.

Demande d'amélioration de clause OVER - clause DISTINCT pour les fonctions d'agrégation

Une autre variante possible serait

SELECT M.A,
       M.B,
       T.A_B
FROM   MyTable M
       JOIN (SELECT CAST(COUNT(DISTINCT A) AS NUMERIC(18,8)) / SUM(COUNT(*)) OVER() AS A_B,
                    B
             FROM   MyTable
             GROUP  BY B) T
         ON EXISTS (SELECT M.B INTERSECT SELECT T.B) 

le casting vers NUMERICest là pour éviter la division entière. La raison de la clause join est expliquée ici .

Il peut être remplacé par ON M.B = T.B OR (M.B IS NULL AND T.B IS NULL)si préféré (ou simplement ON M.B = T.Bsi la Bcolonne n'est pas nullable).

Martin Smith
la source
14

Cela donne le nombre distinct (*) pour A partitionné par B:

dense_rank() over (partition by B order by A) 
+ dense_rank() over (partition by B order by A desc) 
- 1
Ben
la source
3
Solution intéressante. Je suppose qu'il devrait avoir une clause de non-responsabilité selon laquelle il fonctionne Auniquement lorsqu'il est non annulable (car je pense qu'il compte également les null).
ypercubeᵀᴹ
Ça devrait être abs(dense_rank - dense_rank) + 1je crois.
norcalli
7

Vous pouvez prendre la valeur maximale de dense_rank()pour obtenir le nombre distinct de A partitionné par B.

Pour prendre en charge le cas où A peut avoir des valeurs nulles, vous pouvez utiliser first_valuepour déterminer si un null est présent dans la partition ou non, puis soustraire 1 s'il est tel que suggéré par Martin Smith dans le commentaire.

select (max(T.DenseRankA) over(partition by T.B) - 
          cast(iif(T.FirstA is null, 1, 0) as numeric(18, 8))) / T.TotalCount as A_B
from (
     select dense_rank() over(partition by T.B order by T.A) DenseRankA,
            first_value(T.A) over(partition by T.B order by T.A) as FirstA,
            count(*) over() as TotalCount,
            T.A,
            T.B
     from MyTable as T
     ) as T
Mikael Eriksson
la source
5

Essayez de faire une sous-requête, en regroupant par A, B et en incluant le nombre. Ensuite, dans votre requête externe, votre nombre (distinct) devient un nombre normal et votre nombre (*) devient une somme (cnt).

select
count(A) over (partition by B) * 1.0 / 
    sum(cnt) over() as A_B
from
(select A, B, count(*) as cnt
 from MyTable
 group by A, B) as partial;
Rob Farley
la source