sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Seleccionar la primera fila en cada grupo GROUP BY?

DISTINCT ON suele ser más simple y rápido para esto en PostgreSQL .
(Para obtener información sobre la optimización del rendimiento para determinadas cargas de trabajo, consulte a continuación).

SELECT DISTINCT ON (customer)
       id, customer, total
FROM   purchases
ORDER  BY customer, total DESC, id;

O más corto (si no es tan claro) con números ordinales de columnas de salida:

SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

Si total puede ser NULL (no le hará daño de ninguna manera, pero querrá hacer coincidir los índices existentes):

...
ORDER  BY customer, total DESC NULLS LAST, id;

Puntos principales

DISTINCT ON es una extensión PostgreSQL del estándar (donde solo DISTINCT en general SELECT la lista está definida).

Enumere cualquier cantidad de expresiones en DISTINCT ON cláusula, el valor de fila combinado define duplicados. El manual:

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.

Énfasis en negrita mío.

DISTINCT ON se puede combinar con ORDER BY . Expresiones principales en ORDER BY debe estar en el conjunto de expresiones en DISTINCT ON , pero puede reorganizar el orden entre ellos libremente. Ejemplo.
Puede agregar adicionales expresiones a ORDER BY para elegir una fila en particular de cada grupo de pares. O, como dice el manual:

El DISTINCT ON la(s) expresión(es) debe(n) coincidir con el ORDER BY más a la izquierda expresión(es). El ORDER BY La cláusula normalmente contendrá expresiones adicionales que determinan la precedencia deseada de las filas dentro de cada DISTINCT ON grupo.

Agregué id como último elemento para desempatar:
"Seleccione la fila con el id más pequeño de cada grupo que comparte el total más alto ."

Para ordenar los resultados de una manera que no esté de acuerdo con el orden de clasificación que determina el primero por grupo, puede anidar la consulta anterior en una consulta externa con otro ORDER BY . Ejemplo.

Si total puede ser NULL, muy probablemente quiere la fila con el mayor valor no nulo. Agregar NULLS LAST como demostrado. Ver:

  • ¿Ordenar por columna ASC, pero los valores NULL primero?

El SELECT lista no está limitado por expresiones en DISTINCT ON o ORDER BY de cualquier manera. (No es necesario en el caso simple anterior):

  • Usted no tiene que hacerlo incluir cualquiera de las expresiones en DISTINCT ON o ORDER BY .

  • Tu puedes incluir cualquier otra expresión en el SELECT lista. Esto es fundamental para reemplazar consultas mucho más complejas con subconsultas y funciones de agregación/ventana.

Probé con las versiones 8.3 a 13 de Postgres. Pero la característica ha estado allí al menos desde la versión 7.1, así que básicamente siempre.

Índice

El perfecto El índice de la consulta anterior sería un índice de varias columnas que abarcaría las tres columnas en secuencia coincidente y con orden de clasificación coincidente:

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

Puede ser demasiado especializado. Pero utilícelo si el rendimiento de lectura para la consulta en particular es crucial. Si tiene DESC NULLS LAST en la consulta, use 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 costo y el beneficio antes de crear índices personalizados para cada consulta. El potencial del índice anterior depende en gran medida de la distribución de datos .

El índice se usa porque entrega datos preordenados. En Postgres 9.2 o posterior, la consulta también se puede beneficiar de un análisis de solo índice si el índice es más pequeño que la tabla subyacente. Sin embargo, el índice debe escanearse en su totalidad.

Para pocos filas por cliente (alta cardinalidad en la columna customer ), esto es muy eficiente. Más aún si necesita una salida ordenada de todos modos. El beneficio se reduce con un número creciente de filas por cliente.
Idealmente, tiene suficiente work_mem para procesar el paso de clasificación involucrado en la RAM y no derramarlo en el disco. Pero generalmente establecer work_mem también alta puede tener efectos adversos. Considere SET LOCAL para consultas excepcionalmente grandes. Encuentra cuánto necesitas con EXPLAIN ANALYZE . Mención de "Disco: " en el paso de clasificación indica la necesidad de más:

  • Parámetro de configuración work_mem en PostgreSQL en Linux
  • Optimizar consulta simple usando ORDER BY fecha y texto

Para muchos filas por cliente (baja cardinalidad en la columna customer ), un escaneo de índice suelto (también conocido como "escaneo de omisión") sería (mucho) más eficiente, pero eso no está implementado hasta Postgres 14. (Se está desarrollando una implementación para escaneos de solo índice para Postgres 15. Consulte aquí y aquí).
Para ahora, existen técnicas de consulta más rápidas para sustituir esto. En particular, si tiene una mesa separada con clientes únicos, que es el caso de uso típico. Pero también si no:

  • SELECT DISTINCT es más lento de lo esperado en mi tabla en PostgreSQL
  • Optimizar la consulta GROUP BY para recuperar la fila más reciente por usuario
  • Optimizar consulta máxima grupal
  • Consulta las últimas N filas relacionadas por fila

Puntos de referencia

Ver respuesta separada.