Hay una tabla messages
que 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?
sql
mysql
group-by
greatest-n-per-group
Vijay Dev
fuente
fuente
Respuestas:
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:
A continuación se muestra la respuesta original que escribí para esta pregunta en 2009:
Escribo la solución de esta manera:
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
Posts
tabla. 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 BY
en una subconsulta:Incluso el
EXPLAIN
análisis lleva más de 16 segundos:Ahora produzca el mismo resultado de la consulta usando mi técnica con
LEFT JOIN
:El
EXPLAIN
análisis muestra que ambas tablas pueden usar sus índices:Aquí está el DDL para mi
Posts
mesa:fuente
<=
no ayudará si tiene una columna no única. Debe usar una columna única como desempate.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/2
soloIS NULL
comparaciones.Hice mis pruebas en una tabla de
18684446
filas InnoDB con1182
grupos. La tabla contiene resultados de pruebas para pruebas funcionales y tiene(test_id, request_id)
como clave principal. Por lo tanto,test_id
es un grupo y estaba buscando el últimorequest_id
para cada unotest_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 index
en EXPLICAR).Tengo un par de otras soluciones que se basan en las mismas ideas:
(group_id, item_value)
par más grande es el último valor dentro de cada unogroup_id
, ese es el primero para cada unogroup_id
si recorremos el índice en orden descendente;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:
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:
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:
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.
fuente
SELECT test_id, request_id FROM testresults GROUP BY test_id;
devolvería el mínimo request_id para cada test_id.Use su subconsulta para devolver la agrupación correcta, porque está a medio camino.
Prueba esto:
Si no es así
id
, quieres el máximo de:De esta manera, evita las subconsultas y / o pedidos correlacionados en sus subconsultas, que tienden a ser muy lentas / ineficientes.
fuente
other_col
: si esa columna no es única, puede recuperar varios registros con la mismaname
, si están vinculadosmax(other_col)
. Encontré esta publicación que describe una solución para mis necesidades, donde necesito exactamente un registro porname
.INDEX(name, id)
yINDEX(name, other_col)
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 IN
construcción: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.
fuente
Solución por subconsulta Fiddle Link
Solución Por enlace de violín de condición de unión
La razón de esta publicación es dar solo el enlace de violín. El mismo SQL ya se proporciona en otras respuestas.
fuente
Un enfoque con considerable velocidad es el siguiente.
Resultado
fuente
id
se ordena de la manera que lo necesita. En el caso general se necesita alguna otra columna.Aquí hay dos sugerencias. Primero, si mysql admite ROW_NUMBER (), es muy simple:
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:
En otras palabras, seleccione mensajes donde no haya un mensaje de identificación posterior con el mismo nombre.
fuente
ROW_NUMBER()
CTE.Todavía no he probado con DB grande, pero creo que esto podría ser más rápido que unir tablas:
fuente
Aquí hay otra forma de obtener el último registro relacionado
GROUP_CONCAT
con el orden ySUBSTRING_INDEX
seleccionar uno de los registros de la listaLa consulta anterior agrupará a todos los
Other_Columns
que están en el mismoName
grupo y el usoORDER BY id DESC
se unirá a todosOther_Columns
en un grupo específico en orden descendente con el separador proporcionado en mi caso que he usado||
, el usoSUBSTRING_INDEX
sobre esta lista elegirá el primeroFiddle Demo
fuente
group_concat_max_len
limita la cantidad de filas que puede manejar.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:
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:
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
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
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
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
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
Esto examinó 6.017.808 filas y tomó ~ 4.2 segundos en 8.0.4-rc
Ejemplo 6
Esto examinó 6.017.908 filas y tomó ~ 17.5 segundos en 8.0.4-rc
Ejemplo 7
Este me estaba tomando una eternidad, así que tuve que matarlo.
fuente
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 excedamax_allowed_packet
, que por defecto es de 4 MB en MySQL 5.7.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í
fuente
fuente
Aquí está mi solución:
fuente
SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME
.Prueba esto:
fuente
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:
fuente
Puede ver desde aquí también.
http://sqlfiddle.com/#!9/ef42b/9
PRIMERA SOLUCION
SEGUNDA SOLUCION
fuente
fuente
** **
Hola, esta consulta podría ayudar:
** **
fuente
¿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.
¿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:
fuente
La consulta a continuación funcionará bien según su pregunta.
fuente
Si desea la última fila para cada uno
Name
, puede dar un número de fila a cada grupo de filas porName
y ordenar porId
en orden descendente.CONSULTA
Violín de SQL
fuente
Qué tal esto:
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
fuente
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í:
fuente
fuente
Puede agrupar contando y también obtener el último elemento del grupo como:
fuente
Espero que debajo de la consulta de Oracle pueda ayudar:
fuente
Otro enfoque :
Encuentre la propiedad con el máximo m2_price dentro de cada programa (n propiedades en 1 programa):
fuente