MYSQL показывает некорректные строки при использовании GROUP BY - PullRequest
4 голосов
/ 11 июня 2009

У меня есть две таблицы:

article('id', 'ticket_id', 'incoming_time', 'to', 'from', 'message')
ticket('id', 'queue_id')

где тикеты представляют собой поток писем между сотрудниками службы поддержки и клиентами, а статьи - отдельные сообщения, составляющие цепочку.

Я ищу статью с наибольшим временем поступления (выраженную как метка времени Unix) для каждого ticket_id, и этот запрос я сейчас использую:

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

Например,

: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
...

Но в результате получается строка с наименьшим идентификатором статьи вместо того, что я ищу - статья с наибольшим временем поступления.

Любой совет будет принята с благодарностью!

Ответы [ 2 ]

17 голосов
/ 11 июня 2009

Это классическое препятствие, с которым сталкивается большинство программистов MySQL.

  • У вас есть столбец ticket_id, который является аргументом для GROUP BY. Отдельные значения в этом столбце определяют группы.
  • У вас есть столбец incoming_time, который является аргументом для MAX(). Наибольшее значение в этом столбце по строкам в каждой группе возвращается как значение MAX().
  • У вас есть все остальные столбцы таблицы статей. Значения, возвращаемые для этих столбцов, являются произвольными, а не из той же строки, где встречается значение MAX().

База данных не может сделать вывод, что вам нужны значения из той же строки, где встречается максимальное значение.

Подумайте о следующих случаях:

  • В нескольких строках встречается одно и то же максимальное значение. Какую строку следует использовать для отображения столбцов article.*?

  • Вы пишете запрос, который возвращает как MIN(), так и MAX(). Это допустимо, но какую строку следует article.* показать?

    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
    
  • Вы используете агрегатную функцию, например AVG() или SUM(), где ни одна строка не имеет этого значения. Как базе данных угадать, какую строку отображать?

    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
    

В большинстве марок баз данных - а также в самом стандарте SQL - вам не разрешено писать такой запрос из-за неоднозначности. Вы не можете включить в список выбора ни один столбец, который не находится внутри агрегатной функции или не назван в предложении GROUP BY.

MySQL более разрешительный. Это позволяет вам делать это и оставляет за вами право писать запросы без двусмысленности. Если у вас есть неоднозначность, он выбирает значения из строки, которая физически является первой в группе (но это зависит от механизма хранения).

Что бы это ни стоило, SQLite также имеет такое поведение, но выбирает последнюю строку в группе, чтобы устранить неоднозначность. Пойди разберись. Если в стандарте SQL не указано, что делать, дело за реализацией поставщика.

Вот запрос, который может решить вашу проблему для вас:

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;

Другими словами, ищите строку (a1), для которой нет другой строки (a2) с таким же ticket_id и большим incoming_time. Если больше incoming_time не найдено, LEFT OUTER JOIN возвращает NULL вместо совпадения.

3 голосов
/ 11 июня 2009
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);
...