Contando DISTINCT sobre múltiples columnas

214

¿Hay una mejor manera de hacer una consulta como esta:

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

Necesito contar el número de elementos distintos de esta tabla, pero el elemento distintivo está sobre dos columnas.

Mi consulta funciona bien, pero me preguntaba si puedo obtener el resultado final usando solo una consulta (sin usar una subconsulta)

Novitzky
fuente
IordanTanev, Mark Brackett, RC: gracias por las respuestas, fue un buen intento, pero debe verificar lo que está haciendo antes de publicar en SO. Las consultas que proporcionó no son equivalentes a mi consulta. Puede ver fácilmente que siempre tengo un resultado escalar, pero su consulta devuelve varias filas.
Novitzky
Acabo de actualizar la pregunta para incluir su comentario aclaratorio de una de las respuestas
Jeff
Esta es una buena pregunta. También me preguntaba si había una manera más simple de hacer esto
Anupam,

Respuestas:

73

Si está intentando mejorar el rendimiento, puede intentar crear una columna calculada persistente en un valor hash o concatenado de las dos columnas.

Una vez que persiste, siempre que la columna sea determinista y esté utilizando configuraciones de base de datos "sanas", se puede indexar y / o se pueden crear estadísticas en ella.

Creo que un recuento distinto de la columna calculada sería equivalente a su consulta.

Jason Horner
fuente
44
Excelente sugerencia! Cuanto más leo, más me doy cuenta de que SQL se trata menos de conocer la sintaxis y las funciones y más de aplicar la lógica pura. ¡Ojalá tuviera 2 votos a favor!
tumchaaditya
Muy buena sugerencia. Me evitó escribir código innecesario para esto.
Avrajit Roy
1
¿Podría agregar un ejemplo o ejemplo de código para mostrar más sobre lo que esto significa y cómo hacerlo?
Jayqui
52

Editar: alterado de la consulta de suma de comprobación poco confiable, descubrí una forma de hacer esto (en SQL Server 2005) que funciona bastante bien para mí y puedo usar tantas columnas como necesite (agregándolas a la función CHECKSUM ()). La función REVERSE () convierte los ints en varchars para hacer que el distintivo sea más confiable

SELECT COUNT(DISTINCT (CHECKSUM(DocumentId,DocumentSessionId)) + CHECKSUM(REVERSE(DocumentId),REVERSE(DocumentSessionId)) )
FROM DocumentOutPutItems
JayTee
fuente
1
+1 Nice one, funciona perfecto (cuando tienes los tipos de columna correctos para realizar un CheckSum en ...;)
Bernoulli IT
8
Con hashes como Checksum (), existe una pequeña posibilidad de que se devuelva el mismo hash para diferentes entradas, por lo que el recuento puede estar muy ligeramente desactivado. HashBytes () es una posibilidad aún menor pero aún no cero. Si esos dos Ids fueran int (32b), entonces un "hash sin pérdidas" podría combinarlos en un bigint (64b) como Id1 << 32 + Id2.
crokusek
1
la posibilidad no es tan pequeña, especialmente cuando comienzas a combinar columnas (que es para lo que se suponía que debía ser). Tenía curiosidad sobre este enfoque y, en un caso particular, la suma de verificación terminó con un recuento 10% menor. Si lo piensa un poco más, Checksum solo devuelve un int, por lo que si suma un rango de bigint completo, obtendrá un recuento distinto aproximadamente 2 mil millones de veces más pequeño de lo que realmente hay. -1
pvolders
Se actualizó la consulta para incluir el uso de "REVERSE" para eliminar la posibilidad de duplicados
JayTee
44
¿Podríamos evitar CHECKSUM? ¿Podríamos concatenar los dos valores juntos? Supongo que corre el riesgo de considerar lo mismo: ('él', 'arte') == 'escuchar', 't'). Pero creo que eso se puede resolver con un delimitador como propone @APC (algún valor que no aparece en ninguna de las columnas), entonces 'he | ​​art'! = 'Escuchar | t' ¿Hay otros problemas con una simple "concatenación" ¿Acercarse?
The Red Pea
32

¿De qué se trata su consulta existente que no le gusta? Si te preocupa queDISTINCT en dos columnas no devuelva solo las permutaciones únicas, ¿por qué no probarlo?

Ciertamente funciona como es de esperar en 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>

editar

Bajé por un callejón sin salida con análisis, pero la respuesta fue deprimentemente obvia ...

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

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

SQL>

editar 2

Teniendo en cuenta los siguientes datos, la solución de concatenación proporcionada anteriormente contará erróneamente:

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

Entonces incluiremos un separador ...

select col1 + '*' + col2 from t23
/

Obviamente, el separador elegido debe ser un carácter, o conjunto de caracteres, que nunca puede aparecer en ninguna columna.

APC
fuente
+1 de mi parte Gracias por tu respuesta. Mi consulta funciona bien, pero me preguntaba si puedo conseguir el resultado final utilizando una sola consulta (sin necesidad de utilizar una subconsulta)
Novitzky
20

Para ejecutar como una sola consulta, concatene las columnas, luego obtenga el recuento distinto de instancias de la cadena concatenada.

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

En MySQL puede hacer lo mismo sin el paso de concatenación de la siguiente manera:

SELECT count(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems;

Esta característica se menciona en la documentación de MySQL:

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

spelunk1
fuente
Esta fue una pregunta de SQL Server, y ambas opciones que publicó ya se han mencionado en las siguientes respuestas a esta pregunta: stackoverflow.com/a/1471444/4955425 y stackoverflow.com/a/1471713/4955425 .
sstan
1
FWIW, esto casi funciona en PostgreSQL; solo necesito paréntesis adicionales:SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems;
ijoseph
14

¿Qué tal algo como:

seleccione cuenta (*)
de
  (seleccione cuenta (*) cnt
   de DocumentOutputItems
   grupo por DocumentId, DocumentSessionId) t1

Probablemente solo haga lo mismo que usted, pero evita el DISTINCT.

Trevor Tippins
fuente
en mis pruebas (usando SET SHOWPLAN_ALL ON), tenía el mismo plan de ejecución y exactamente el mismo TotalSubtreeCost
KM.
1
Dependiendo de la complejidad de la consulta original, resolver esto GROUP BYpuede presentar un par de desafíos adicionales a la transformación de la consulta para lograr el resultado deseado (por ejemplo, cuando la consulta original ya tenía GROUP BYo HAVINGcláusulas ...)
Lukas Eder
8

Aquí hay una versión más corta sin la subselección:

SELECT COUNT(DISTINCT DocumentId, DocumentSessionId) FROM DocumentOutputItems

Funciona bien en MySQL, y creo que al optimizador le resulta más fácil entenderlo.

Editar: Aparentemente leí mal MSSQL y MySQL, lo siento, pero tal vez ayude de todos modos.

Alexander Kjäll
fuente
66
en SQL Server obtienes: Msg 102, Nivel 15, Estado 1, Línea 1 Sintaxis incorrecta cerca de ','.
KM.
Esto es en lo que estaba pensando. Quiero hacer algo similar en MSSQL si es posible.
Novitzky
@Kamil Nowicki, en SQL Server, solo puede tener un campo en COUNT (), en mi respuesta, muestro que puede concatenar los dos campos en uno y probar este enfoque. Sin embargo, me quedaría con el original ya que los planes de consulta terminarían igual.
KM.
1
Por favor, eche un vistazo en @JayTee respuesta. Funciona a las mil maravillas. count ( distinct CHECKSUM ([Field1], [Field2])
Custodio
5

Muchas (¿la mayoría?) Bases de datos SQL pueden funcionar con tuplas como valores, por lo que puede hacer lo siguiente: SELECT COUNT(DISTINCT (DocumentId, DocumentSessionId)) FROM DocumentOutputItems; si su base de datos no lo admite, puede simularse según la sugerencia de @ oncel-umut-turer de CHECKSUM u otra función escalar que brinde una buena singularidad. por ej COUNT(DISTINCT CONCAT(DocumentId, ':', DocumentSessionId)).

Un uso relacionado de tuplas es realizar INconsultas como: SELECT * FROM DocumentOutputItems WHERE (DocumentId, DocumentSessionId) in (('a', '1'), ('b', '2'));

karmakaze
fuente
¿Qué bases de datos son compatibles select count(distinct(a, b))? : D
Vytenis Bivainis
@VytenisBivainis Sé que PostgreSQL lo hace, no estoy seguro desde qué versión.
karmakaze
3

No hay nada malo con su consulta, pero también puede hacerlo de esta manera:

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

Espero que esto funcione, estoy escribiendo en prima vista

SELECT COUNT(*) 
FROM DocumentOutputItems 
GROUP BY DocumentId, DocumentSessionId
IordanTanev
fuente
77
Para que esto dé la respuesta final, deberá envolverlo en otro SELECT COUNT (*) FROM (...). Esencialmente, esta respuesta es simplemente darle otra forma de enumerar los distintos valores que desea contar. No es mejor que tu solución original.
Dave Costa
Gracias Dave Sé que puedes usar group by en lugar de distinto en mi caso. Me preguntaba si obtienes el resultado final usando solo una consulta. Creo que es imposible pero podría estar equivocado.
Novitzky
3

He usado este enfoque y me ha funcionado.

SELECT COUNT(DISTINCT DocumentID || DocumentSessionId) 
FROM  DocumentOutputItems

Para mi caso, proporciona el resultado correcto.

Jaanis Veinberg
fuente
No le da el recuento de valores distintos en conjunción con dos columnas. Al menos no en MySQL 5.8.
Anwar Shaikh
Esta pregunta está etiquetada como SQL Server, y esta no es la sintaxis de SQL Server
Tab Alleman
2

si solo tuviera un campo para "DISTINCT", podría usar:

SELECT COUNT(DISTINCT DocumentId) 
FROM DocumentOutputItems

y eso devuelve el mismo plan de consulta que el original, según lo probado con SET SHOWPLAN_ALL ON. Sin embargo, está utilizando dos campos para que pueda probar algo loco como:

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

pero tendrá problemas si hay NULL involucrados. Solo me quedaría con la consulta original.

KM.
fuente
+1 de mi parte Gracias, pero seguiré con mi consulta como usted sugirió. El uso de "convertir" puede disminuir el rendimiento aún más.
Novitzky
2

Encontré esto cuando busqué en Google mi propio problema, descubrí que si cuentas los objetos DISTINCT, obtienes el número correcto devuelto (estoy usando MySQL)

SELECT COUNT(DISTINCT DocumentID) AS Count1, 
  COUNT(DISTINCT DocumentSessionId) AS Count2
  FROM DocumentOutputItems
tehaugmenter
fuente
55
La consulta anterior devolverá un conjunto de resultados diferente al que estaba buscando el OP (las distintas combinaciones de DocumentIdy DocumentSessionId). Alexander Kjäll ya publicó la respuesta correcta si el OP estaba usando MySQL y no MS SQL Server.
Anthony Geoghegan
1

Desearía que MS SQL también pudiera hacer algo como COUNT (DISTINCT A, B). Pero no puede.

Al principio, la respuesta de JayTee me pareció una solución, pero después de algunas pruebas CHECKSUM () no pudo crear valores únicos. Un ejemplo rápido es que tanto CHECKSUM (31,467,519) como CHECKSUM (69,1120,823) dan la misma respuesta que es 55.

Luego investigué un poco y descubrí que Microsoft NO recomienda usar CHECKSUM para fines de detección de cambios. En algunos foros algunos sugirieron usar

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

pero esto tampoco es reconfortante.

Puede usar la función HASHBYTES () como se sugiere en el enigma CHECKSUM de TSQL . Sin embargo, esto también tiene una pequeña posibilidad de no devolver resultados únicos.

Sugeriría usar

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

Qué tal esto,

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

Esto nos dará el recuento de todas las combinaciones posibles de DocumentId y DocumentSessionId

Nikhil Singh
fuente
0

Esto funciona para mi. En oráculo:

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;
Nata
fuente
0

Tenía una pregunta similar, pero la consulta que tuve fue una subconsulta con los datos de comparación en la consulta principal. algo como:

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

Ignorando las complejidades de esto, me di cuenta de que no podía obtener el valor de un código en la subconsulta con la subconsulta doble descrita en la pregunta original

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

Así que eventualmente descubrí que podía hacer trampa y combinar las columnas:

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

Esto es lo que terminó funcionando

Mark Rogers
fuente
0

Si está trabajando con tipos de datos de longitud fija, puede enviarlos binarypara hacerlo de manera muy fácil y rápida. Asumiendo DocumentIdy DocumentSessionIdson ambos ints, y por lo tanto son 4 bytes de longitud ...

SELECT COUNT(DISTINCT CAST(DocumentId as binary(4)) + CAST(DocumentSessionId as binary(4)))
FROM DocumentOutputItems

Mi problema específico me obligaba a dividir un SUMporCOUNT combinación distinta de varias claves foráneas y un campo de fecha, agrupando por otra clave foránea y ocasionalmente filtrando por ciertos valores o claves. La tabla es muy grande y el uso de una subconsulta aumentó drásticamente el tiempo de consulta. Y debido a la complejidad, las estadísticas simplemente no eran una opción viable. La CHECKSUMsolución también fue demasiado lenta en su conversión, particularmente como resultado de los diversos tipos de datos, y no podía arriesgar su falta de confiabilidad.

Sin embargo, el uso de la solución anterior prácticamente no aumentó el tiempo de consulta (en comparación con el uso de simplemente el SUM), ¡y debería ser completamente confiable! Debería poder ayudar a otros en una situación similar, así que lo estoy publicando aquí.

IphStich
fuente
-1

Simplemente puede usar la función Count dos veces.

En este caso, sería:

SELECT COUNT (DISTINCT DocumentId), COUNT (DISTINCT DocumentSessionId) 
FROM DocumentOutputItems
Bibek
fuente
esto no hace lo que se requiere en la pregunta, cuenta lo distinto en cada columna por separado
naviram
-1

Este código utiliza distintos parámetros en 2 y proporciona el recuento de la cantidad de filas específicas para esos valores distintos. Me funcionó en MySQL como un encanto.

select DISTINCT DocumentId as i,  DocumentSessionId as s , count(*) 
from DocumentOutputItems   
group by i ,s;
rishi jain
fuente