¿Seleccionar la primera fila de cada grupo GROUP BY?
lenguaje-todo: lang-sql -->
Como sugiere el título, me gustaría seleccionar la primera fila de cada conjunto de filas agrupadas con un GROUP BY
.
En concreto, si tengo una tabla de compras
con el siguiente aspecto
SELECT * FROM purchases;
Mi salida:
id | cliente | total ---+----------+------ 1 | Joe | 5 2. Sally: 3 3 | Joe | 2 4 | Sally | 1
Me gustaría consultar el id
de la mayor compra (total
) realizada por cada cliente
. Algo así:
SELECT FIRST(id), customer, FIRST(total)
FROM purchases
GROUP BY customer
ORDER BY total DESC;
Salida esperada: pre> FIRST(id) | cliente | FIRST(total) ----------+----------+------------- 1 | Joe | 5 2 | Sally | 3
1194
3
En PostgreSQL esto es típicamente más simple y más rápido (más optimización de rendimiento abajo):
O más corto (aunque no tan claro) con los números ordinales de las columnas de salida:Si
###Puntos principales - **`DISTINCT ON`**][1] es una extensión de PostgreSQL del estándar (donde sólo se define `DISTINCT` en toda la lista `SELECT`). - Enumera cualquier número de expresiones en la cláusula `DISTINCT ON`, el valor de la fila combinada define los duplicados. [El manual:][2] > Obviamente, dos filas se consideran distintas si difieren en al menos > un valor de columna. **Los valores nulos se consideran iguales en esta comparación. El énfasis en negrita es mío. - La expresión `DISTINCT ON` puede combinarse con **`ORDER BY`**. Las expresiones principales tienen que coincidir con las expresiones principales de `DISTINCT ON` en el mismo orden. Puedes añadir expresiones *adicionales* a `ORDER BY` para elegir una fila concreta de cada grupo de pares. He añadido `id` como último elemento para romper los empates: *"Elige la fila con el menor `id` de cada grupo que comparta el mayor `total`"*. Para ordenar los resultados de una manera que no coincida con el orden que determina el primero por grupo, puede anidar la consulta anterior en una consulta externa con otro `ORDER BY`. Por ejemplo - https://stackoverflow.com/questions/9795660/postgresql-distinct-on-with-different-order-by/9796104#9796104 - Si `total` puede ser NULL, lo más probable es que quieras la fila con el mayor valor no nulo. Añade **`NULLS LAST`** como se ha demostrado. Detalles: - https://stackoverflow.com/questions/9510509/postgresql-sort-by-datetime-asc-null-first/9511492#9511492 - La lista `SELECT`** no está restringida por las expresiones en `DISTINCT ON` o `ORDER BY` de ninguna manera. (No es necesario en el caso simple de arriba): - No tiene que incluir ninguna de las expresiones en `DISTINCT ON` o `ORDER BY`. - Puede incluir cualquier otra expresión en la lista `SELECT`. Esto es fundamental para reemplazar consultas mucho más complejas con subconsultas y funciones de agregación/ventana. - He probado con las versiones 8.3 - 12 de Postgres. Pero la característica ha estado allí al menos desde la versión 7.1, así que básicamente siempre. ##Índice El índice *perfecto* para la consulta anterior sería un [índice multicolumna][3] que abarcara las tres columnas en la misma secuencia y con el mismo orden de clasificación:total
puede ser NULL (no hará daño de cualquier manera, pero querrá coincidir con los índices existentes):Puede ser demasiado especializado. Pero utilícelo si el rendimiento de lectura para la consulta particular es crucial. Si tiene
DESC NULLS LAST
en la consulta, utilice lo mismo en el índice para que el orden de clasificación coincida y el índice sea aplicable.Eficacia / Optimización del rendimiento
Sopese el coste y el beneficio antes de crear índices a medida para cada consulta. El potencial del índice anterior depende en gran medida de la distribución de los datos. El índice se utiliza porque proporciona datos preordenados. En Postgres 9.2 o posterior, la consulta también puede beneficiarse de un escaneo de sólo índice si el índice es más pequeño que la tabla subyacente. Sin embargo, el índice tiene que ser escaneado en su totalidad.
Lo ideal es que tenga suficiente
work_mem
para procesar el paso de ordenación en la RAM y no pasarlo al disco. Pero en general, establecerwork_mem
demasiado alto puede tener efectos adversos. ConsidereSET LOCAL
para consultas excepcionalmente grandes. Averigüe cuánto necesita conEXPLAIN ANALYZE
. La mención de "Disk:" en el paso de clasificación indica la necesidad de más:cliente
), un escaneo de índice suelto (también conocido como "skip scan") sería (mucho) más eficiente, pero eso no está implementado hasta Postgres 12. (Se está desarrollando una implementación para escaneos sólo de índices para Postgres 13). Véase aquí y aquí).Por ahora, hay técnicas de consulta más rápidas para sustituir esto. En particular, si tiene una tabla separada que contenga clientes únicos, que es el caso típico de uso. Pero también si no lo tienes:
Benchmark
Aquí tenía un benchmark sencillo que ya está obsoleto. Lo he sustituido por un comprobación detallada en esta respuesta separada.
En Oracle 9.2+ (no 8i+ como se dijo originalmente), SQL Server 2005+, PostgreSQL 8.4+, DB2, Firebird 3.0+, Teradata, Sybase, Vertica:
Soportado por cualquier base de datos:
Pero hay que añadir la lógica para romper los empates:
La solución no es muy eficiente como señala Erwin, debido a la presencia de SubQs