web-dev-qa-db-fra.com

Compter DISTINCT sur plusieurs colonnes

Existe-t-il une meilleure façon de faire une requête comme celle-ci:

SELECT COUNT(*) 
FROM (SELECT DISTINCT DocumentId, DocumentSessionId
      FROM DocumentOutputItems) AS internalQuery

Je dois compter le nombre d'éléments distincts de ce tableau, mais le nombre distinct est supérieur à deux colonnes.

Ma requête fonctionne bien mais je me demandais si je pouvais obtenir le résultat final en utilisant une seule requête (sans utiliser une sous-requête)

154
Novitzky

Si vous essayez d’améliorer les performances, vous pouvez essayer de créer une colonne calculée persistante sur une valeur de hachage ou concaténée des deux colonnes.

Une fois qu'elle est persistante, à condition que la colonne soit déterministe et que vous utilisiez des paramètres de base de données "sane", vous pouvez l'indexer et/ou créer des statistiques. 

Je crois qu'un compte distinct de la colonne calculée serait équivalent à votre requête.

55
JasonHorner

Modification: modification de la requête de somme de contrôle moins que fiable J'ai découvert un moyen de le faire (dans SQL Server 2005) qui fonctionne assez bien pour moi et je peux utiliser autant de colonnes J'ai besoin (en les ajoutant à la fonction CHECKSUM ()). La fonction REVERSE () transforme les ints en varchars pour améliorer la fiabilité

SELECT COUNT(DISTINCT (CHECKSUM(DocumentId,DocumentSessionId)) + CHECKSUM(REVERSE(DocumentId),REVERSE(DocumentSessionId)) )
FROM DocumentOutPutItems
47
JayTee

Qu'est-ce qui vous déplaît dans votre requête existante? Si vous pensez que DISTINCT sur deux colonnes ne renvoie pas uniquement les permutations uniques, pourquoi ne pas l'essayer? 

Cela fonctionne certainement comme prévu dans Oracle.

SQL> select distinct deptno, job from emp
  2  order by deptno, job
  3  /

    DEPTNO JOB
---------- ---------
        10 CLERK
        10 MANAGER
        10 PRESIDENT
        20 ANALYST
        20 CLERK
        20 MANAGER
        30 CLERK
        30 MANAGER
        30 SALESMAN

9 rows selected.


SQL> select count(*) from (
  2  select distinct deptno, job from emp
  3  )
  4  /

  COUNT(*)
----------
         9

SQL>

modifier

Je suis allé dans une impasse avec les analyses mais la réponse était terriblement évidente ...

SQL> select count(distinct concat(deptno,job)) from emp
  2  /

COUNT(DISTINCTCONCAT(DEPTNO,JOB))
---------------------------------
                                9

SQL>

modifier 2

Compte tenu des données suivantes, la solution de concaténation fournie ci-dessus comptera de manière erronée:

col1  col2
----  ----
A     AA
AA    A

Nous devons donc inclure un séparateur ...

select col1 + '*' + col2 from t23
/

De toute évidence, le séparateur choisi doit être un caractère, ou un ensemble de caractères, qui ne peuvent jamais apparaître dans l'une ou l'autre colonne. 

22
APC

Que diriez-vous de quelque chose comme:

 sélectionner le compte (*) 
 à partir de 
 (sélectionnez count (*) cnt 
 à partir de DocumentOutputItems 
 groupe par DocumentId, DocumentSessionId) t1 

Probablement juste fait la même chose que vous êtes déjà mais cela évite le DISTINCT.

14
Trevor Tippins

Pour exécuter une requête unique, concaténez les colonnes, puis obtenez le nombre d'instances distinct de la chaîne concaténée.

SELECT count(DISTINCT concat(DocumentId, DocumentSessionId)) FROM DocumentOutputItems;

Dans MySQL, vous pouvez faire la même chose sans l'étape de concaténation comme suit:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Cette fonctionnalité est mentionnée dans la documentation MySQL:

http://dev.mysql.com/doc/refman/5.7/fr/group-by-functions.html#function_count-distinct

9
spelunk1

Voici une version plus courte sans la sous-sélection:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

Cela fonctionne très bien dans MySQL, et je pense que l'optimiseur a plus de facilité à comprendre celui-ci.

Edit: Apparemment, j'ai mal interprété MSSQL et MySQL - désolé, mais peut-être que ça aide quand même.

7
Alexander Kjäll

J'ai trouvé cela quand j'ai cherché sur Google pour mon propre problème. J'ai constaté que si vous comptez les objets DISTINCT, vous obtenez le nombre correct renvoyé (j'utilise MySQL).

SELECT COUNT(DISTINCT DocumentID) AS Count1, 
  COUNT(DISTINCT DocumentSessionId) AS Count2
  FROM DocumentOutputItems
4
tehaugmenter

Votre requête n'a rien d'anormal, mais vous pouvez aussi le faire de cette façon:

WITH internalQuery (Amount)
AS
(
    SELECT (0)
      FROM DocumentOutputItems
  GROUP BY DocumentId, DocumentSessionId
)
SELECT COUNT(*) AS NumberOfDistinctRows
  FROM internalQuery
3
Bliek

J'espère que cela fonctionne, j'écris sur prima Vista

SELECT COUNT(*) 
FROM DocumentOutputItems 
GROUP BY DocumentId, DocumentSessionId
2
IordanTanev

si vous ne possédez qu'un seul champ pour "DISTINCT", vous pouvez utiliser:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

et cela renvoie le même plan de requête que l'original, testé avec SET SHOWPLAN_ALL ON. Cependant, vous utilisez deux champs afin que vous puissiez essayer quelque chose de fou comme:

    SELECT COUNT(DISTINCT convert(varchar(15),DocumentId)+'|~|'+convert(varchar(15), DocumentSessionId)) 
    FROM DocumentOutputItems

mais vous aurez des problèmes si NULL sont impliqués. Je resterais juste avec la requête originale.

2
KM.

Beaucoup de bases de données SQL (la plupart?) Peuvent fonctionner avec des nuplets comme des valeurs. Vous pouvez donc simplement: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; Si votre base de données ne le supporte pas, elle peut être simulée selon la suggestion de CHECKSUM de @ oncel-umut-turer ou une autre fonction scalaire offrant une bonne unicité, par exemple COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Une utilisation connexe des n-uplets consiste à exécuter des requêtes IN telles que: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));

2
karmakaze

Je souhaite que MS SQL puisse également faire quelque chose comme COUNT (DISTINCT A, B). Mais ça ne peut pas.

Au début, la réponse de JayTee semblait être une solution pour moi, mais après quelques tests, CHECKSUM () n'a pas réussi à créer des valeurs uniques. Un exemple rapide est que CHECKSUM (31 467 519) et CHECKSUM (69, 1120 823) donnent la même réponse, à savoir 55.

Après quelques recherches, j'ai constaté que Microsoft ne recommandait PAS d'utiliser CHECKSUM à des fins de détection des modifications. Dans certains forums, certains ont suggéré d'utiliser 

SELECT COUNT(DISTINCT CHECKSUM(value1, value2, ..., valueN) + CHECKSUM(valueN, value(N-1), ..., value1))

mais ce n'est pas non plus rassurant.

Vous pouvez utiliser la fonction HASHBYTES () comme suggéré dans TSQL CHECKSUM conundrum . Cependant, cela a aussi une petite chance de ne pas renvoyer des résultats uniques.

Je suggère d'utiliser

SELECT COUNT(DISTINCT CAST(DocumentId AS VARCHAR)+'-'+CAST(DocumentSessionId AS VARCHAR)) FROM DocumentOutputItems
1
Oncel Umut TURER

J'avais une question similaire mais la requête que j'avais était une sous-requête avec les données de comparaison dans la requête principale. quelque chose comme:

Select code, id, title, name 
(select count(distinct col1) from mytable where code = a.code and length(title) >0)
from mytable a
group by code, id, title, name
--needs distinct over col2 as well as col1

ignorant la complexité de ceci, j'ai réalisé que je ne pouvais pas obtenir la valeur de a.code dans la sous-requête avec la requête double sous décrite dans la question initiale.

Select count(1) from (select distinct col1, col2 from mytable where code = a.code...)
--this doesn't work because the sub-query doesn't know what "a" is

Alors, finalement, j'ai compris que je pouvais tricher et combiner les colonnes:

Select count(distinct(col1 || col2)) from mytable where code = a.code...

C'est ce qui a fini par fonctionner

0
Mark Rogers

Vous pouvez simplement utiliser la fonction Compter deux fois.

Dans ce cas, ce serait:

SELECT COUNT (DISTINCT DocumentId), COUNT (DISTINCT DocumentSessionId) 
FROM DocumentOutputItems
0
Bibek

Que dis-tu de ça,

Select DocumentId, DocumentSessionId, count(*) as c 
from DocumentOutputItems 
group by DocumentId, DocumentSessionId;

Cela nous donnera le nombre de toutes les combinaisons possibles de DocumentId et DocumentSessionId

0
Nikhil Singh

J'ai utilisé cette approche et cela a fonctionné pour moi.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

Pour mon cas, le résultat est correct.

0
Jaanis Veinberg

Ça marche pour moi. Dans Oracle:

SELECT SUM(DECODE(COUNT(*),1,1,1))
FROM DocumentOutputItems GROUP BY DocumentId, DocumentSessionId;

En jpql:

SELECT SUM(CASE WHEN COUNT(i)=1 THEN 1 ELSE 1 END)
FROM DocumentOutputItems i GROUP BY i.DocumentId, i.DocumentSessionId;
0
Nata