¿Cómo puedo (o puedo) SELECCIONAR DISTINCT en varias columnas?

415

Necesito recuperar todas las filas de una tabla donde 2 columnas combinadas son todas diferentes. Así que quiero todas las ventas que no tienen otras ventas que ocurrieron el mismo día por el mismo precio. Las ventas que son únicas según el día y el precio se actualizarán a un estado activo.

Entonces estoy pensando:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

Pero me duele el cerebro ir más allá de eso.

fundas
fuente

Respuestas:

436
SELECT DISTINCT a,b,c FROM t

es más o menos equivalente a:

SELECT a,b,c FROM t GROUP BY a,b,c

Es una buena idea acostumbrarse a la sintaxis GROUP BY, ya que es más potente.

Para su consulta, lo haría así:

UPDATE sales
SET status='ACTIVE'
WHERE id IN
(
    SELECT id
    FROM sales S
    INNER JOIN
    (
        SELECT saleprice, saledate
        FROM sales
        GROUP BY saleprice, saledate
        HAVING COUNT(*) = 1 
    ) T
    ON S.saleprice=T.saleprice AND s.saledate=T.saledate
 )
Joel Coehoorn
fuente
117
Esta consulta, si bien es correcta y se acepta desde hace un año, es extremadamente ineficiente e innecesariamente. No uses esto. Proporcioné una alternativa y alguna explicación en otra respuesta.
Erwin Brandstetter
1
¿no es SELECT DISTINCT a, b, c FROM t exactamente lo mismo que SELECT a, b, c FROM t GROUP BY a, b, c?
famargar
8
@famargar para el caso simple, aún, pero tienen diferentes significados semánticamente, y son diferentes en términos de lo que puede hacer para el paso al construir una consulta más grande. Además, las personas en los foros de tecnología a menudo pueden ser extremadamente pedantes sobre las cosas, a menudo me resulta útil agregar palabras de comadreja a mis publicaciones en este contexto.
Joel Coehoorn
344

Si reúne las respuestas hasta ahora, limpia y mejora, llegaría a esta consulta superior:

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 1 
    );

Cual es mucho más rápido que cualquiera de ellos. Nukes el rendimiento de la respuesta actualmente aceptada por el factor 10-15 (en mis pruebas en PostgreSQL 8.4 y 9.1).

Pero esto aún está lejos de ser óptimo. Utilice una NOT EXISTS(anti) semiunión para un rendimiento aún mejor. EXISTSes SQL estándar, ha existido desde siempre (al menos desde PostgreSQL 7.2, mucho antes de que se hiciera esta pregunta) y se ajusta perfectamente a los requisitos presentados:

UPDATE sales s
SET    status = 'ACTIVE'
WHERE  NOT EXISTS (
   SELECT FROM sales s1                     -- SELECT list can be empty for EXISTS
   WHERE  s.saleprice = s1.saleprice
   AND    s.saledate  = s1.saledate
   AND    s.id <> s1.id                     -- except for row itself
   )
AND    s.status IS DISTINCT FROM 'ACTIVE';  -- avoid empty updates. see below

db <> violín aquí
Viejo violín de SQL

Clave única para identificar fila

Si no tiene una clave primaria o única para la tabla ( iden el ejemplo), puede sustituirla con la columna del sistema ctidpara el propósito de esta consulta (pero no para otros fines):

   AND    s1.ctid <> s.ctid

Cada tabla debe tener una clave primaria. Agregue uno si aún no tiene uno. Sugiero unserial o una IDENTITYcolumna en Postgres 10+.

Relacionado:

¿Cómo es esto más rápido?

La subconsulta en el EXISTSanti-semi-join puede dejar de evaluar tan pronto como se encuentre el primer engañado (no tiene sentido buscar más). Para una tabla base con pocos duplicados, esto es solo un poco más eficiente. Con muchos duplicados esto se convierte en camino más eficiente.

Excluir actualizaciones vacías

Para las filas que ya tienen status = 'ACTIVE'esta actualización, no cambiaría nada, pero aún así se inserta una nueva versión de la fila al costo total (se aplican excepciones menores). Normalmente, no quieres esto. Agregue otra WHEREcondición como se demostró anteriormente para evitar esto y hacerlo aún más rápido:

Si statusestá definido NOT NULL, puede simplificarlo para:

AND status <> 'ACTIVE';

El tipo de datos de la columna debe ser compatible con el <>operador. Algunos tipos jsonno lo hacen. Ver:

Diferencia sutil en el manejo NULL

Esta consulta (a diferencia de la respuesta aceptada actualmente por Joel ) no trata los valores NULL como iguales. Las siguientes dos filas (saleprice, saledate)calificarían como "distintas" (aunque parezcan idénticas al ojo humano):

(123, NULL)
(123, NULL)

También pasa un índice único y casi en cualquier otro lugar, ya que los valores NULL no se comparan igual de acuerdo con el estándar SQL. Ver:

Otoh, GROUP BY, DISTINCTo DISTINCT ON ()valores treat NULL como iguales. Use un estilo de consulta apropiado según lo que quiera lograr. Todavía puede usar esta consulta más rápida con en IS NOT DISTINCT FROMlugar de= cualquiera o todas las comparaciones para hacer que la comparación NULL sea igual. Más:

Si se definen todas las columnas que se comparan NOT NULL, no hay lugar para el desacuerdo.

Erwin Brandstetter
fuente
16
Buena respuesta. Soy un chico servidor SQL, por lo que no se me ocurrió la primera sugerencia de usar una tupla con una comprobación IN (). La sugerencia de no existe generalmente terminará con el mismo plan de ejecución en el servidor sql que la unión interna.
Joel Coehoorn
2
Agradable. La explicación aumenta enormemente el valor de la respuesta. Estoy casi tentado de ejecutar algunas pruebas con Oracle para ver cómo se comparan los planes con Postgres y SQLServer.
Peter
2
@alairock: ¿De dónde sacaste eso? Para Postgres, lo contrario es cierto. Mientras cuenta todas las filas, count(*)es más eficiente que count(<expression>). Solo inténtalo. Postgres tiene una implementación más rápida para esta variante de la función agregada. ¿Quizás estás confundiendo Postgres con algún otro RDBMS?
Erwin Brandstetter
66
@alairock: Soy coautor de esa página y no dice nada por el estilo.
Erwin Brandstetter
2
@ErwinBrandstetter, siempre estás a punto con tus respuestas en la pila. Has ayudado a lo largo de los años en una cantidad casi inimaginable. En cuanto a este ejemplo, conocía algunas formas diferentes de resolver mi problema, pero quería ver que alguien hubiera probado la eficiencia entre las posibilidades. Gracias.
WebWanderer
24

El problema con su consulta es que cuando usa una cláusula GROUP BY (que esencialmente hace al usar distintivo) solo puede usar columnas por las que agrupa o agrega funciones. No puede usar el id de columna porque hay valores potencialmente diferentes. En su caso, siempre hay un solo valor debido a la cláusula HAVING, pero la mayoría de los RDBMS no son lo suficientemente inteligentes como para reconocerlo.

Sin embargo, esto debería funcionar (y no necesita una combinación):

UPDATE sales
SET status='ACTIVE'
WHERE id IN (
  SELECT MIN(id) FROM sales
  GROUP BY saleprice, saledate
  HAVING COUNT(id) = 1
)

También puede usar MAX o AVG en lugar de MIN, solo es importante usar una función que devuelva el valor de la columna si solo hay una fila coincidente.

Christian Berg
fuente
1

Quiero seleccionar los valores distintos de una columna 'GrondOfLucht' pero deben clasificarse en el orden que figura en la columna 'clasificación'. No puedo obtener los valores distintos de una sola columna usando

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

También le dará a la columna 'clasificación' y debido a que 'GrondOfLucht' Y 'clasificación' no es única, el resultado será TODAS las filas.

use el GRUPO para seleccionar los registros de 'GrondOfLucht' en el orden dado por 'clasificación

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)
Frans eilering
fuente
Básicamente, esto explica lo que hace la respuesta aceptada, pero recomendaría no usar tales nombres para un ejemplo (al menos traducirlos). PD: Recomiendo nombrar siempre todo en inglés en todos los proyectos, incluso si eres holandés.
Kerwin Sneijders
0

Si su DBMS no es compatible con varias columnas como esta:

select distinct(col1, col2) from table

La selección múltiple en general se puede ejecutar de forma segura de la siguiente manera:

select distinct * from (select col1, col2 from table ) as x

Como esto puede funcionar en la mayoría de los DBMS y se espera que sea más rápido que agrupar por solución, ya que está evitando la funcionalidad de agrupación.

Abdulhafeth Sartawi
fuente