Skip to content
Empieza gratis

¿Por qué DISTINCT no quita mis duplicados? ROW_NUMBER y QUALIFY en DuckDB

Compara DISTINCT con ROW_NUMBER para quitar folios repetidos en SQL, y quédate con la última captura de cada venta.

Curso gratis. Tu Maestro, una IA, .

En SQL, la primera palabra que viene a la mente para quitar repetidos es DISTINCT. Tiene el mismo problema que drop_duplicates() sin subset: compara renglones completos. En la hoja de marzo:

  • SELECT DISTINCT * FROM ventas devuelve 60 renglones. No quita nada, porque la columna captura es distinta en cada uno.
  • Sin captura, SELECT DISTINCT con las demás columnas devuelve 59. Quita la copia exacta de V-0017, pero la V-0031 sigue dos veces, porque sus kilos son distintos.
  • Hay 58 folios distintos. Ese es el número de ventas que debería quedar.

DISTINCT contesta "¿qué combinaciones distintas hay?", no "¿cuál copia de cada venta vale?". Para la segunda pregunta necesitas decir, como en pandas, la llave (el folio) y la regla para elegir (la captura más reciente). En SQL eso se escribe con ROW_NUMBER().

SQL
SELECT folio, captura, kilos,
       ROW_NUMBER() OVER (PARTITION BY folio ORDER BY captura DESC) AS n
FROM ventas
WHERE folio IN ('V-0017', 'V-0031', 'V-0040', 'V-0041')
ORDER BY folio, n;
Numera las copias de cada folio, de la captura más reciente a la más vieja.

Léelo por partes:

  • PARTITION BY folio hace un grupo por folio. Es la llave.
  • ORDER BY captura DESC ordena cada grupo de la captura más reciente a la más vieja. Es la regla de Marisol, escrita dentro de la consulta: no depende del orden en que estén guardados los renglones, que es justo el problema que tenía keep="last" sin sort_values.
  • ROW_NUMBER() numera cada grupo: 1, 2, 3...

Corrida sobre esos cuatro folios, la consulta da: V-0017 con la captura 18 como 1 y la 17 como 2; V-0031 con la captura 36 (53 kilos) como 1 y la 32 (5 kilos) como 2; V-0040 y V-0041 con un solo renglón cada una, los dos como 1. Quedarte con los renglones donde n es 1 es quedarte con la última captura de cada venta.

  1. 60 renglones de ventas

  2. Un grupo por folio

    PARTITION BY folio

  3. La captura más reciente primero

    ORDER BY captura DESC

  4. Numera cada grupo

    ROW_NUMBER()

  5. Quédate con n = 1

    58 ventas

Dos formas de quedarte con n = 1

No puedes escribir WHERE n = 1 en la misma consulta que calcula n. Si lo intentas, DuckDB se detiene con "Binder Error: WHERE clause cannot contain window functions!". Hay dos salidas, y dan el mismo resultado.

Con QUALIFY, que DuckDB tiene justo para filtrar el resultado de una función de ventana:

SQL
SELECT folio, captura, kilos
FROM ventas
QUALIFY ROW_NUMBER() OVER (PARTITION BY folio ORDER BY captura DESC) = 1;
QUALIFY filtra después de calcular la función de ventana, así que no hace falta un número visible.

Con un WITH, la forma que te va a servir si tu base de datos no tiene QUALIFY: primero numeras, después filtras.

SQL
WITH numeradas AS (
  SELECT *,
         ROW_NUMBER() OVER (PARTITION BY folio ORDER BY captura DESC) AS n
  FROM ventas
)
SELECT folio, captura, kilos
FROM numeradas
WHERE n = 1;
La primera consulta numera; la segunda filtra con WHERE n = 1.

Las dos devuelven 58 ventas en la hoja de marzo, con la V-0031 en 53 kilos. Y las dos conservan los dos pedidos de Hotel Mirador, porque tienen folios distintos: ROW_NUMBER solo compara renglones dentro del mismo folio.

¿Tu base de datos no tiene QUALIFY? Pregúntale a tu Maestro cómo escribir la versión con WITH para tu caso.

Si en tu trabajo hay una forma de saber cuál copia vale (una fecha de modificación, un número de versión, una columna de estado), va en el ORDER BY de la ventana. Si no la hay, la decisión no se puede escribir en una consulta y se pregunta, como hiciste con Juana en el padrón.

Inténtalo

La consulta de abajo usa DISTINCT y devuelve 60 renglones. Cámbiala para que devuelva una sola venta por folio, la de la captura más reciente. Si te sale un error con WHERE, pregúntale a tu Maestro por qué.

Reescribe la consulta con ROWNUMBER() para quedarte con la última captura de cada folio (con QUALIFY o con un WITH). Devuelve folio, captura y kilos, ordenado por folio. Terminaste cuando salen 58 renglones y la V-0031 tiene 53 kilos.

Tu turno

Reescribe la consulta con ROW_NUMBER() para quedarte con la última captura de cada folio (con QUALIFY o con un WITH). Devuelve folio, captura y kilos, ordenado por folio. Terminaste cuando salen 58 renglones y la V-0031 tiene 53 kilos.

Tablas: ventas (captura, folio, fecha, cliente, ciudad, producto, kilos, monto, descuento)

ventas · 60 filas

ColumnaTipo
captura Orden en que se capturó el renglón en la hoja (1 es el primero).BIGINT
folio Folio de la venta.VARCHAR
fecha Fecha de la venta, AAAA-MM-DD.VARCHAR
clienteVARCHAR
ciudad Ciudad del cliente, como se capturó.VARCHAR
productoVARCHAR
kilos Kilos vendidos, como se capturaron.DOUBLE
monto Monto de la venta en pesos, como texto tal como se capturó.VARCHAR
descuento Descuento en porcentaje; vacío cuando no hubo descuento.DOUBLE

Primeras filas

capturafoliofechaclienteciudadproductokilosmontodescuento
1V-00012026-03-01Abarrotes Doña MaryGuadalajaraCafé tostado28$7,784.0010
2V-00022026-03-01Grano FinoCiudad de MéxicoCafé verde464,69210
3V-00032026-03-02Hotel MiradorpueblaCafé tostado4514,130NULL

Cada ejecución empieza con copias nuevas de las tablas del curso, así que puedes probar lo que quieras.

Listo para ejecutar tu consulta.

Ejecútalo tú

Si tu resultado tiene 58 renglones, revisa además la V-0031: si dice 5 kilos, tu ORDER BY está al revés y te quedaste con la captura más vieja. Es la misma comprobación que hiciste en pandas: contar no basta, hay que mirar los casos que conoces.

Comprueba lo que sabes: En la hoja de marzo, `SELECT DISTINCT * FROM ventas` devuelve 60 renglones. ¿Qué te dice eso?