J'ai une table avec deux colonnes, je veux compter les valeurs distinctes sur Col_B sur (conditionné par) Col_A.
MaTable
Col_A | Col_B
A | 1
A | 1
A | 2
A | 2
A | 2
A | 3
b | 4
b | 4
b | 5
Résultat attend
Col_A | Col_B | Result
A | 1 | 3
A | 1 | 3
A | 2 | 3
A | 2 | 3
A | 2 | 3
A | 3 | 3
b | 4 | 2
b | 4 | 2
b | 5 | 2
J'ai essayé le code suivant
select *,
count (distinct col_B) over (partition by col_A) as 'Result'
from MyTable
count (distinct col_B) ne fonctionne pas. Comment puis-je réécrire la fonction de comptage pour compter des valeurs distinctes?
Voici comment je le ferais:
SELECT *
FROM #MyTable AS mt
CROSS APPLY ( SELECT COUNT(DISTINCT mt2.Col_B) AS dc
FROM #MyTable AS mt2
WHERE mt2.Col_A = mt.Col_A
-- GROUP BY mt2.Col_A
) AS ca;
Le GROUP BY
la clause est redondante compte tenu des données fournies dans la question, mais peut vous donner un meilleur plan d'exécution. Voir le Q & A de suivi CROSS APPLY produit une jointure externe .
Envisagez de voter pour demande d'amélioration de la clause OVER - clause DISTINCT pour les fonctions d'agrégation sur le site de commentaires si vous souhaitez que cette fonctionnalité soit ajoutée à SQL Server.
Vous pouvez l'émuler en utilisant dense_rank
, Puis choisissez le rang maximum pour chaque partition:
select col_a, col_b, max(rnk) over (partition by col_a)
from (
select col_a, col_b
, dense_rank() over (partition by col_A order by col_b) as rnk
from #mytable
) as t
Vous devez exclure les valeurs nulles de col_b
Pour obtenir les mêmes résultats que COUNT(DISTINCT)
.
C'est, en quelque sorte, une extension de la solution de Lennart , mais c'est si moche que je n'ose pas le suggérer comme une modification. Le but ici est d'obtenir les résultats sans tableau dérivé. Il n'y aura peut-être jamais besoin de cela, et combiné avec la laideur de la requête, l'effort dans son ensemble peut sembler un effort inutile. Je voulais quand même le faire comme un exercice et je voudrais maintenant partager mon résultat:
SELECT
Col_A,
Col_B,
DistinctCount = DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B ASC )
+ DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B DESC)
- 1
- CASE COUNT(Col_B) OVER (PARTITION BY Col_A)
WHEN COUNT( * ) OVER (PARTITION BY Col_A)
THEN 0
ELSE 1
END
FROM
dbo.MyTable
;
La partie centrale du calcul est la suivante (et je voudrais tout d'abord noter que l'idée n'est pas la mienne, j'ai appris cette astuce ailleurs):
DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B ASC )
+ DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B DESC)
- 1
Cette expression peut être utilisée sans aucune modification si les valeurs dans Col_B
Sont garanties de ne jamais avoir de valeurs nulles. Si la colonne peut avoir des valeurs nulles, cependant, vous devez en tenir compte, et c'est exactement à cela que sert l'expression CASE
. Il compare le nombre de lignes par partition avec le nombre de valeurs Col_B
par partition. Si les nombres diffèrent, cela signifie que certaines lignes ont une valeur nulle dans Col_B
Et, par conséquent, le calcul initial (DENSE_RANK() ... + DENSE_RANK() - 1
) doit être réduit de 1.
Notez que parce que le - 1
Fait partie de la formule principale, j'ai choisi de le laisser comme ça. Cependant, il peut en fait être incorporé dans l'expression CASE
, dans la tentative futile de rendre la solution entière moins laide:
SELECT
Col_A,
Col_B,
DistinctCount = DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B ASC )
+ DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B DESC)
- CASE COUNT(Col_B) OVER (PARTITION BY Col_A)
WHEN COUNT( * ) OVER (PARTITION BY Col_A)
THEN 1
ELSE 2
END
FROM
dbo.MyTable
;
Cette démo en direct à db <> fiddle.uk peut être utilisé pour tester les deux variantes de la solution.
create table #MyTable (
Col_A varchar(5),
Col_B int
)
insert into #MyTable values ('A',1)
insert into #MyTable values ('A',1)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',3)
insert into #MyTable values ('B',4)
insert into #MyTable values ('B',4)
insert into #MyTable values ('B',5)
;with t1 as (
select t.Col_A,
count(*) cnt
from (
select Col_A,
Col_B,
count(*) as ct
from #MyTable
group by Col_A,
Col_B
) t
group by t.Col_A
)
select a.*,
t1.cnt
from #myTable a
join t1
on a.Col_A = t1.Col_a
Alternative si vous êtes légèrement allergique aux sous-requêtes corrélées (réponse d'Erik Darling) et aux CTE (réponse de kevinnwhat) comme moi.
Sachez que lorsque des valeurs nulles sont ajoutées au mixage, aucune de celles-ci ne peut fonctionner comme vous le souhaitez. (mais il est assez simple de les modifier à votre goût)
Cas simple:
--ignore the existence of nulls
SELECT [mt].*, [Distinct_B].[Distinct_B]
FROM #MyTable AS [mt]
INNER JOIN(
SELECT [Col_A], COUNT(DISTINCT [Col_B]) AS [Distinct_B]
FROM #MyTable
GROUP BY [Col_A]
) AS [Distinct_B] ON
[mt].[Col_A] = [Distinct_B].[Col_A]
;
Comme ci-dessus, mais avec des commentaires sur ce qu'il faut changer pour une gestion nulle:
--customizable null handling
SELECT [mt].*, [Distinct_B].[Distinct_B]
FROM #MyTable AS [mt]
INNER JOIN(
SELECT
[Col_A],
(
COUNT(DISTINCT [Col_B])
/*
--uncomment if you also want to count Col_B NULL
--as a distinct value
+
MAX(
CASE
WHEN [Col_B] IS NULL
THEN 1
ELSE 0
END
)
*/
)
AS [Distinct_B]
FROM #MyTable
GROUP BY [Col_A]
) AS [Distinct_B] ON
[mt].[Col_A] = [Distinct_B].[Col_A]
/*
--uncomment if you also want to include Col_A when it's NULL
OR
([mt].[Col_A] IS NULL AND [Distinct_B].[Col_A] IS NULL)
*/