¿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 ventasdevuelve 60 renglones. No quita nada, porque la columnacapturaes distinta en cada uno.- Sin
captura,SELECT DISTINCTcon 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().
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;Léelo por partes:
PARTITION BY foliohace un grupo por folio. Es la llave.ORDER BY captura DESCordena 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íakeep="last"sinsort_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.
60 renglones de ventas
Un grupo por folio
PARTITION BY folio
La captura más reciente primero
ORDER BY captura DESC
Numera cada grupo
ROW_NUMBER()
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:
SELECT folio, captura, kilos
FROM ventas
QUALIFY ROW_NUMBER() OVER (PARTITION BY folio ORDER BY captura DESC) = 1;Con un WITH, la forma que te va a servir si tu base de datos no tiene QUALIFY: primero numeras, después filtras.
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;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
| Columna | Tipo |
|---|---|
| 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 |
| cliente | VARCHAR |
| ciudad Ciudad del cliente, como se capturó. | VARCHAR |
| producto | VARCHAR |
| 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
| captura | folio | fecha | cliente | ciudad | producto | kilos | monto | descuento |
|---|---|---|---|---|---|---|---|---|
| 1 | V-0001 | 2026-03-01 | Abarrotes Doña Mary | Guadalajara | Café tostado | 28 | $7,784.00 | 10 |
| 2 | V-0002 | 2026-03-01 | Grano Fino | Ciudad de México | Café verde | 46 | 4,692 | 10 |
| 3 | V-0003 | 2026-03-02 | Hotel Mirador | puebla | Café tostado | 45 | 14,130 | NULL |
Cada ejecución empieza con copias nuevas de las tablas del curso, así que puedes probar lo que quieras. Tab agrega sangría; para salir del editor, presiona Escape y luego Tab.
Listo para ejecutar tu consulta.
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.