MySQL ordenar antes de agrupar por

243

Aquí se pueden encontrar muchas preguntas similares, pero no creo que ninguna responda la pregunta adecuadamente.

Continuaré con la pregunta más popular actual y usaré su ejemplo si está bien.

La tarea en este caso es obtener la última publicación para cada autor en la base de datos.

La consulta de ejemplo produce resultados inutilizables ya que no siempre es la última publicación que se devuelve.

SELECT wp_posts.* FROM wp_posts
    WHERE wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
    GROUP BY wp_posts.post_author           
    ORDER BY wp_posts.post_date DESC

La respuesta actual aceptada es

SELECT
    wp_posts.*
FROM wp_posts
WHERE
    wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
HAVING wp_posts.post_date = MAX(wp_posts.post_date) <- ONLY THE LAST POST FOR EACH AUTHOR
ORDER BY wp_posts.post_date DESC

Lamentablemente, esta respuesta es sencilla y errónea, y en muchos casos produce resultados menos estables que la consulta original.

Mi mejor solución es usar una subconsulta del formulario

SELECT wp_posts.* FROM 
(
    SELECT * 
    FROM wp_posts
    ORDER BY wp_posts.post_date DESC
) AS wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author 

Mi pregunta es simple: ¿hay alguna forma de ordenar filas antes de agrupar sin recurrir a una subconsulta?

Editar : Esta pregunta fue una continuación de otra pregunta y los detalles de mi situación son ligeramente diferentes. Puede (y debe) asumir que también hay un wp_posts.id que es un identificador único para esa publicación en particular.

Rob Forrest
fuente
2
Como mencionó en los comentarios a las respuestas dadas, podría ser posible tener algunas publicaciones con la misma marca de tiempo. Si es así, proporcione un ejemplo con los datos y el resultado esperado. Y describa por qué espera este resultado. post_authory post_dateno son suficientes para obtener una fila única, por lo que tiene que haber más para obtener una fila única porpost_author
Sir Rufo
@SirRufo Tienes razón, he agregado una edición para ti.
Rob Forrest
There are plenty of similar questions to be found on here but I don't think that any answer the question adequately.Para eso están las recompensas.
ligereza corre en órbita
@LightnessRacesinOrbit, si la pregunta actual ya tiene una respuesta aceptada que, en mi opinión, es incorrecta, ¿qué sugeriría hacer?
Rob Forrest
1
Preguntándose por qué aceptó una respuesta que utiliza una subconsulta - cuando su pregunta claramente pregunta ... "" ¿Hay alguna forma de ordenar filas antes de agrupar sin recurrir a una subconsulta? "
TV-C-15

Respuestas:

373

Usar un ORDER BYen una subconsulta no es la mejor solución para este problema.

La mejor solución para obtener el max(post_date)autor es usar una subconsulta para devolver la fecha máxima y luego unirla a su tabla tanto en la post_authorfecha como en la fecha máxima.

La solución debería ser:

SELECT p1.* 
FROM wp_posts p1
INNER JOIN
(
    SELECT max(post_date) MaxPostDate, post_author
    FROM wp_posts
    WHERE post_status='publish'
       AND post_type='post'
    GROUP BY post_author
) p2
  ON p1.post_author = p2.post_author
  AND p1.post_date = p2.MaxPostDate
WHERE p1.post_status='publish'
  AND p1.post_type='post'
order by p1.post_date desc

Si tiene los siguientes datos de muestra:

CREATE TABLE wp_posts
    (`id` int, `title` varchar(6), `post_date` datetime, `post_author` varchar(3))
;

INSERT INTO wp_posts
    (`id`, `title`, `post_date`, `post_author`)
VALUES
    (1, 'Title1', '2013-01-01 00:00:00', 'Jim'),
    (2, 'Title2', '2013-02-01 00:00:00', 'Jim')
;

La subconsulta devolverá la fecha máxima y el autor de:

MaxPostDate | Author
2/1/2013    | Jim

Luego, ya que está volviendo a unir eso a la tabla, en ambos valores devolverá los detalles completos de esa publicación.

Ver SQL Fiddle con Demo .

Para ampliar mis comentarios sobre el uso de una subconsulta para devolver con precisión estos datos.

MySQL no lo obliga a GROUP BYtodas las columnas que incluye en la SELECTlista. Como resultado, si solo tiene GROUP BYuna columna pero devuelve 10 columnas en total, no hay garantía de post_authorque se devuelvan los otros valores de columna que pertenecen a la columna . Si la columna no está en unGROUP BY MySQL, elige qué valor se debe devolver.

El uso de la subconsulta con la función agregada garantizará que se devuelva el autor y la publicación correctos cada vez.

Como nota al margen, si bien MySQL le permite usar un ORDER BYen una subconsulta y le permite aplicar una GROUP BYa no todas las columnas de la SELECTlista, este comportamiento no está permitido en otras bases de datos, incluido SQL Server.

Taryn
fuente
44
Veo lo que has hecho allí, pero eso simplemente devuelve la fecha en que se realizó la publicación más reciente, no toda la fila de esa publicación más reciente.
Rob Forrest
1
@RobForrest eso es lo que hace la unión. Devuelve la fecha de publicación más reciente en la subconsulta por autor y luego se une a su wp_postsen ambas columnas para obtener la fila completa.
Taryn
77
@RobForrest Por un lado, cuando aplicas GROUP BYsolo a una columna, no hay garantía de que los valores en las otras columnas sean consistentemente correctos. Desafortunadamente, MySQL permite que este tipo de SELECT / GROUPing suceda que otros productos no. Dos, la sintaxis de usar un ORDER BYen una subconsulta mientras está permitida en MySQL no está permitida en otros productos de bases de datos, incluido SQL Server. Debe usar una solución que le devuelva el resultado adecuado cada vez que se ejecute.
Taryn
2
Para escalar, el compuesto INDEX(post_author, post_date)es importante.
Rick James
1
@ jtcotton63 Cierto, pero si pones post_idtu consulta interna, técnicamente también deberías agruparla , lo que probablemente sesgaría tus resultados.
Taryn
20

Su solución hace uso de un extensión de la cláusula GROUP BY que permite agrupar por algunos campos (en este caso, solo post_author):

GROUP BY wp_posts.post_author

y seleccione columnas no agregadas:

SELECT wp_posts.*

que no se enumeran en el grupo por cláusula, o que no se utilizan en una función agregada (MIN, MAX, COUNT, etc.).

Uso correcto de la extensión a la cláusula GROUP BY

Esto es útil cuando todos los valores de las columnas no agregadas son iguales para cada fila.

Por ejemplo, suponga que tiene una mesa GardensFlowers( namedel jardín, flowerque crece en el jardín):

INSERT INTO GardensFlowers VALUES
('Central Park',       'Magnolia'),
('Hyde Park',          'Tulip'),
('Gardens By The Bay', 'Peony'),
('Gardens By The Bay', 'Cherry Blossom');

y desea extraer todas las flores que crecen en un jardín, donde crecen varias flores. Luego debe usar una subconsulta, por ejemplo, podría usar esto:

SELECT GardensFlowers.*
FROM   GardensFlowers
WHERE  name IN (SELECT   name
                FROM     GardensFlowers
                GROUP BY name
                HAVING   COUNT(DISTINCT flower)>1);

Si necesita extraer todas las flores que son las únicas flores en el jardinero, puede cambiar la condición de TENER a HAVING COUNT(DISTINCT flower)=1, pero MySql también le permite usar esto:

SELECT   GardensFlowers.*
FROM     GardensFlowers
GROUP BY name
HAVING   COUNT(DISTINCT flower)=1;

sin subconsulta, no SQL estándar, pero más simple.

Uso incorrecto de la extensión a la cláusula GROUP BY

Pero, ¿qué sucede si SELECCIONA columnas no agregadas que no son iguales para cada fila? ¿Cuál es el valor que elige MySql para esa columna?

Parece que MySql siempre elige el PRIMER valor que encuentra.

Para asegurarse de que el primer valor que encuentra es exactamente el valor que desea, debe aplicar GROUP BYa una consulta ordenada, de ahí la necesidad de utilizar una subconsulta. No puedes hacerlo de otra manera.

Dado el supuesto de que MySql siempre elige la primera fila que encuentra, está ordenando correctamente las filas antes de GROUP BY. Pero desafortunadamente, si lee la documentación detenidamente, notará que esta suposición no es cierta.

Al seleccionar columnas no agregadas que no son siempre iguales, MySql es libre de elegir cualquier valor, por lo que el valor resultante que realmente muestra es indeterminado .

Veo que este truco para obtener el primer valor de una columna no agregada se usa mucho, y generalmente / casi siempre funciona, lo uso a veces también (bajo mi propio riesgo). Pero como no está documentado, no puede confiar en este comportamiento.

Este enlace (¡gracias ypercube!) El truco GROUP BY ha sido optimizado y muestra una situación en la que la misma consulta devuelve resultados diferentes entre MySql y MariaDB, probablemente debido a un motor de optimización diferente.

Entonces, si este truco funciona, es solo cuestión de suerte.

La respuesta aceptada en la otra pregunta me parece incorrecta:

HAVING wp_posts.post_date = MAX(wp_posts.post_date)

wp_posts.post_datees una columna no agregada, y su valor será oficialmente indeterminado, pero probablemente será el primero que se post_dateencuentre. Pero dado que el truco GROUP BY se aplica a una tabla desordenada, no está seguro de cuál es el primeropost_date encuentra.

Probablemente devolverá publicaciones que son las únicas publicaciones de un solo autor, pero incluso esto no siempre es seguro.

Una posible solución

Creo que esta podría ser una posible solución:

SELECT wp_posts.*
FROM   wp_posts
WHERE  id IN (
  SELECT max(id)
  FROM wp_posts
  WHERE (post_author, post_date) = (
    SELECT   post_author, max(post_date)
    FROM     wp_posts
    WHERE    wp_posts.post_status='publish'
             AND wp_posts.post_type='post'
    GROUP BY post_author
  ) AND wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
  GROUP BY post_author
)

En la consulta interna, estoy devolviendo la fecha máxima de publicación para cada autor. Entonces estoy teniendo en cuenta el hecho de que el mismo autor podría tener dos publicaciones al mismo tiempo, por lo que solo obtengo la ID máxima. Y luego estoy devolviendo todas las filas que tienen esas ID máximas. Podría hacerse más rápido utilizando combinaciones en lugar de la cláusula IN.

(Si está seguro de que IDsolo está aumentando, y si ID1 > ID2también significa eso post_date1 > post_date2, entonces la consulta podría hacerse mucho más simple, pero no estoy seguro de si este es el caso).

fthiella
fuente
Esa extension to GROUP Byes una lectura interesante, gracias por eso.
Rob Forrest
2
Un ejemplo donde falla: el truco GROUP BY ha sido optimizado
ypercubeᵀᴹ
Las columnas no agregadas en expresiones seleccionadas con GROUP BY ya no funcionan de manera predeterminada con MySQL 5.7: stackoverflow.com/questions/34115174/… . Lo que en mi humilde opinión es mucho más seguro y obliga a algunas personas a escribir consultas más eficaces.
rink.attendant.6
¿Esta respuesta no utiliza una subconsulta? ¿No está el Cartel original pidiendo una solución que NO use una subconsulta?
TV-C-15
1
@ TV-C-15 el problema es con el recurso de la subconsulta, y estoy explicando por qué el recurso de una subconsulta no funcionará. Incluso la respuesta aceptada usa una subconsulta, pero comienza a explicar por qué recurrir es una mala idea ( Usar un ORDER BY en una subconsulta no es la mejor solución para este problema )
fthiella
9

Lo que vas a leer es bastante chiflado, ¡así que no lo intentes en casa!

En SQL, en general, la respuesta a su pregunta es NO , pero debido al modo relajado de GROUP BY(mencionado por @bluefeet ), la respuesta es en MySQL.

Supongamos que tiene un índice BTREE en (post_status, post_type, post_author, post_date). ¿Cómo se ve el índice debajo del capó?

(post_status = 'publicar', post_type = 'publicar', post_author = 'usuario A', post_date = '2012-12-01') (post_status = 'publicar', post_type = 'publicar', post_author = 'usuario A', post_date = '2012-12-31') (post_status = 'publicar', post_type = 'post', post_author = 'usuario B', post_date = '2012-10-01') (post_status = 'publicar', post_type = ' post ', post_author =' usuario B ', post_date =' 2012-12-01 ')

Es decir, los datos se ordenan por todos esos campos en orden ascendente.

Cuando está haciendo un GROUP BYpor defecto, ordena los datos por el campo de agrupación ( post_authoren nuestro caso; post_status, post_type son requeridos por la WHEREcláusula) y si hay un índice coincidente, toma los datos de cada primer registro en orden ascendente. Esa es la consulta obtendrá lo siguiente (la primera publicación para cada usuario):

(post_status = 'publicar', post_type = 'publicar', post_author = 'usuario A', post_date = '2012-12-01') (post_status = 'publicar', post_type = 'publicar', post_author = 'usuario B', post_date = '2012-10-01')

Pero GROUP BYen MySQL le permite especificar el orden explícitamente. Y cuando solicite post_useren orden descendente, recorrerá nuestro índice en el orden opuesto, aún tomando el primer registro para cada grupo que en realidad es el último.

Es decir

...
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC

nos dará

(post_status = 'publicar', post_type = 'publicar', post_author = 'usuario B', post_date = '2012-12-01') (post_status = 'publicar', post_type = 'publicar', post_author = 'usuario A', post_date = '2012-12-31')

Ahora, cuando ordena los resultados de la agrupación por post_date, obtiene los datos que desea.

SELECT wp_posts.*
FROM wp_posts
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC
ORDER BY wp_posts.post_date DESC;

NB :

Esto no es lo que recomendaría para esta consulta en particular. En este caso, usaría una versión ligeramente modificada de lo que sugiere @bluefeet . Pero esta técnica puede ser muy útil. Echa un vistazo a mi respuesta aquí: recuperar el último registro de cada grupo

Errores : las desventajas del enfoque es que

  • el resultado de la consulta depende del índice, que está en contra del espíritu del SQL (los índices solo deberían acelerar las consultas);
  • index no sabe nada sobre su influencia en la consulta (usted u otra persona en el futuro podrían encontrar que el índice consume demasiado recursos y cambiarlo de alguna manera, rompiendo los resultados de la consulta, no solo su rendimiento)
  • Si no comprende cómo funciona la consulta, lo más probable es que olvide la explicación en un mes y la consulta los confundirá a usted y a sus colegas.

La ventaja es el rendimiento en casos difíciles. En este caso, el rendimiento de la consulta debe ser el mismo que en la consulta de @ bluefeet, debido a la cantidad de datos involucrados en la clasificación (todos los datos se cargan en una tabla temporal y luego se ordenan; por cierto, su consulta también requiere el (post_status, post_type, post_author, post_date)índice) .

Lo que sugeriría :

Como dije, esas consultas hacen que MySQL pierda tiempo clasificando cantidades potencialmente enormes de datos en una tabla temporal. En caso de que necesite paginación (es decir, LIMIT está involucrado), la mayoría de los datos incluso se descartan. Lo que haría es minimizar la cantidad de datos ordenados: es decir, ordenar y limitar un mínimo de datos en la subconsulta y luego volver a unirme a la tabla completa.

SELECT * 
FROM wp_posts
INNER JOIN
(
  SELECT max(post_date) post_date, post_author
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) p2 USING (post_author, post_date)
WHERE post_status='publish' AND post_type='post';

La misma consulta utilizando el enfoque descrito anteriormente:

SELECT *
FROM (
  SELECT post_id
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author DESC
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) as ids
JOIN wp_posts USING (post_id);

Todas esas consultas con sus planes de ejecución en SQLFiddle .

nuevo
fuente
Esa es una técnica interesante que tienes para ir allí. Dos cosas: usted dice que no intente esto en casa, ¿cuáles son las posibles dificultades? en segundo lugar, mencionas una versión ligeramente modificada de la respuesta de bluefeet, ¿cuál sería?
Rob Forrest
Gracias por eso, es interesante ver a alguien atacar el problema de una manera diferente. Como mi conjunto de datos no está cerca de sus 18M + filas, no creo que el rendimiento sea tan crucial como la mantenibilidad, así que creo que sus opciones posteriores probablemente sean más adecuadas. Me gusta la idea del límite en el interior de la subconsulta.
Rob Forrest
8

Prueba este. Simplemente obtenga la lista de las últimas fechas de publicación de cada autor . Eso es

SELECT wp_posts.* FROM wp_posts WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post' AND wp_posts.post_date IN(SELECT MAX(wp_posts.post_date) FROM wp_posts GROUP BY wp_posts.post_author) 
sanchitkhanna26
fuente
@ Rob Forrest, revisa mi solución. ¡Resuelve tu pregunta, con suerte!
sanchitkhanna26
1
Lo siento, no creo que eso funcione. Por ejemplo, si tanto el autor 1 como el autor 2 publican algo el 01/02/13 y luego el autor 2 publica algo nuevo el 08/02/13, se devolverán las 3 publicaciones. Sí, el campo de fecha y hora incluye la hora, por lo que la situación es menos probable, pero de ninguna manera está garantizada en un conjunto de datos lo suficientemente grande.
Rob Forrest
+1 por usar el post_date IN (select max(...) ...). Esto es más eficiente que hacer un grupo en una selección secundaria, consulte dev.mysql.com/doc/refman/5.6/en/subquery-optimization.html
Seaux
solo para aclarar, eso solo es más óptimo si tiene indexado post_author.
Seaux
1
IN ( SELECT ... )es mucho menos eficiente que el equivalente JOIN.
Rick James
3

No. No tiene sentido ordenar los registros antes de la agrupación, ya que la agrupación va a mutar el conjunto de resultados. La forma de subconsulta es la forma preferida. Si esto va demasiado lento, tendría que cambiar el diseño de su tabla, por ejemplo, almacenando la identificación de la última publicación para cada autor en una tabla separada, o introducir una columna booleana que indique para cada autor cuál de sus publicaciones es la última uno.

Dennisch
fuente
Dennish, ¿cómo respondería a los comentarios de Bluefeet de que este tipo de consulta no es la sintaxis SQL correcta y, por lo tanto, no es portátil en las plataformas de bases de datos? También existe la preocupación de que no hay garantía de que esto produzca los resultados correctos cada vez.
Rob Forrest
2

Solo use la función max y la función de grupo

    select max(taskhistory.id) as id from taskhistory
            group by taskhistory.taskid
            order by taskhistory.datum desc
Konstantin XFlash Stratigenas
fuente
3
¿Qué pasa si el que tiene la identificación más alta no es el publicado más recientemente? Un ejemplo de esto podría ser que el autor mantuvo su puesto en borrador durante un largo período de tiempo antes de publicarlo.
Rob Forrest
0

Solo para recapitular, la solución estándar utiliza una subconsulta no correlacionada y se ve así:

SELECT x.*
  FROM my_table x
  JOIN (SELECT grouping_criteria,MAX(ranking_criterion) max_n FROM my_table GROUP BY grouping_criteria) y
    ON y.grouping_criteria = x.grouping_criteria
   AND y.max_n = x.ranking_criterion;

Si está utilizando una versión antigua de MySQL, o un conjunto de datos bastante pequeño, puede utilizar el siguiente método:

SELECT x.*
  FROM my_table x
  LEFT
  JOIN my_table y
    ON y.joining_criteria = x.joining_criteria
   AND y.ranking_criteria < x.ranking_criteria
 WHERE y.some_non_null_column IS NULL;  
fresa
fuente
Cuando dices versión antigua, ¿en qué versión de MySQL funcionaría esto? Y lo siento, no, el conjunto de datos es bastante grande en mi ejemplo.
Rob Forrest
Funcionará (lentamente) en cualquier versión. Las versiones anteriores no pueden usar subconsultas.
Fresa
Sí, el método # 2 (la versión que he probado es de aquí ) no funcionará en un conjunto de datos grande (millones de filas), arroja un error de conexión perdido . El método n. ° 1 tarda unos 15 segundos en ejecutar una consulta. Inicialmente quería evitar el uso de consultas anidadas, pero esto me hizo reconsiderar. ¡Gracias!
aexl
@TheSexiestManinJamaica Sí. No ha cambiado mucho en 3.5 años. Suponiendo que una consulta es eficiente en sí misma, el tiempo que demora en ejecutarse depende en gran medida del tamaño del conjunto de datos, la disposición de los índices y el hardware disponible.
Fresa
-1

** Las subconsultas pueden tener un impacto negativo en el rendimiento cuando se usan con grandes conjuntos de datos **

Consulta original

SELECT wp_posts.*
FROM   wp_posts
WHERE  wp_posts.post_status = 'publish'
       AND wp_posts.post_type = 'post'
GROUP  BY wp_posts.post_author
ORDER  BY wp_posts.post_date DESC; 

Consulta modificada

SELECT p.post_status,
       p.post_type,
       Max(p.post_date),
       p.post_author
FROM   wp_posts P
WHERE  p.post_status = "publish"
       AND p.post_type = "post"
GROUP  BY p.post_author
ORDER  BY p.post_date; 

porque estoy usando maxen el select clause==> max(p.post_date)es posible evitar consultas de selección secundaria y ordenar por la columna máxima después del grupo por.

Guykaplan
fuente
1
De hecho, esto devuelve la fecha de publicación más reciente por autor, pero no hay garantía de que el resto de los datos devueltos se relacionen con la publicación con la fecha de publicación más reciente.
Rob Forrest
@RobForrest -> No entiendo por qué? es una buena idea elaborar su respuesta y simplemente rechazar reclamos. Según tengo entendido, los datos están garantizados para estar relacionados, ya que uso la cláusula where para filtrar los datos relacionados.
guykaplan
1
Hasta cierto punto, tiene toda la razón, cada uno de los 4 campos que está seleccionando se relacionará con esa fecha_después máxima, pero esto no responde a la pregunta que se le hizo. Por ejemplo, si agregó post_id, o el contenido de la publicación, entonces no se garantizaría que esas columnas sean del mismo registro que la fecha máxima. Para que su consulta anterior devuelva el resto de los detalles de la publicación, deberá ejecutar una segunda consulta. Si la pregunta era sobre encontrar la fecha de la publicación más reciente, entonces sí, su respuesta estaría bien.
Rob Forrest
@ guykaplan, las subconsultas no son lentas. El tamaño del conjunto de datos no importa. Depende de como lo uses. Ver percona.com/blog/2010/03/18/when-the-subselect-runs-faster
Pacerier
@Pacerier: el artículo de hecho muestra cómo puede obtener un beneficio en el rendimiento de las subconsultas, pero me encantaría verlo convertir el escenario dado para obtener un mejor rendimiento. y el tamaño de los datos es importante, nuevamente en el artículo dado que publicó está asumiendo que solo hay una tabla para trabajar. el tamaño de los datos no es por tamaño de fila, es por tamaño de complejidad. Dicho esto, si está trabajando con una subconsulta de tabla realmente grande (no hay muchas tablas involucradas) puede funcionar mucho mejor.
guykaplan 01 de
-4

Primero, no use * en select, afecta su rendimiento e impide el uso del grupo por y el orden por. Prueba esta consulta:

SELECT wp_posts.post_author, wp_posts.post_date as pdate FROM wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author           
ORDER BY pdate DESC

Cuando no especifica la tabla en ORDER BY, solo el alias, ordenarán el resultado de la selección.

Bruno Nardini
fuente
Ignore los select * 's, son por brevedad en este ejemplo. Tu respuesta es exactamente la misma que el primer ejemplo que di.
Rob Forrest
El alias no tiene ningún efecto sobre qué fila se devuelve ni la clasificación de los resultados.
Rob Forrest