1. Qué es
DISTINCT elimina filas de resultado repetidas. Es el paso 6 del orden-logico-de-ejecucion: actúa sobre lo que el SELECT ya produjo, no sobre la tabla.
Y ahí está todo lo importante del tema, en dos frases:
DISTINCTcompara la fila entera delSELECT, no la primera columna.DISTINCTes a veces la respuesta y muchas veces una tirita. Cuando aparece para "arreglar" filas duplicadas que no esperabas, casi siempre está tapando un error en elFROM.
Este último punto es el que hace que este tema cierre el bloque 1 y no lo abra.
2. Sintaxis
SELECT DISTINCT columna1, columna2 FROM tabla; -- filas únicas
SELECT count(DISTINCT columna) FROM tabla; -- cuántos valores distintos
SELECT DISTINCT ON (columna) * FROM tabla ORDER BY columna, otra; -- solo PostgreSQL
DISTINCT va justo después de SELECT y afecta a todas las columnas. No existe SELECT col1, DISTINCT col2.
3. Ejemplos sobre Northwind
Los valores distintos de una columna:
SELECT DISTINCT country FROM customers ORDER BY country;
country
-----------
Argentina
Austria
Belgium
Brazil
...
21 países para 91 clientes.
Contar distintos sin listarlos:
SELECT count(*) AS filas,
count(DISTINCT country) AS paises,
count(DISTINCT city) AS ciudades
FROM customers;
filas | paises | ciudades
-------+--------+----------
91 | 21 | 69
Esas tres cifras juntas son un perfilado: 91 clientes repartidos en 69 ciudades y 21 países. Con eso ya sabes que la ciudad casi identifica al cliente y el país no.
DISTINCT mira la fila entera
SELECT DISTINCT country, city FROM customers ORDER BY country, city;
country | city
-----------+--------------
Argentina | Buenos Aires
Austria | Graz
Austria | Salzburg
Belgium | Bruxelles
Belgium | Charleroi
69 filas, no 21. Austria sale dos veces porque las parejas son distintas. Si esperabas 21, estabas pensando en DISTINCT country y escribiste otra cosa.
DISTINCT sí junta los NULL
SELECT DISTINCT region FROM customers ORDER BY region NULLS LAST;
Devuelve 19 filas: 18 regiones más una fila vacía. Pero:
SELECT count(DISTINCT region) FROM customers; -- 18
DISTINCT trata todos los NULL como uno solo y los devuelve; count(DISTINCT ...) los ignora. No es contradictorio —count ignora nulos siempre, null-y-la-logica-de-tres-valores— pero explica por qué la lista tiene una fila más que el conteo. Si comparas las dos cifras sin saberlo, parecen un error.
DISTINCT vs GROUP BY
SELECT DISTINCT country FROM customers;
SELECT country FROM customers GROUP BY country;
Idénticas: mismo resultado y mismo plan de ejecución. La diferencia es de intención:
DISTINCTdice "quita repetidos".GROUP BYdice "agrupa", y deja la puerta abierta a añadir uncount(*)o unsum().
Usa DISTINCT cuando solo quieras la lista, y GROUP BY en cuanto vayas a agregar algo. Casi siempre acabas queriendo agregar algo.
La parte importante: DISTINCT como tirita
Este es el ejemplo que hay que entender. Un JOIN entre pedidos y sus líneas:
SELECT count(*) FROM orders; -- 830
SELECT count(*) FROM orders o
JOIN order_details od ON o.order_id = od.order_id; -- 2155
El JOIN multiplicó las filas: cada pedido aparece una vez por cada línea que tiene. Es correcto —así funciona un join, inner-join— pero rompe cualquier suma sobre columnas del pedido:
SELECT sum(freight) FROM orders; -- 64942.69 ← el real
SELECT sum(o.freight) FROM orders o
JOIN order_details od ON o.order_id = od.order_id; -- 207306.10 ← inflado
Un 219% de más. Y ahora la tentación, que es exactamente lo que hace todo el mundo la primera vez:
SELECT sum(DISTINCT o.freight) FROM orders o
JOIN order_details od ON o.order_id = od.order_id; -- 64526.94
64.526,94. Se parece muchísimo al bueno. Y está mal.
sum(DISTINCT ...) no suma "un flete por pedido": suma valores de flete distintos. Dos pedidos que casualmente costaron 32,38 de flete cuentan una sola vez. La diferencia son 415,75 escondidos entre casi 65.000 — el tipo de error que pasa todas las revisiones, porque el número es plausible.
Regla: si
DISTINCTaparece para arreglar filas de más, el problema está en elFROM, no en elSELECT. La solución correcta es agregar al nivel adecuado, no deduplicar al final. Es el tema joins-multiples-y-granularidad, y por eso este ejemplo se planta aquí: para que cuando llegues allí ya te suene el peligro.
Nota complementaria: count(DISTINCT o.order_id) sobre ese mismo join sí devuelve 830 correctamente. Contar distintos suele ser legítimo; sumar distintos casi nunca lo es.
DISTINCT ON — el atajo de PostgreSQL
El último pedido de cada cliente:
SELECT DISTINCT ON (customer_id)
customer_id, order_id, order_date
FROM orders
ORDER BY customer_id, order_date DESC, order_id DESC;
customer_id | order_id | order_date
-------------+----------+------------
ALFKI | 11011 | 1998-04-09
ANATR | 10926 | 1998-03-04
ANTON | 10856 | 1998-01-28
AROUT | 11016 | 1998-04-10
Se queda con la primera fila de cada grupo, y "primera" la define el ORDER BY.
Dos reglas que no son negociables:
- El
ORDER BYdebe empezar por las mismas columnas delDISTINCT ON. Si no, error. - Lo que venga después decide cuál se queda. Sin
order_date DESCte quedas con un pedido cualquiera del cliente, no con el último.
DISTINCT ON no existe en el estándar SQL ni en SQL Server. Es de las cosas más cómodas de PostgreSQL y de las menos portables. El equivalente universal es ROW_NUMBER() (ranking-row-number-rank-ntile).
Encontrar duplicados, que no es lo mismo que quitarlos
DISTINCT los esconde. Para verlos se usa GROUP BY + HAVING:
SELECT customer_id, order_date, count(*)
FROM orders
GROUP BY customer_id, order_date
HAVING count(*) > 1
ORDER BY count(*) DESC;
customer_id | order_date | count
-------------+------------+-------
LACOR | 1998-03-24 | 2
LINOD | 1998-01-19 | 2
SAVEA | 1997-10-22 | 2
7 casos de un cliente con dos pedidos el mismo día. ¿Son duplicados? Vamos a mirar:
SELECT order_id, customer_id, order_date, employee_id, freight
FROM orders WHERE customer_id = 'LACOR' AND order_date = '1998-03-24';
order_id | customer_id | order_date | employee_id | freight
----------+-------------+------------+-------------+---------
10972 | LACOR | 1998-03-24 | 4 | 0.02
10973 | LACOR | 1998-03-24 | 6 | 15.17
No son duplicados. Distinto id, distinto empleado, distinto flete: son dos pedidos reales del mismo cliente el mismo día. Y esa es la lección del ejercicio:
GROUP BY ... HAVING count(*) > 1no encuentra duplicados. Encuentra repeticiones de la clave que tú elegiste. Si eliges mal la clave, todo parece duplicado. Decidir qué combinación de columnas debería ser única es una pregunta de negocio, no de SQL.
4. Errores comunes
- Creer que
DISTINCTaplica solo a la primera columna. Aplica a la fila entera. - Usar
DISTINCTpara tapar unJOINque multiplica filas. Ver arriba. Arregla elFROM. sum(DISTINCT x)oavg(DISTINCT x). Casi siempre da un número equivocado y creíble.- Comparar
DISTINCT colconcount(DISTINCT col)y asustarse por la diferencia de uno. Es elNULL. DISTINCT ONsin elORDER BYcorrecto. O da error, o te devuelve una fila arbitraria del grupo.- Poner
DISTINCT"por si acaso". Cuesta una ordenación o un hash sobre todo el resultado, y sobre todo te quita el aviso: si sobran filas, quieres enterarte.
5. Diferencias con SQL Server
| PostgreSQL | SQL Server | |
|---|---|---|
| Filas únicas | SELECT DISTINCT |
SELECT DISTINCT — igual |
| Contar distintos | count(DISTINCT col) |
COUNT(DISTINCT col) — igual |
| Primera fila por grupo | DISTINCT ON |
❌ No existe → ROW_NUMBER() OVER (PARTITION BY ...) |
count(DISTINCT) con ventana |
No permitido | No permitido |
DISTINCT ON es el único punto de divergencia, y es grande: una consulta con DISTINCT ON no se puede pegar en SQL Server. Si el material tiene que valer para los dos motores, escribe la versión con ROW_NUMBER().
6. Ejercicios
- Los cargos (
title) distintos que existen enemployees, y cuántos hay. - Cuántos países distintos aparecen como destino de un pedido (
ship_country), y si coinciden con los países de los clientes. - Las parejas país-ciudad distintas de
suppliers, ordenadas. - El pedido más antiguo de cada cliente, en una sola consulta.
- Explica por qué estas dos consultas dan números distintos y cuál es la correcta para "cuántos clientes han comprado":
sql SELECT count(*) FROM orders; SELECT count(DISTINCT customer_id) FROM orders;
7. Pregunta de negocio
Te piden "cuántos clientes distintos compraron en 1997 y de qué países". Un compañero ya la escribió y le salen 89 clientes, pero la empresa solo tiene 91 clientes en total y sabe que muchos no compraron ese año. ¿Qué pasó?