¿Seleccionar la primera fila de cada grupo GROUP BY?

Como sugiere el título, me gustaría seleccionar la primera fila de cada conjunto de filas agrupadas con un GROUP BY.

Específicamente, si tengo un purchases tabla que se ve así:

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 compra más grandetotal) hecho por cada customer. Algo como esto:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY total DESC;

Rendimiento esperado:

PRIMERO (id) | cliente | PRIMERO (total) ---------- + ---------- + ------------- 1 | Joe | 5 2 | Sally | 3

preguntado el 26 de septiembre de 10 a las 22:09

ya que solo busca cada uno de los más grandes, ¿por qué no consultar MAX(total)? -

@ phil294 al consultar max (total) no asociará ese total con el valor 'id' de la fila en la que ocurrió. -

si alguien quiere group by + join rows data, consulte nuestra página, stackoverflow.com/questions/12558509/… -

18 Respuestas

En bases de datos que Soporta CTE y funciones de ventanas.:

WITH summary AS (
    SELECT p.id, 
           p.customer, 
           p.total, 
           ROW_NUMBER() OVER(PARTITION BY p.customer 
                                 ORDER BY p.total DESC) AS rank
      FROM PURCHASES p)
 SELECT *
   FROM summary
 WHERE rank = 1

Compatible con cualquier base de datos:

Pero necesitas agregar lógica para romper los lazos:

  SELECT MIN(x.id),  -- change to MAX if you want the highest
         x.customer, 
         x.total
    FROM PURCHASES x
    JOIN (SELECT p.customer,
                 MAX(total) AS max_total
            FROM PURCHASES p
        GROUP BY p.customer) y ON y.customer = x.customer
                              AND y.max_total = x.total
GROUP BY x.customer, x.total

contestado el 06 de mayo de 21 a las 18:05

Informix 12.x también admite funciones de ventana (aunque el CTE debe convertirse en una tabla derivada). Y Firebird 3.0 también admitirá funciones de Windows: un caballo sin nombre

ROW_NUMBER() OVER(PARTITION BY [...]) junto con algunas otras optimizaciones me ayudaron a reducir una consulta de 30 segundos a unos pocos milisegundos. ¡Gracias! (PostgreSQL 9.2) - Diana

Si hay varias compras con igualmente las más altas total para un cliente, el 1 consulta devuelve un arbitrario ganador (dependiendo de los detalles de la implementación; el id puede cambiar para cada ejecución!). Normalmente (no siempre) querrías una fila por cliente, definida por criterios adicionales como "el que tiene el menor id". Para solucionarlo, agregue id a ORDER BY lista de row_number(). Entonces obtienes el mismo resultado que con el 2nd consulta, que es muy ineficiente para este caso. Además, necesitaría otra subconsulta para cada columna adicional. - Erwin Brandstetter

BigQuery de Google también admite el comando ROW_NUMBER () de la primera consulta. Funcionó como un encanto para nosotros - Praxíteles

Tenga en cuenta que la primera versión con la función de ventana funciona a partir de la versión 3.25.0 de SQLite: sqlite.org/windowfunctions.html#history - BrianZ

In PostgreSQL esto es típicamente mas simple y rapido (más optimización del rendimiento a continuación):

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

O más corto (si no 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;

If total puede ser NULO (no dolerá de ninguna manera, pero querrá coincidir con 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 conjunto SELECT lista está definida).

Enumere cualquier número de expresiones en el 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.

El énfasis audaz es 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 añadir adicional expresiones para ORDER BY para elegir una fila en particular de cada grupo de pares. O como el manual lo pone:

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

yo añadí id como último elemento para romper los lazos:
"Elija la fila con el más pequeño id de cada grupo que comparte el más alto total."

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 otra ORDER BY. Ejemplo.

If total puede ser NULO, tu más probablemente quiere la fila con el mayor valor no nulo. Agregar NULLS LAST como se demostró. Ver:

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

  • Usted no tienes que incluir cualquiera de las expresiones en DISTINCT ON or ORDER BY.

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

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

Home

La Perfecto índice para la consulta anterior sería un índice de varias columnas abarcando las tres columnas en una secuencia coincidente y con un 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 usted 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.

Optimización de la eficacia / 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 distribución de datos.

El índice se utiliza porque entrega datos preordenados. En Postgres 9.2 o posterior, la consulta también puede beneficiarse de una escaneo 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 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, tienes suficiente work_mem para procesar el paso de clasificación involucrado en la RAM y no derramarlo en el disco. Pero en general estableciendo work_mem muy alto puede tener efectos adversos. Considerar 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:

Para muchas filas por cliente (cardinalidad baja en columna customer), La escaneo de índice suelto (también conocido como "omitir escaneo") sería (mucho) más eficiente, pero eso no se implementó hasta Postgres 13. (Se está desarrollando una implementación para escaneos de solo índice para Postgres 14. Consulte aquí y aquí.)
Por ahora, hay técnicas de consulta más rápidas para sustituir esto. En particular, si tiene una tabla separada con clientes únicos, que es el caso de uso típico. Pero también si no lo hace:

punto de referencia

Tenía un punto de referencia simple aquí que ahora está desactualizado. Lo reemplacé con un punto de referencia detallado en esta respuesta separada.

Respondido 20 ago 20, 00:08

Esta es una excelente respuesta para la mayoría de los tamaños de bases de datos, pero quiero señalar que a medida que se acerca a ~ millones de filas DISTINCT ON se vuelve extremadamente lento. La implementación siempre ordena toda la tabla y la escanea en busca de duplicados, ignorando todos los índices (incluso si ha creado el índice de varias columnas requerido). Ver expliqueextended.com/2009/05/03/postgresql-optimizing-distinct para una posible solución. - meekohi

Usar ordinales para "acortar el código" es una idea terrible. ¿Qué tal dejar los nombres de las columnas para que sean legibles? - KOTJMF

@KOTJMF: Le sugiero que elija sus preferencias personales. Demuestro ambas opciones para educar. La sintaxis abreviada puede ser útil para expresiones largas en el SELECT lista. - Erwin Brandstetter

@jangorecki: El punto de referencia original es de 2011, ya no tengo la configuración. Pero ya era hora de ejecutar pruebas con la página 9.4 y la página 9.5 de todos modos. Vea los detalles en la respuesta agregada.. ¿Podría agregar un comentario con el resultado de su instalación a continuación? - Erwin Brandstetter

@PirateApp: No desde lo alto de mi cabeza. DISTINCT ON solo es bueno para conseguir una fila por grupo de compañeros. - Erwin Brandstetter

punto de referencia

Probando los candidatos más interesantes con Postgres 9.4 y 9.5 con una tabla medio realista de 200k filas in purchases y 10k distintos customer_id (prom. 20 filas por cliente).

Para Postgres 9.5, realicé una segunda prueba con 2 clientes distintos. Vea abajo (prom. 2.3 filas por cliente).

Preparar

Mesa principal

CREATE TABLE purchases (
  id          serial
, customer_id int  -- REFERENCES customer
, total       int  -- could be amount of money in Cent
, some_column text -- to make the row bigger, more realistic
);

Yo uso un serial (Restricción PK agregada a continuación) y un número entero customer_id ya que esa es una configuración más típica. También añadido some_column para compensar normalmente más columnas.

Datos ficticios, PK, índice: una tabla típica también tiene algunas tuplas muertas:

INSERT INTO purchases (customer_id, total, some_column)    -- insert 200k rows
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,200000) g;

ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);

DELETE FROM purchases WHERE random() > 0.9; -- some dead rows

INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,20000) g;  -- add 20k to make it ~ 200k

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

VACUUM ANALYZE purchases;

customer tabla - para una consulta superior:

CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM   purchases
GROUP  BY 1
ORDER  BY 1;

ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);

VACUUM ANALYZE customer;

En mi segunda prueba para 9.5 utilicé la misma configuración, pero con random() * 100000 para generar customer_id para obtener solo unas pocas filas por customer_id.

Tamaños de objeto para mesa purchases

Generado con una consulta tomada de esta respuesta relacionada:

               what                | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
 core_relation_size                | 20496384 | 20 MB        |           102
 visibility_map                    |        0 | 0 bytes      |             0
 free_space_map                    |    24576 | 24 kB        |             0
 table_size_incl_toast             | 20529152 | 20 MB        |           102
 indexes_size                      | 10977280 | 10 MB        |            54
 total_size_incl_toast_and_indexes | 31506432 | 30 MB        |           157
 live_rows_in_text_representation  | 13729802 | 13 MB        |            68
 ------------------------------    |          |              |
 row_count                         |   200045 |              |
 live_tuples                       |   200045 |              |
 dead_tuples                       |    19955 |              |

Consultas

1. row_number() en CTE, (ver otra respuesta)

WITH cte AS (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   )
SELECT id, customer_id, total
FROM   cte
WHERE  rn = 1;

  1. row_number() en subconsulta (mi optimización)
SELECT id, customer_id, total
FROM   (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   ) sub
WHERE  rn = 1;

3. DISTINCT ON (ver otra respuesta)

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

4. rCTE con LATERAL subconsultamire aquí)

WITH RECURSIVE cte AS (
   (  -- parentheses required
   SELECT id, customer_id, total
   FROM   purchases
   ORDER  BY customer_id, total DESC
   LIMIT  1
   )
   UNION ALL
   SELECT u.*
   FROM   cte c
   ,      LATERAL (
      SELECT id, customer_id, total
      FROM   purchases
      WHERE  customer_id > c.customer_id  -- lateral reference
      ORDER  BY customer_id, total DESC
      LIMIT  1
      ) u
   )
SELECT id, customer_id, total
FROM   cte
ORDER  BY customer_id;

5. customer mesa con LATERAL (mire aquí)

SELECT l.*
FROM   customer c
,      LATERAL (
   SELECT id, customer_id, total
   FROM   purchases
   WHERE  customer_id = c.customer_id  -- lateral reference
   ORDER  BY total DESC
   LIMIT  1
   ) l;

6. array_agg() con ORDER BY (ver otra respuesta)

SELECT (array_agg(id ORDER BY total DESC))[1] AS id
     , customer_id
     , max(total) AS total
FROM   purchases
GROUP  BY customer_id;

Resultados

Tiempo de ejecución de las consultas anteriores con EXPLAIN ANALYZE (y todas las opciones off), lo mejor de 5 carreras.

Todos las consultas utilizaron un Escaneo de solo índice on purchases2_3c_idx (entre otros pasos). Algunos de ellos solo por el tamaño más pequeño del índice, otros de manera más efectiva.

A. Postgres 9.4 con 200k filas y ~ 20 por customer_id

1. 273.274 ms  
2. 194.572 ms  
3. 111.067 ms  
4.  92.922 ms  
5.  37.679 ms  -- winner
6. 189.495 ms

B. Lo mismo con Postgres 9.5

1. 288.006 ms
2. 223.032 ms  
3. 107.074 ms  
4.  78.032 ms  
5.  33.944 ms  -- winner
6. 211.540 ms  

C.Igual que B., pero con ~ 2.3 filas por customer_id

1. 381.573 ms
2. 311.976 ms
3. 124.074 ms  -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms

Benchmarks relacionados

Aquí hay uno nuevo por "ogr" probando con 10 millones de filas y 60 "clientes" únicos on postgres 11.5 (vigente a septiembre de 2019). Los resultados siguen en línea con lo que hemos visto hasta ahora:

Punto de referencia original (desactualizado) de 2011

Ejecuté tres pruebas con PostgreSQL 9.1 en una tabla de la vida real de 65579 filas e índices de árbol b de una sola columna en cada una de las tres columnas involucradas y tomó el mejor Tiempo de ejecución del de 5 carreras.
Comparando @OMGPonies ' primera consultaA) al arriba DISTINCT ON solución (B):

  1. Seleccione toda la tabla, resulta en 5958 filas en este caso.
A: 567.218 ms
B: 386.673 ms
  1. Condición de uso WHERE customer BETWEEN x AND y resultando en 1000 filas.
A: 249.136 ms
B:  55.111 ms
  1. Seleccione un solo cliente con WHERE customer = x.
A:   0.143 ms
B:   0.072 ms

Misma prueba repetida con el índice descrito en la otra respuesta

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

1A: 277.953 ms  
1B: 193.547 ms

2A: 249.796 ms -- special index not used  
2B:  28.679 ms

3A:   0.120 ms  
3B:   0.048 ms

Respondido 20 ago 20, 00:08

Gracias por un gran punto de referencia. Me preguntaba si preguntando Eventos datos donde tiene una marca de tiempo en lugar de total se beneficiaría del nuevo índice BRIN. Potencialmente, esto puede acelerar las consultas temporales. - Jangorecki

@jangorecki: Cualquiera enorme La tabla con datos ordenados físicamente puede beneficiarse de un índice BRIN. - Erwin Brandstetter

@ErwinBrandstetter En el 2. row_number() y 5. customer table with LATERAL ejemplos, ¿qué asegura que la identificación sea la más pequeña? - Artem Nóvikov

@ArtemNovikov: Nada. El objetivo es recuperar, por customer_id la fila con el más alto total. Es una coincidencia engañosa en los datos de prueba de la pregunta que el id en las filas seleccionadas pasa a ser también el más pequeño por customer_id. - Erwin Brandstetter

@ArtemNovikov: para permitir análisis de solo índice. - Erwin Brandstetter

Esto es común problema, que ya ha sido probado y altamente soluciones optimizadas. Personalmente prefiero el Solución de unión a la izquierda por Bill Karwin (El publicación original con muchas otras soluciones).

Tenga en cuenta que, sorprendentemente, se pueden encontrar un montón de soluciones a este problema común en una de las fuentes más oficiales, Manual de MySQL! Ver Ejemplos de consultas comunes: Las filas que contienen el máximo grupal de una determinada columna.

Respondido 04 Jul 18, 11:07

¿Cómo es el manual de MySQL de alguna manera "oficial" para las preguntas de Postgres / SQLite (sin mencionar SQL)? Además, para ser claros, el DISTINCT ON La versión es mucho más corta, más simple y generalmente funciona mejor en Postgres que las alternativas con un LEFT JOIN o semi-anti-unión con NOT EXISTS. También está "bien probado". - Erwin Brandstetter

Además de lo que escribió Erwin, diría que usar una función de ventana (que es una funcionalidad común de SQL hoy en día) es casi siempre más rápido que usar una combinación con una tabla derivada. un caballo sin nombre

Grandes referencias. No sabía que esto se llamaba el mayor problema de n por grupo. Gracias. - david mann

En un caso de dos campos de orden que probé, la "solución de combinación izquierda de Bill Karwin" dio un rendimiento deficiente. Ver mi comentario a continuación stackoverflow.com/a/8749095/684229 - johnny wong

@reinierpost La pregunta en realidad pide el mayor n por grupo. Op solicitó el primer registro para la "compra más grande (total)", por lo que primero significa la más grande en ese campo. Esta es una gran diferencia de simplemente preguntar "Quiero que cualquier registro aparezca primero en un conjunto desordenado". Y como nota al margen, si no especifica el orden en SQL, se supone que los registros pueden devolverse en cualquier orden. SQL no ofrece garantía sobre un pedido predeterminado. Y el pedido que se devuelve incluso podría cambiar entre solicitudes: es un detalle del proveedor. - jameshoux

En Postgres puedes usar array_agg Me gusta esto:

SELECT  customer,
        (array_agg(id ORDER BY total DESC))[1],
        max(total)
FROM purchases
GROUP BY customer

Esto te dará la id de la compra más grande de cada cliente.

Algunas cosas a tener en cuenta:

  • array_agg es una función agregada, por lo que funciona con GROUP BY.
  • array_agg le permite especificar un orden limitado a sí mismo, por lo que no restringe la estructura de toda la consulta. También existe una sintaxis para ordenar los NULL, si necesita hacer algo diferente al predeterminado.
  • Una vez que construimos la matriz, tomamos el primer elemento. (Las matrices de Postgres tienen un índice 1, no un índice 0).
  • Podrías usar array_agg de forma similar para la tercera columna de salida, pero max(total) es más simple.
  • Diferente a la DISTINCT ON, utilizando array_agg te permite mantener tu GROUP BY, en caso de que lo desee por otras razones.

Respondido 27 ago 14, 19:08

La solución no es muy eficiente como apunta Erwin, debido a la presencia de SubQs

select * from purchases p1 where total in
(select max(total) from purchases where p1.customer=customer) order by total desc;

Respondido el 17 de junio de 13 a las 21:06

Gracias, sí, de acuerdo con usted, la unión entre subq y consulta externa en realidad lleva más tiempo. "En" no será un problema aquí, ya que subq dará como resultado solo una fila. Por cierto, ¿a qué error de sintaxis estás apuntando? - cosmos

ohh .. solía "Teradata" .. editado ahora .. sin embargo, no es necesario romper los lazos aquí, ya que es necesario encontrar el total más alto para cada cliente .. - cosmos

¿Sabe que obtiene varias filas para un solo cliente en caso de empate? Si eso es deseable depende de los requisitos exactos. Normalmente no lo es. Para la pregunta que nos ocupa, el título es bastante claro. - Erwin Brandstetter

Esto no está claro a partir de la pregunta, si el mismo cliente tiene una compra = Max para 2 ID diferentes, creo que deberíamos mostrar ambos. - cosmos

La consulta:

SELECT purchases.*
FROM purchases
LEFT JOIN purchases as p 
ON 
  p.customer = purchases.customer 
  AND 
  purchases.total < p.total
WHERE p.total IS NULL

¡CÓMO FUNCIONA! (He estado allí)

Queremos asegurarnos de tener solo el total más alto para cada compra.


Algunas cosas teóricas (omita esta parte si solo desea comprender la consulta)

Sea Total una función T (cliente, id) donde devuelve un valor dado el nombre y el id. Para demostrar que el total dado (T (cliente, id)) es el más alto, tenemos que demostrar que queremos probar

  • ∀x T (cliente, id)> T (cliente, x) (este total es mayor que todos los demás totales para ese cliente)

OR

  • ¬∃x T (cliente, id) <T (cliente, x) (no existe un total más alto para ese cliente)

El primer enfoque necesitará que obtengamos todos los registros de ese nombre que realmente no me gusta.

El segundo necesitará una forma inteligente de decir que no puede haber un récord más alto que este.


Volver a SQL

Si dejamos se une a la tabla en el nombre y el total es menor que la tabla unida:

LEFT JOIN purchases as p 
ON 
p.customer = purchases.customer 
AND 
purchases.total < p.total

nos aseguramos de que todos los registros que tengan otro registro con el total más alto para el mismo usuario se unan:

+--------------+---------------------+-----------------+------+------------+---------+
| purchases.id |  purchases.customer | purchases.total | p.id | p.customer | p.total |
+--------------+---------------------+-----------------+------+------------+---------+
|            1 | Tom                 |             200 |    2 | Tom        |     300 |
|            2 | Tom                 |             300 |      |            |         |
|            3 | Bob                 |             400 |    4 | Bob        |     500 |
|            4 | Bob                 |             500 |      |            |         |
|            5 | Alice               |             600 |    6 | Alice      |     700 |
|            6 | Alice               |             700 |      |            |         |
+--------------+---------------------+-----------------+------+------------+---------+

Eso nos ayudará a filtrar el total más alto para cada compra sin necesidad de agrupar:

WHERE p.total IS NULL
    
+--------------+----------------+-----------------+------+--------+---------+
| purchases.id | purchases.name | purchases.total | p.id | p.name | p.total |
+--------------+----------------+-----------------+------+--------+---------+
|            2 | Tom            |             300 |      |        |         |
|            4 | Bob            |             500 |      |        |         |
|            6 | Alice          |             700 |      |        |         |
+--------------+----------------+-----------------+------+--------+---------+

Y esa es la respuesta que necesitamos.

Respondido 01 ago 20, 22:08

En SQL Server puede hacer esto:

SELECT *
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY customer
ORDER BY total DESC) AS StRank, *
FROM Purchases) n
WHERE StRank = 1

Explicación: aquí Agrupar por se realiza sobre la base del cliente y luego lo ordena por total, luego a cada grupo se le asigna un número de serie como StRank y sacamos el primer cliente cuyo StRank es 1

Respondido el 29 de diciembre de 18 a las 16:12

¡Gracias! Esto funcionó perfectamente y fue muy fácil de entender e implementar. - ruohola

Yo uso de esta manera (solo postgresql): https://wiki.postgresql.org/wiki/First/last_%28aggregate%29

-- Create a function that always returns the first non-NULL item
CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

-- Create a function that always returns the last non-NULL item
CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $2;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.last (
        sfunc    = public.last_agg,
        basetype = anyelement,
        stype    = anyelement
);

Entonces tu ejemplo debería funcionar casi como es:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY FIRST(total) DESC;

AVISO: ignora las filas NULL


Edición 1: use la extensión de postgres en su lugar

Ahora uso de esta manera: http://pgxn.org/dist/first_last_agg/

Para instalar en ubuntu 14.04:

apt-get install postgresql-server-dev-9.3 git build-essential -y
git clone git://github.com/wulczer/first_last_agg.git
cd first_last_app
make && sudo make install
psql -c 'create extension first_last_agg'

Es una extensión de postgres que le brinda la primera y la última función; aparentemente más rápido que la forma anterior.


Edición 2: ordenar y filtrar

Si usa funciones agregadas (como estas), puede ordenar los resultados, sin la necesidad de tener los datos ya ordenados:

http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-AGGREGATES

Entonces, el ejemplo equivalente, con ordenar sería algo como:

SELECT first(id order by id), customer, first(total order by id)
  FROM purchases
 GROUP BY customer
 ORDER BY first(total);

Por supuesto, puede ordenar y filtrar según lo considere adecuado dentro del agregado; es una sintaxis muy poderosa.

respondido 10 mar '15, 22:03

Utilizando este enfoque de función personalizada también. Suficientemente universal y simple. ¿Por qué complicar las cosas, es esta solución significativamente menos eficaz que otras? - Serguéi Shcherbakov

Utiliza ARRAY_AGG función para PostgreSQL, U-SQL, IBM DB2y SQL de BigQuery de Google:

SELECT customer, (ARRAY_AGG(id ORDER BY total DESC))[1], MAX(total)
FROM purchases
GROUP BY customer

Respondido 04 Abr '19, 22:04

Solucion muy rapida

SELECT a.* 
FROM
    purchases a 
    JOIN ( 
        SELECT customer, min( id ) as id 
        FROM purchases 
        GROUP BY customer 
    ) b USING ( id );

y realmente muy rápido si la tabla está indexada por id:

create index purchases_id on purchases (id);

Respondido 02 ago 16, 22:08

La cláusula USING es muy estándar. Es solo que algunos sistemas de bases de datos menores no lo tienen. - Holger Jacobs

Esto no encuentra la compra de los clientes con el mayor total: johnny wong

Soportes Snowflake / Teradata QUALIFY cláusula que funciona como HAVING para funciones en ventana:

SELECT id, customer, total
FROM PURCHASES
QUALIFY ROW_NUMBER() OVER(PARTITION BY p.customer ORDER BY p.total DESC) = 1

respondido 17 nov., 19:21

La solución aceptada de OMG Ponies "Compatible con cualquier base de datos" tiene buena velocidad según mi prueba.

Aquí proporciono un mismo enfoque, pero una solución de base de datos más completa y limpia. Se consideran los lazos (suponga que desea obtener solo una fila para cada cliente, incluso varios registros para el total máximo por cliente), y se seleccionarán otros campos de compra (por ejemplo, purchase_payment_id) para las filas coincidentes reales en la tabla de compras.

Compatible con cualquier base de datos:

select * from purchase
join (
    select min(id) as id from purchase
    join (
        select customer, max(total) as total from purchase
        group by customer
    ) t1 using (customer, total)
    group by customer
) t2 using (id)
order by customer

Esta consulta es razonablemente rápida, especialmente cuando hay un índice compuesto como (cliente, total) en la tabla de compras.

Observación:

  1. t1, t2 son alias de subconsultas que podrían eliminarse según la base de datos.

  2. Advertencia: el using (...) La cláusula actualmente no es compatible con MS-SQL y Oracle db a partir de esta edición en enero de 2017. Debe expandirla usted mismo para, por ejemplo, on t2.id = purchase.id etc. La sintaxis USING funciona en SQLite, MySQL y PostgreSQL.

Respondido el 11 de enero de 17 a las 10:01

En PostgreSQL, otra posibilidad es utilizar el first_value función de ventana en combinación con SELECT DISTINCT:

select distinct customer_id,
                first_value(row(id, total)) over(partition by customer_id order by total desc, id)
from            purchases;

Creé un compuesto (id, total), por lo que ambos valores son devueltos por el mismo agregado. Por supuesto, siempre puedes postularte first_value() dos veces.

Respondido el 09 de diciembre de 19 a las 12:12

Para su información, en DB2 no necesitamos una declaración de "fila". - Mateo Santz

De esta manera funciona para mí:

SELECT article, dealer, price
FROM   shop s1
WHERE  price=(SELECT MAX(s2.price)
              FROM shop s2
              WHERE s1.article = s2.article
              GROUP BY s2.article)
ORDER BY article;

Seleccione el precio más alto en cada artículo

Respondido 17 Jul 20, 04:07

Si hay 2 artículos iguales con el mismo precio, aquí se seleccionarán ambos. Entonces, si la intención era recuperar una fila por grupo por cláusula, esto no funcionará. Pero si la intención es que todas las filas cumplan con el criterio de precio máximo, está bien. - Por quéGeeEx

  • Si desea seleccionar cualquier fila (por alguna condición específica) del conjunto de filas agregadas.

  • Si desea utilizar otro (sum/avg) función de agregación además de max/min. Por lo tanto, no puedes usar la pista con DISTINCT ON

Puede usar la siguiente subconsulta:

SELECT  
    (  
       SELECT **id** FROM t2   
       WHERE id = ANY ( ARRAY_AGG( tf.id ) ) AND amount = MAX( tf.amount )   
    ) id,  
    name,   
    MAX(amount) ma,  
    SUM( ratio )  
FROM t2  tf  
GROUP BY name

Puedes reemplazar amount = MAX( tf.amount ) con cualquier condición que desee con una restricción: esta subconsulta no debe devolver más de una fila

Pero si quieres hacer esas cosas, probablemente estés buscando funciones de ventana

Respondido el 28 de Septiembre de 18 a las 15:09

Para SQl Server, la forma más eficiente es:

with
ids as ( --condition for split table into groups
    select i from (values (9),(12),(17),(18),(19),(20),(22),(21),(23),(10)) as v(i) 
) 
,src as ( 
    select * from yourTable where  <condition> --use this as filter for other conditions
)
,joined as (
    select tops.* from ids 
    cross apply --it`s like for each rows
    (
        select top(1) * 
        from src
        where CommodityId = ids.i 
    ) as tops
)
select * from joined

y no olvide crear un índice agrupado para las columnas usadas

Respondido el 18 de enero de 19 a las 11:01

Mi enfoque a través de la función de ventana dbviolín:

  1. Asignar row_number en cada grupo: row_number() over (partition by agreement_id, order_id ) as nrow
  2. Tome solo la primera fila en el grupo: filter (where nrow = 1)
with intermediate as (select 
 *,
 row_number() over ( partition by agreement_id, order_id ) as nrow,
 (sum( suma ) over ( partition by agreement_id, order_id ))::numeric( 10, 2) as order_suma,
from <your table>)

select 
  *,
  sum( order_suma ) filter (where nrow = 1) over (partition by agreement_id)
from intermediate

contestado el 13 de mayo de 21 a las 14:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.