Recuperando el último registro en cada grupo - MySQL


959

Hay una tabla messagesque contiene datos como se muestra a continuación:

Id   Name   Other_Columns
-------------------------
1    A       A_data_1
2    A       A_data_2
3    A       A_data_3
4    B       B_data_1
5    B       B_data_2
6    C       C_data_1

Si ejecuto una consulta select * from messages group by name, obtendré el resultado como:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

¿Qué consulta devolverá el siguiente resultado?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

Es decir, se debe devolver el último registro de cada grupo.

En la actualidad, esta es la consulta que uso:

SELECT
  *
FROM (SELECT
  *
FROM messages
ORDER BY id DESC) AS x
GROUP BY name

Pero esto parece muy ineficiente. ¿Alguna otra forma de lograr el mismo resultado?


2
vea la respuesta aceptada en stackoverflow.com/questions/1379565/… para una solución más eficiente
eyaler


77
¿Por qué no puede simplemente agregar DESC, es decir, seleccionar * del grupo de mensajes por nombre DESC
Kim Prince


2
@KimPrince ¡Parece que la respuesta que sugiere no hace lo que se espera! Acabo de probar su método y tomó la PRIMERA fila para cada grupo y ordené DESC. NO toma la última fila de cada grupo
Ayrat

Respuestas:


971

MySQL 8.0 ahora admite funciones de ventanas, como casi todas las implementaciones SQL populares. Con esta sintaxis estándar, podemos escribir las consultas más grandes por grupo:

WITH ranked_messages AS (
  SELECT m.*, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id DESC) AS rn
  FROM messages AS m
)
SELECT * FROM ranked_messages WHERE rn = 1;

A continuación se muestra la respuesta original que escribí para esta pregunta en 2009:


Escribo la solución de esta manera:

SELECT m1.*
FROM messages m1 LEFT JOIN messages m2
 ON (m1.name = m2.name AND m1.id < m2.id)
WHERE m2.id IS NULL;

En cuanto al rendimiento, una solución u otra puede ser mejor, dependiendo de la naturaleza de sus datos. Por lo tanto, debe probar ambas consultas y utilizar la que tenga un mejor rendimiento dada su base de datos.

Por ejemplo, tengo una copia del volcado de datos de agosto de StackOverflow . Lo usaré para la evaluación comparativa. Hay 1,114,357 filas en la Poststabla. Esto se ejecuta en MySQL 5.0.75 en mi Macbook Pro 2.40GHz.

Escribiré una consulta para encontrar la publicación más reciente para un ID de usuario determinado (el mío).

Primero usando la técnica mostrada por @Eric con el GROUP BYen una subconsulta:

SELECT p1.postid
FROM Posts p1
INNER JOIN (SELECT pi.owneruserid, MAX(pi.postid) AS maxpostid
            FROM Posts pi GROUP BY pi.owneruserid) p2
  ON (p1.postid = p2.maxpostid)
WHERE p1.owneruserid = 20860;

1 row in set (1 min 17.89 sec)

Incluso el EXPLAINanálisis lleva más de 16 segundos:

+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
| id | select_type | table      | type   | possible_keys              | key         | key_len | ref          | rows    | Extra       |
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL                       | NULL        | NULL    | NULL         |   76756 |             | 
|  1 | PRIMARY     | p1         | eq_ref | PRIMARY,PostId,OwnerUserId | PRIMARY     | 8       | p2.maxpostid |       1 | Using where | 
|  2 | DERIVED     | pi         | index  | NULL                       | OwnerUserId | 8       | NULL         | 1151268 | Using index | 
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
3 rows in set (16.09 sec)

Ahora produzca el mismo resultado de la consulta usando mi técnica con LEFT JOIN:

SELECT p1.postid
FROM Posts p1 LEFT JOIN posts p2
  ON (p1.owneruserid = p2.owneruserid AND p1.postid < p2.postid)
WHERE p2.postid IS NULL AND p1.owneruserid = 20860;

1 row in set (0.28 sec)

El EXPLAINanálisis muestra que ambas tablas pueden usar sus índices:

+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
| id | select_type | table | type | possible_keys              | key         | key_len | ref   | rows | Extra                                |
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
|  1 | SIMPLE      | p1    | ref  | OwnerUserId                | OwnerUserId | 8       | const | 1384 | Using index                          | 
|  1 | SIMPLE      | p2    | ref  | PRIMARY,PostId,OwnerUserId | OwnerUserId | 8       | const | 1384 | Using where; Using index; Not exists | 
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
2 rows in set (0.00 sec)

Aquí está el DDL para mi Postsmesa:

CREATE TABLE `posts` (
  `PostId` bigint(20) unsigned NOT NULL auto_increment,
  `PostTypeId` bigint(20) unsigned NOT NULL,
  `AcceptedAnswerId` bigint(20) unsigned default NULL,
  `ParentId` bigint(20) unsigned default NULL,
  `CreationDate` datetime NOT NULL,
  `Score` int(11) NOT NULL default '0',
  `ViewCount` int(11) NOT NULL default '0',
  `Body` text NOT NULL,
  `OwnerUserId` bigint(20) unsigned NOT NULL,
  `OwnerDisplayName` varchar(40) default NULL,
  `LastEditorUserId` bigint(20) unsigned default NULL,
  `LastEditDate` datetime default NULL,
  `LastActivityDate` datetime default NULL,
  `Title` varchar(250) NOT NULL default '',
  `Tags` varchar(150) NOT NULL default '',
  `AnswerCount` int(11) NOT NULL default '0',
  `CommentCount` int(11) NOT NULL default '0',
  `FavoriteCount` int(11) NOT NULL default '0',
  `ClosedDate` datetime default NULL,
  PRIMARY KEY  (`PostId`),
  UNIQUE KEY `PostId` (`PostId`),
  KEY `PostTypeId` (`PostTypeId`),
  KEY `AcceptedAnswerId` (`AcceptedAnswerId`),
  KEY `OwnerUserId` (`OwnerUserId`),
  KEY `LastEditorUserId` (`LastEditorUserId`),
  KEY `ParentId` (`ParentId`),
  CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`PostTypeId`) REFERENCES `posttypes` (`PostTypeId`)
) ENGINE=InnoDB;

8
De Verdad? ¿Qué pasa si tienes un montón de entradas? Por ejemplo, si está trabajando con un control de versiones interno, por ejemplo, y tiene una tonelada de versiones por archivo, el resultado de la unión sería enorme. ¿Alguna vez ha comparado el método de subconsulta con este? Tengo curiosidad por saber cuál ganaría, pero no lo suficiente como para no preguntarte primero.
Eric

2
Hice algunas pruebas. En una mesa pequeña (~ 300k registros, ~ 190k grupos, por lo que no son grupos masivos ni nada), las consultas empataron (8 segundos cada una).
Eric

1
@BillKarwin: Vea meta.stackexchange.com/questions/123017 , especialmente los comentarios debajo de la respuesta de Adam Rackis. Avíseme si desea reclamar su respuesta a la nueva pregunta.
Robert Harvey

3
@Tim, no, <=no ayudará si tiene una columna no única. Debe usar una columna única como desempate.
Bill Karwin

2
El rendimiento se degrada exponencialmente a medida que aumenta el número de filas o cuando los grupos se hacen más grandes. Por ejemplo, un grupo que consta de 5 fechas producirá 4 + 3 + 2 + 1 + 1 = 11 filas a través de la unión izquierda, de las cuales una fila se filtra al final. El rendimiento de la unión con resultados agrupados es casi lineal. Sus pruebas se ven defectuosas.
Salman A

148

UPD: 2017-03-31, la versión 5.7.5 de MySQL habilitó el conmutador ONLY_FULL_GROUP_BY habilitado de manera predeterminada (por lo tanto, las consultas GROUP BY no deterministas se deshabilitaron). Además, actualizaron la implementación de GROUP BY y la solución podría no funcionar como se esperaba incluso con el interruptor deshabilitado. Uno necesita verificar.

La solución de Bill Karwin anterior funciona bien cuando el recuento de elementos dentro de los grupos es bastante pequeño, pero el rendimiento de la consulta se vuelve malo cuando los grupos son bastante grandes, ya que la solución requiere n*n/2 + n/2solo IS NULLcomparaciones.

Hice mis pruebas en una tabla de 18684446filas InnoDB con 1182grupos. La tabla contiene resultados de pruebas para pruebas funcionales y tiene (test_id, request_id)como clave principal. Por lo tanto, test_ides un grupo y estaba buscando el último request_idpara cada uno test_id.

La solución de Bill ya se ha estado ejecutando durante varias horas en mi dell e4310 y no sé cuándo terminará aunque funcione en un índice de cobertura (por lo tanto, using indexen EXPLICAR).

Tengo un par de otras soluciones que se basan en las mismas ideas:

  • si el índice subyacente es el índice BTREE (que suele ser el caso), el (group_id, item_value)par más grande es el último valor dentro de cada uno group_id, ese es el primero para cada uno group_idsi recorremos el índice en orden descendente;
  • si leemos los valores que están cubiertos por un índice, los valores se leen en el orden del índice;
  • cada índice contiene implícitamente columnas de clave primaria agregadas a eso (es decir, la clave primaria está en el índice de cobertura). En las soluciones a continuación, opero directamente en la clave primaria, en su caso, solo tendrá que agregar columnas de clave principal en el resultado.
  • en muchos casos, es mucho más barato recopilar los identificadores de fila requeridos en el orden requerido en una subconsulta y unir el resultado de la subconsulta en la identificación. Dado que para cada fila en el resultado de la subconsulta, MySQL necesitará una búsqueda única basada en la clave primaria, la subconsulta se colocará primero en la unión y las filas se generarán en el orden de los identificadores en la subconsulta (si omitimos ORDER BY explícito para la unión)

3 maneras en que MySQL usa índices es un gran artículo para comprender algunos detalles.

Solución 1

Este es increíblemente rápido, toma alrededor de 0,8 segundos en mis 18M + filas:

SELECT test_id, MAX(request_id) AS request_id
FROM testresults
GROUP BY test_id DESC;

Si desea cambiar el orden a ASC, póngalo en una subconsulta, devuelva solo los identificadores y úselo como subconsulta para unirse al resto de las columnas:

SELECT test_id, request_id
FROM (
    SELECT test_id, MAX(request_id) AS request_id
    FROM testresults
    GROUP BY test_id DESC) as ids
ORDER BY test_id;

Este toma alrededor de 1,2 segundos en mis datos.

Solución 2

Aquí hay otra solución que toma alrededor de 19 segundos para mi mesa:

SELECT test_id, request_id
FROM testresults, (SELECT @group:=NULL) as init
WHERE IF(IFNULL(@group, -1)=@group:=test_id, 0, 1)
ORDER BY test_id DESC, request_id DESC

También devuelve las pruebas en orden descendente. Es mucho más lento ya que realiza un escaneo de índice completo, pero está aquí para darle una idea de cómo generar N filas máximas para cada grupo.

La desventaja de la consulta es que su caché de consultas no puede almacenar en caché su resultado.


Enlace a un volcado de sus tablas para que las personas puedan probarlo en sus plataformas.
Pacerier

3
La solución 1 no puede funcionar, no puede seleccionar request_id sin tener eso en grupo por cláusula,
giò

2
@ giò, esta es la respuesta tiene 5 años. Hasta MySQL 5.7.5 ONLY_FULL_GROUP_BY fue desactivado por defecto y esta solución trabajó fuera de la caja dev.mysql.com/doc/relnotes/mysql/5.7/en/... . Ahora no estoy seguro de si la solución aún funciona cuando deshabilita el modo, porque la implementación de GROUP BY ha cambiado.
nuevo el

Si quisieras ASC en la primera solución, ¿funcionaría si cambias MAX a MIN?
Jin

@JinIzzraeel, tiene MIN por defecto en la parte superior de cada grupo (es el orden del índice de cobertura): SELECT test_id, request_id FROM testresults GROUP BY test_id;devolvería el mínimo request_id para cada test_id.
newtover

102

Use su subconsulta para devolver la agrupación correcta, porque está a medio camino.

Prueba esto:

select
    a.*
from
    messages a
    inner join 
        (select name, max(id) as maxid from messages group by name) as b on
        a.id = b.maxid

Si no es así id, quieres el máximo de:

select
    a.*
from
    messages a
    inner join 
        (select name, max(other_col) as other_col 
         from messages group by name) as b on
        a.name = b.name
        and a.other_col = b.other_col

De esta manera, evita las subconsultas y / o pedidos correlacionados en sus subconsultas, que tienden a ser muy lentas / ineficientes.


1
Tenga en cuenta una advertencia para la solución con other_col: si esa columna no es única, puede recuperar varios registros con la misma name, si están vinculados max(other_col). Encontré esta publicación que describe una solución para mis necesidades, donde necesito exactamente un registro por name.
Eric Simonton

En algunas situaciones, solo puede usar esta solución, pero no la aceptada.
tom10271

En mi experiencia, ¡está agrupando toda la tabla de mensajes que tiende a ser lenta / ineficiente! En otras palabras, tenga en cuenta que la subconsulta requiere un escaneo completo de la tabla y se agrupa para arrancar ... a menos que su optimizador esté haciendo algo que el mío no está haciendo. Entonces, esta solución depende en gran medida de mantener toda la tabla en la memoria.
Timo

Esos se beneficiarían de INDEX(name, id)yINDEX(name, other_col)
Rick James

55

Llegué a una solución diferente, que es obtener las ID para la última publicación dentro de cada grupo, luego seleccionar de la tabla de mensajes usando el resultado de la primera consulta como argumento para una WHERE x INconstrucción:

SELECT id, name, other_columns
FROM messages
WHERE id IN (
    SELECT MAX(id)
    FROM messages
    GROUP BY name
);

No sé cómo funciona esto en comparación con algunas de las otras soluciones, pero funcionó espectacularmente para mi mesa con más de 3 millones de filas. (4 segundos de ejecución con más de 1200 resultados)

Esto debería funcionar tanto en MySQL como en SQL Server.


Solo asegúrese de tener un índice en (nombre, id).
Samuel Åslund

1
Mucho mejor que uno mismo
anwerj

Aprendí algo de usted que es un buen trabajo y esta consulta es más rápida
Humphrey

33

Solución por subconsulta Fiddle Link

select * from messages where id in
(select max(id) from messages group by Name)

Solución Por enlace de violín de condición de unión

select m1.* from messages m1 
left outer join messages m2 
on ( m1.id<m2.id and m1.name=m2.name )
where m2.id is null

La razón de esta publicación es dar solo el enlace de violín. El mismo SQL ya se proporciona en otras respuestas.


1
@AlexanderSuraphel mysql5.5 no está disponible en fiddle ahora, el enlace de violín se creó usando eso. Hoy en día, el violín admite mysql5.6, cambié la base de datos a mysql 5.6 y puedo construir un esquema y ejecutar el sql.
Vipin el

8

Un enfoque con considerable velocidad es el siguiente.

SELECT * 
FROM messages a
WHERE Id = (SELECT MAX(Id) FROM messages WHERE a.Name = Name)

Resultado

Id  Name    Other_Columns
3   A   A_data_3
5   B   B_data_2
6   C   C_data_1

Esto supone que idse ordena de la manera que lo necesita. En el caso general se necesita alguna otra columna.
Rick James

6

Aquí hay dos sugerencias. Primero, si mysql admite ROW_NUMBER (), es muy simple:

WITH Ranked AS (
  SELECT Id, Name, OtherColumns,
    ROW_NUMBER() OVER (
      PARTITION BY Name
      ORDER BY Id DESC
    ) AS rk
  FROM messages
)
  SELECT Id, Name, OtherColumns
  FROM messages
  WHERE rk = 1;

Supongo que por "último" te refieres al último en orden de identificación. De lo contrario, cambie la cláusula ORDER BY de la ventana ROW_NUMBER () en consecuencia. Si ROW_NUMBER () no está disponible, esta es otra solución:

En segundo lugar, si no es así, esta suele ser una buena forma de proceder:

SELECT
  Id, Name, OtherColumns
FROM messages
WHERE NOT EXISTS (
  SELECT * FROM messages as M2
  WHERE M2.Name = messages.Name
  AND M2.Id > messages.Id
)

En otras palabras, seleccione mensajes donde no haya un mensaje de identificación posterior con el mismo nombre.


8
MySQL no es compatible con ROW_NUMBER () o CTE.
Bill Karwin el

1
MySQL 8.0 (y MariaDB 10.2) ahora son compatibles con ROW_NUMBER()CTE.
Rick James

6

Todavía no he probado con DB grande, pero creo que esto podría ser más rápido que unir tablas:

SELECT *, Max(Id) FROM messages GROUP BY Name

14
Esto devuelve datos arbitrarios. En otras palabras, las columnas devueltas podrían no ser del registro con MAX (Id).
Daño

Útil para seleccionar el ID máximo de un conjunto de registros con la condición WHERE: "SELECT Max (Id) FROM Prod WHERE Pn = '" + Pn + "'" Devuelve el Id máximo de un conjunto de registros con el mismo Pn. En c # use reader.GetString (0) para obtener el resultado
Nicola

5

Aquí hay otra forma de obtener el último registro relacionado GROUP_CONCATcon el orden y SUBSTRING_INDEXseleccionar uno de los registros de la lista

SELECT 
  `Id`,
  `Name`,
  SUBSTRING_INDEX(
    GROUP_CONCAT(
      `Other_Columns` 
      ORDER BY `Id` DESC 
      SEPARATOR '||'
    ),
    '||',
    1
  ) Other_Columns 
FROM
  messages 
GROUP BY `Name` 

La consulta anterior agrupará a todos los Other_Columnsque están en el mismo Namegrupo y el uso ORDER BY id DESCse unirá a todos Other_Columnsen un grupo específico en orden descendente con el separador proporcionado en mi caso que he usado ||, el uso SUBSTRING_INDEXsobre esta lista elegirá el primero

Fiddle Demo


Tenga en cuenta que group_concat_max_lenlimita la cantidad de filas que puede manejar.
Rick James

5

Claramente, hay muchas maneras diferentes de obtener los mismos resultados, su pregunta parece ser cuál es una forma eficiente de obtener los últimos resultados en cada grupo en MySQL. Si está trabajando con grandes cantidades de datos y suponiendo que está usando InnoDB incluso con las últimas versiones de MySQL (como 5.7.21 y 8.0.4-rc), entonces puede que no haya una forma eficiente de hacerlo.

A veces necesitamos hacer esto con tablas con incluso más de 60 millones de filas.

Para estos ejemplos, usaré datos con solo alrededor de 1.5 millones de filas donde las consultas tendrían que encontrar resultados para todos los grupos en los datos. En nuestros casos reales, a menudo necesitaríamos devolver datos de aproximadamente 2,000 grupos (lo que hipotéticamente no requeriría examinar gran parte de los datos).

Usaré las siguientes tablas:

CREATE TABLE temperature(
  id INT UNSIGNED NOT NULL AUTO_INCREMENT, 
  groupID INT UNSIGNED NOT NULL, 
  recordedTimestamp TIMESTAMP NOT NULL, 
  recordedValue INT NOT NULL,
  INDEX groupIndex(groupID, recordedTimestamp), 
  PRIMARY KEY (id)
);

CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id)); 

La tabla de temperatura se rellena con aproximadamente 1,5 millones de registros aleatorios y con 100 grupos diferentes. El grupo seleccionado se rellena con esos 100 grupos (en nuestros casos, normalmente sería menos del 20% para todos los grupos).

Como estos datos son aleatorios, significa que varias filas pueden tener las mismas marcas de tiempo registradas. Lo que queremos es obtener una lista de todos los grupos seleccionados en orden de ID de grupo con la última marca de tiempo registrada para cada grupo, y si el mismo grupo tiene más de una fila coincidente como esa, entonces la última identificación coincidente de esas filas.

Si hipotéticamente MySQL tenía una función last () que devolvía valores de la última fila en una cláusula especial ORDER BY, entonces simplemente podríamos hacer:

SELECT 
  last(t1.id) AS id, 
  t1.groupID, 
  last(t1.recordedTimestamp) AS recordedTimestamp, 
  last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;

que solo necesitaría examinar unas 100 filas en este caso, ya que no utiliza ninguna de las funciones normales de GROUP BY. Esto se ejecutaría en 0 segundos y, por lo tanto, sería altamente eficiente. Tenga en cuenta que normalmente en MySQL veríamos una cláusula ORDER BY después de la cláusula GROUP BY, sin embargo, esta cláusula ORDER BY se usa para determinar el ORDER para la última función (), si fuera después de GROUP BY, estaría ordenando los GRUPOS. Si no hay una cláusula GROUP BY, los últimos valores serán los mismos en todas las filas devueltas.

Sin embargo, MySQL no tiene esto, así que echemos un vistazo a las diferentes ideas de lo que tiene y demostremos que ninguno de estos es eficiente.

Ejemplo 1

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT t2.id
  FROM temperature t2 
  WHERE t2.groupID = g.id
  ORDER BY t2.recordedTimestamp DESC, t2.id DESC
  LIMIT 1
);

Esto examinó 3,009,254 filas y tomó ~ 0.859 segundos en 5.7.21 y un poco más de tiempo en 8.0.4-rc

Ejemplo 2

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
INNER JOIN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
) t5 ON t5.id = t1.id;

Esto examinó 1,505,331 filas y tomó ~ 1.25 segundos en 5.7.21 y un poco más de tiempo en 8.0.4-rc

Ejemplo 3

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
WHERE t1.id IN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
)
ORDER BY t1.groupID;

Esto examinó 3,009,685 filas y tomó ~ 1.95 segundos en 5.7.21 y un poco más de tiempo en 8.0.4-rc

Ejemplo 4

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT max(t2.id)
  FROM temperature t2 
  WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
      SELECT max(t3.recordedTimestamp)
      FROM temperature t3 
      WHERE t3.groupID = g.id
    )
);

Esto examinó 6.137.810 filas y tomó ~ 2.2 segundos en 5.7.21 y un poco más de tiempo en 8.0.4-rc

Ejemplo 5

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
  SELECT 
    t2.id, 
    t2.groupID, 
    t2.recordedTimestamp, 
    t2.recordedValue, 
    row_number() OVER (
      PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
    ) AS rowNumber
  FROM selected_group g 
  INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;

Esto examinó 6.017.808 filas y tomó ~ 4.2 segundos en 8.0.4-rc

Ejemplo 6

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM (
  SELECT 
    last_value(t2.id) OVER w AS id, 
    t2.groupID, 
    last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp, 
    last_value(t2.recordedValue) OVER w AS recordedValue
  FROM selected_group g
  INNER JOIN temperature t2 ON t2.groupID = g.id
  WINDOW w AS (
    PARTITION BY t2.groupID 
    ORDER BY t2.recordedTimestamp, t2.id 
    RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  )
) t1
GROUP BY t1.groupID;

Esto examinó 6.017.908 filas y tomó ~ 17.5 segundos en 8.0.4-rc

Ejemplo 7

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2 
  ON t2.groupID = g.id 
  AND (
    t2.recordedTimestamp > t1.recordedTimestamp 
    OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
  )
WHERE t2.id IS NULL
ORDER BY t1.groupID;

Este me estaba tomando una eternidad, así que tuve que matarlo.


Este es un problema diferente. Y la solución es una gran consulta UNION ALL.
Paul Spiegel

@PaulSpiegel Supongo que estás bromeando sobre la enorme UNION ALL. Además del hecho de que uno necesitaría conocer todos los grupos seleccionados de antemano, y que con 2,000 grupos seleccionados sería una consulta increíblemente grande, funcionaría aún peor que el ejemplo más rápido anterior, así que no, eso no sería un solución.
Yoseph

Estoy absolutamente en serio. Lo he probado en el pasado con un par de cientos de grupos. Cuando necesita manejar vínculos en grandes grupos, UNION ALL es la única forma en MySQL de forzar un plan de ejecución óptimo. SELECT DISTINCT(groupID)es rápido y le dará todos los datos que necesita para construir dicha consulta. Debería estar bien con el tamaño de la consulta siempre que no exceda max_allowed_packet, que por defecto es de 4 MB en MySQL 5.7.
Paul Spiegel

5

veremos cómo puede usar MySQL para obtener el último registro en un grupo de registros. Por ejemplo, si tiene este conjunto de resultados de publicaciones.

id category_id post_title

1 1 Title 1

2 1 Title 2

3 1 Title 3

4 2 Title 4

5 2 Title 5

6 3 Title 6

Quiero poder obtener la última publicación en cada categoría, que son Título 3, Título 5 y Título 6. Para obtener las publicaciones por categoría, usará el teclado Grupo MySQL por.

select * from posts group by category_id

Pero los resultados que obtenemos de esta consulta son.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

El grupo by siempre devolverá el primer registro del grupo en el conjunto de resultados.

SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );

Esto devolverá las publicaciones con las ID más altas en cada grupo.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Referencia Haga clic aquí


4
SELECT 
  column1,
  column2 
FROM
  table_name 
WHERE id IN 
  (SELECT 
    MAX(id) 
  FROM
    table_name 
  GROUP BY column1) 
ORDER BY column1 ;

¿Podría explicar un poco su respuesta? ¿Por qué es preferible su consulta a la consulta original de Vijays?
Janfoeh

4

Aquí está mi solución:

SELECT 
  DISTINCT NAME,
  MAX(MESSAGES) OVER(PARTITION BY NAME) MESSAGES 
FROM MESSAGE;

Esto no devuelve el último mensaje por nombre. Y es solo una versión demasiado complicada de SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel

Además, esta formulación es extremadamente ineficiente.
Rick James

3

Prueba esto:

SELECT jos_categories.title AS name,
       joined .catid,
       joined .title,
       joined .introtext
FROM   jos_categories
       INNER JOIN (SELECT *
                   FROM   (SELECT `title`,
                                  catid,
                                  `created`,
                                  introtext
                           FROM   `jos_content`
                           WHERE  `sectionid` = 6
                           ORDER  BY `id` DESC) AS yes
                   GROUP  BY `yes`.`catid` DESC
                   ORDER  BY `yes`.`created` DESC) AS joined
         ON( joined.catid = jos_categories.id )  

3

Hola @Vijay Dev, si los mensajes de su tabla contienen Id, que es la clave primaria de incremento automático, para buscar la última base de registro en la clave primaria, su consulta debería leerse a continuación:

SELECT m1.* FROM messages m1 INNER JOIN (SELECT max(Id) as lastmsgId FROM messages GROUP BY Name) m2 ON m1.Id=m2.lastmsgId

Este es el más rápido que encontré
CORSAIR

3

Puede ver desde aquí también.

http://sqlfiddle.com/#!9/ef42b/9

PRIMERA SOLUCION

SELECT d1.ID,Name,City FROM Demo_User d1
INNER JOIN
(SELECT MAX(ID) AS ID FROM Demo_User GROUP By NAME) AS P ON (d1.ID=P.ID);

SEGUNDA SOLUCION

SELECT * FROM (SELECT * FROM Demo_User ORDER BY ID DESC) AS T GROUP BY NAME ;

3
SELECT * FROM table_name WHERE primary_key IN (SELECT MAX(primary_key) FROM table_name GROUP BY column_name )

3

** **

Hola, esta consulta podría ayudar:

** **

SELECT 
  *
FROM 
  message 

WHERE 
  `Id` IN (
    SELECT 
      MAX(`Id`) 
    FROM 
      message 
    GROUP BY 
      `Name`
  ) 
ORDER BY 
   `Id` DESC

2

¿Hay alguna forma de que podamos usar este método para eliminar duplicados en una tabla? El conjunto de resultados es básicamente una colección de registros únicos, por lo que si pudiéramos eliminar todos los registros que no están en el conjunto de resultados, ¿no tendríamos duplicados? Intenté esto pero mySQL me dio un error 1093.

DELETE FROM messages WHERE id NOT IN
 (SELECT m1.id  
 FROM messages m1 LEFT JOIN messages m2  
 ON (m1.name = m2.name AND m1.id < m2.id)  
 WHERE m2.id IS NULL)

¿Hay alguna manera de guardar la salida en una variable temporal y luego eliminarla de NOT IN (variable temporal)? @Bill gracias por una solución muy útil.

EDITAR: Creo que encontré la solución:

DROP TABLE IF EXISTS UniqueIDs; 
CREATE Temporary table UniqueIDs (id Int(11)); 

INSERT INTO UniqueIDs 
    (SELECT T1.ID FROM Table T1 LEFT JOIN Table T2 ON 
    (T1.Field1 = T2.Field1 AND T1.Field2 = T2.Field2 #Comparison Fields  
    AND T1.ID < T2.ID) 
    WHERE T2.ID IS NULL); 

DELETE FROM Table WHERE id NOT IN (SELECT ID FROM UniqueIDs);

2

La consulta a continuación funcionará bien según su pregunta.

SELECT M1.* 
FROM MESSAGES M1,
(
 SELECT SUBSTR(Others_data,1,2),MAX(Others_data) AS Max_Others_data
 FROM MESSAGES
 GROUP BY 1
) M2
WHERE M1.Others_data = M2.Max_Others_data
ORDER BY Others_data;

2

Si desea la última fila para cada uno Name, puede dar un número de fila a cada grupo de filas por Namey ordenar porId en orden descendente.

CONSULTA

SELECT t1.Id, 
       t1.Name, 
       t1.Other_Columns
FROM 
(
     SELECT Id, 
            Name, 
            Other_Columns,
    (
        CASE Name WHEN @curA 
        THEN @curRow := @curRow + 1 
        ELSE @curRow := 1 AND @curA := Name END 
    ) + 1 AS rn 
    FROM messages t, 
    (SELECT @curRow := 0, @curA := '') r 
    ORDER BY Name,Id DESC 
)t1
WHERE t1.rn = 1
ORDER BY t1.Id;

Violín de SQL


2

Qué tal esto:

SELECT DISTINCT ON (name) *
FROM messages
ORDER BY name, id DESC;

Tuve un problema similar (en postgresql tough) y en una tabla de registros de 1M. Esta solución toma 1.7s vs 44s producidos por el que tiene LEFT JOIN. En mi caso, tuve que filtrar el corresponsal de su campo de nombre contra valores NULL, lo que resultó en un rendimiento aún mejor en 0.2 segundos


1

Si realmente le preocupa el rendimiento, puede introducir una nueva columna en la tabla llamada IsLastInGroup de tipo BIT.

Póngalo en verdadero en las columnas que son las últimas y manténgalo con cada fila insertar / actualizar / eliminar. Las escrituras serán más lentas, pero te beneficiarás con las lecturas. Depende de su caso de uso y lo recomiendo solo si está centrado en la lectura.

Entonces su consulta se verá así:

SELECT * FROM Messages WHERE IsLastInGroup = 1

Algunas tablas en Moodle tienen una columna de bandera como esta.
Lawrence


0

Puede agrupar contando y también obtener el último elemento del grupo como:

SELECT 
    user,
    COUNT(user) AS count,
    MAX(id) as last
FROM request 
GROUP BY user

0

Espero que debajo de la consulta de Oracle pueda ayudar:

WITH Temp_table AS
(
    Select id, name, othercolumns, ROW_NUMBER() over (PARTITION BY name ORDER BY ID 
    desc)as rank from messages
)
Select id, name,othercolumns from Temp_table where rank=1

0

Otro enfoque :

Encuentre la propiedad con el máximo m2_price dentro de cada programa (n propiedades en 1 programa):

select * from properties p
join (
    select max(m2_price) as max_price 
    from properties 
    group by program_id
) p2 on (p.program_id = p2.program_id)
having p.m2_price = max_price
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.