MYSQL muestra filas incorrectas cuando se utiliza GROUP BY
-
13-09-2019 - |
Pregunta
Tengo dos tablas:
article('id', 'ticket_id', 'incoming_time', 'to', 'from', 'message')
ticket('id', 'queue_id')
donde las entradas representan un hilo de correos electrónicos entre clientes y personal de apoyo, y los artículos son los mensajes individuales que componen un hilo.
Estoy tratando de encontrar el artículo con el mayor tiempo entrante (expresada como una marca de tiempo Unix) para cada ticket_id, y esta es la consulta actualmente estoy usando:
SELECT article.* , MAX(article.incoming_time) as maxtime
FROM ticket, article
WHERE ticket.id = article.ticket_id
AND ticket.queue_id = 1
GROUP BY article.ticket_id
Por ejemplo,
:article:
id --- ticket_id --- incoming_time --- to ------- from ------- message --------
11 1 1234567 help@ client@ I need help...
12 1 1235433 client@ help@ How can we help?
13 1 1240321 help@ client@ Want food!
...
:ticket:
id --- queue_id
1 1
...
Sin embargo, el resultado parece ser la fila con el artículo más pequeño Identificación del lugar de lo que estoy buscando que es el artículo con el máximo de tiempo entrante.
Cualquier consejo sería muy apreciado!
Solución
Esto es un obstáculo clásico que la mayoría de los programadores de MySQL chocan.
- Usted tiene una
ticket_id
columna que es el argumento paraGROUP BY
. valores distintos de esta columna definen los grupos. - Usted tiene una
incoming_time
columna que es el argumento paraMAX()
. El mayor valor en esta columna sobre las filas en cada grupo se devuelve como el valor deMAX()
. - Usted tiene todas las demás columnas de artículo de la tabla. Los valores devueltos de estas columnas son arbitrarias, no de la misma fila en la que se produce el valor
MAX()
.
La base de datos no puede deducir que desea valores de la misma fila en la que se produce el valor máximo.
Piense en los siguientes casos:
-
Hay múltiples filas en las que se produce el mismo valor max. ¿Qué fila se debe utilizar para mostrar las columnas de
article.*
? -
Usted escribe una consulta que devuelve tanto el
MIN()
y laMAX()
. Esto es legal, pero que fila debearticle.*
espectáculo?SELECT article.* , MIN(article.incoming_time), MAX(article.incoming_time) FROM ticket, article WHERE ticket.id = article.ticket_id AND ticket.queue_id = 1 GROUP BY article.ticket_id
-
Se utiliza una función de agregado como
AVG()
oSUM()
, donde ninguna fila tiene ese valor. ¿Cómo es la base de datos de adivinar qué fila para que aparezca?SELECT article.* , AVG(article.incoming_time) FROM ticket, article WHERE ticket.id = article.ticket_id AND ticket.queue_id = 1 GROUP BY article.ticket_id
En la mayoría de las marcas de la base de datos -, así como el estándar SQL en sí - que no están permitidos para escribir una consulta de este tipo, debido a la ambigüedad. No se puede incluir cualquier columna en la lista de selección que no está dentro de una función agregada o citados en la cláusula GROUP BY
.
MySQL es más permisiva. Se le permite hacer esto, y lo deja a usted para escribir consultas y sin ambigüedad. Si usted tiene la ambigüedad, que selecciona los valores de la fila que está físicamente por primera vez en el grupo (pero esto es hasta el motor de almacenamiento).
Por lo que vale, SQLite también tiene este comportamiento, pero elige el última fila del grupo para resolver la ambigüedad. Imagínate. Si el estándar SQL no dice qué hacer, que depende de la implementación del proveedor.
Esta es una consulta que puede resolver su problema para usted:
SELECT a1.* , a1.incoming_time AS maxtime
FROM ticket t JOIN article a1 ON (t.id = a1.ticket_id)
LEFT OUTER JOIN article a2 ON (t.id = a2.ticket_id
AND a1.incoming_time < a2.incoming_time)
WHERE t.queue_id = 1
AND a2.ticket_id IS NULL;
En otras palabras, busca una fila (a1
) para los que no hay otra fila (a2
) con el mismo ticket_id
y una mayor incoming_time
. Si no se encuentra una mayor incoming_time
, el LEFT OUTER JOIN devuelve NULL en lugar de un partido.
Otros consejos
SELECT a1.* FROM article a1
JOIN
(SELECT MAX(a2.incoming_time) AS maxtime
FROM article a2
JOIN ticket ON (a2.ticketid=ticket.id)
WHERE ticket.queue_id=1) xx
ON (a1.incoming_time=xx.maxtime);