MySQL Group By и Order By

Я хочу иметь возможность выбрать группу строк из таблицы электронных писем и сгруппировать их по отправителю. Мой запрос выглядит так:

SELECT 
    `timestamp`, `fromEmail`, `subject`
FROM `incomingEmails` 
GROUP BY LOWER(`fromEmail`) 
ORDER BY `timestamp` DESC

Запрос почти работает так, как я хочу - он выбирает записи, сгруппированные по электронной почте. Проблема в том, что тема и временная метка не соответствуют самой последней записи для определенного адреса электронной почты.

Например, он может вернуть:

fromEmail: [email protected], subject: hello
fromEmail: [email protected], subject: welcome

Когда записи в базе данных:

fromEmail: [email protected], subject: hello
fromEmail: [email protected], subject: programming question
fromEmail: [email protected], subject: welcome

Если тема «вопроса программирования» самая последняя, ​​как я могу заставить MySQL выбирать эту запись при группировании сообщений электронной почты?


person John Kurlak    schedule 30.06.2009    source источник


Ответы (6)


Простое решение - превратить запрос в подзапрос с помощью оператора ORDER first и применить GROUP BY later:

SELECT * FROM ( 
    SELECT `timestamp`, `fromEmail`, `subject`
    FROM `incomingEmails` 
    ORDER BY `timestamp` DESC
) AS tmp_table GROUP BY LOWER(`fromEmail`)

Это похоже на использование соединения, но выглядит намного лучше.

Использование неагрегированных столбцов в SELECT с предложением GROUP BY является нестандартным. MySQL обычно возвращает значения первой найденной строки и отбрасывает остальные. Любые предложения ORDER BY будут применяться только к возвращаемому значению столбца, а не к отброшенным.

ВАЖНОЕ ОБНОВЛЕНИЕ. Выбор неагрегированных столбцов используется на практике, но на него не следует полагаться. Согласно документации MySQL, это полезно прежде всего, когда все значения в каждом неагрегированном столбце, не названном в GROUP BY, одинаковы для каждой группы. Сервер свободен выбрать любое значение из каждой группы, поэтому если они не совпадают, выбранные значения являются неопределенными.

Начиная с 5.7.5 ONLY_FULL_GROUP_BY включен по умолчанию, поэтому неагрегированные столбцы вызывают ошибки запроса (ER_WRONG_FIELD_WITH_GROUP)

Как указывает @mikep ниже, решением является использование ANY_VALUE () от 5.7 и выше

См. http://www.cafewebmaster.com/mysql-order-sort-group https://dev.mysql.com/doc/refman/5.6/en/group-by-handling.html https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_any-value < / а>

person b7kich    schedule 21.03.2012
comment
Я придумал такое же решение несколько лет назад, и это отличное решение. Престижность b7kich. Однако здесь есть две проблемы ... GROUP BY нечувствителен к регистру, поэтому LOWER () не требуется, а во-вторых, $ userID кажется переменной непосредственно из PHP, ваш код может быть уязвимым для SQL-инъекций, если $ userID предоставляется пользователем, а не принудительно быть целым числом. - person velcrow; 23.04.2013
comment
As of 5.7.5 ONLY_FULL_GROUP_BY is enabled by default, i.e. it's impossible to use non-aggregate columns. Режим SQL можно изменить во время выполнения без прав администратора, поэтому очень легко отключить ONLY_FULL_GROUP_BY. Например: SET SESSION sql_mode = '';. Демо: db-fiddle.com/f/esww483qFQXbXzJmkHZ8VT/3 - person mikep; 02.04.2019
comment
Или другой альтернативой включенному обходу ONLY_FULL_GROUP_BY является использование ANY_VALUE (). См. Больше dev.mysql.com/doc/ refman / 8.0 / en / - person mikep; 02.04.2019
comment
Это НЕПРАВИЛЬНО, ORDER BY исключается из подзапросов, строка, выбранная из вложенного запроса, является случайной. Иногда это может сработать, добавляя путаницы, но это приведет к кошмарной ошибке. Правильный ответ здесь: https://stackoverflow.com/questions/1066453/mysql-group-by-and-order-by/35456144#35456144 - person Cârnăciov; 09.03.2021
comment
ORDER BY определенно не удаляется из подзапросов. Но мне тоже нравится ответ Маркуса. - person b7kich; 05.04.2021

Вот один из подходов:

SELECT cur.textID, cur.fromEmail, cur.subject, 
     cur.timestamp, cur.read
FROM incomingEmails cur
LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.timestamp < next.timestamp
WHERE next.timestamp is null
and cur.toUserID = '$userID' 
ORDER BY LOWER(cur.fromEmail)

Обычно вы присоединяетесь к таблице в поисках более поздних строк. В предложении where вы указываете, что не может быть более поздних строк. Это дает вам только последнюю строку.

Если может быть несколько электронных писем с одной и той же меткой времени, этот запрос потребует уточнения. Если в таблице электронной почты есть столбец инкрементного идентификатора, измените JOIN следующим образом:

LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.id < next.id
person Andomar    schedule 30.06.2009
comment
Сказал, что textID был неоднозначным = / - person John Kurlak; 01.07.2009
comment
Затем удалите двусмысленность и добавьте к ней префикс имени таблицы, например cur.textID. Изменился и ответ. - person Andomar; 01.07.2009
comment
Это единственное решение, которое можно сделать с помощью Doctrine DQL. - person VisioN; 19.02.2016
comment
Это не работает, когда вы так хорошо пытаетесь самостоятельно присоединиться к нескольким столбцам. IE, когда вы пытаетесь найти последний адрес электронной почты и последнее имя пользователя, и вам требуется несколько самостоятельных левых соединений для выполнения этой операции в одном запросе. - person Loveen Dyall; 28.05.2017
comment
При работе с прошлыми и будущими временными метками / датами, чтобы ограничить набор результатов не будущими датами, вам необходимо добавить еще одно условие в LEFT JOIN критерии AND next.timestamp <= UNIX_TIMESTAMP() - person Will B.; 23.10.2017

Как уже указывалось в ответе, текущий ответ неверен, потому что GROUP BY произвольно выбирает запись из окна.

Если используется MySQL 5.6 или MySQL 5.7 с ONLY_FULL_GROUP_BY, правильный (детерминированный) запрос:

SELECT incomingEmails.*
  FROM (
    SELECT fromEmail, MAX(timestamp) `timestamp`
    FROM incomingEmails
    GROUP BY fromEmail
  ) filtered_incomingEmails
  JOIN incomingEmails USING (fromEmail, timestamp)
GROUP BY fromEmail, timestamp

Для эффективного выполнения запроса требуется правильная индексация.

Обратите внимание, что для упрощения я удалил LOWER(), который в большинстве случаев не будет использоваться.

person Marcus    schedule 17.02.2016
comment
Это должен быть правильный ответ. Я только что обнаружил на своем сайте ошибку, связанную с этим. order by в подзапросе в других ответах вообще не действует. - person Jette; 07.06.2018
comment
OMG, пожалуйста, сделайте это принятым ответом. Принятый потратил 5 часов моего времени :( - person Richard Kersey; 09.08.2018
comment
Мне нравится этот ответ, но в конце его все равно нужно заказать - person b7kich; 05.04.2021

Сделайте GROUP BY после ORDER BY, заключив свой запрос в GROUP BY следующим образом:

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from
person 11101101b    schedule 30.04.2013
comment
Значит, GROUP BY` автоматически выбирает последний time, или самый новый time, или случайный? - person xrDDDD; 29.08.2013
comment
Он выбирает самое новое время, потому что мы упорядочиваем по time DESC, а затем группа по берет первое (последнее). - person 11101101b; 06.09.2013
comment
Если бы я только мог выполнять JOINS для вложенных выборок в VIEWS, в mysql 5.1. Возможно, эта функция появится в более новой версии. - person IcarusNM; 15.06.2015

Согласно стандарту SQL вы не можете использовать неагрегированные столбцы в списке выбора. MySQL допускает такое использование (если не используется режим ONLY_FULL_GROUP_BY), но результат непредсказуем.

ONLY_FULL_GROUP_BY

Вы должны сначала выбрать fromEmail, MIN (чтение), а затем, со вторым запросом (или подзапросом) - Subject.

person noonex    schedule 30.06.2009
comment
MIN (чтение) вернет минимальное значение чтения. Он, вероятно, вместо этого ищет флаг чтения в последнем электронном письме. - person Andomar; 01.07.2009

Я боролся с обоими этими подходами для более сложных запросов, чем показано, потому что подход с подзапросом был ужасно неэффективным независимо от того, какие индексы я использовал, и потому что я не мог получить внешнее самосоединение через Hibernate

Лучший (и самый простой) способ сделать это - сгруппировать по чему-то, сконструированному так, чтобы содержать конкатенацию требуемых полей, а затем вытащить их, используя выражения в предложении SELECT. Если вам нужно выполнить MAX (), убедитесь, что поле, которое вы хотите использовать MAX (), всегда находится на самом значительном конце объединенного объекта.

Ключ к пониманию этого заключается в том, что запрос может иметь смысл только в том случае, если эти другие поля инвариантны для любого объекта, который удовлетворяет Max (), поэтому с точки зрения сортировки другие части конкатенации можно игнорировать. Он объясняет, как это сделать, в самом низу этой ссылки. http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html

Если вы можете получить событие вставки / обновления (например, триггер) для предварительного вычисления конкатенации полей, вы можете его проиндексировать, и запрос будет таким же быстрым, как если бы группа по занимала только поле, которое вы действительно хотели MAX ( ). Вы даже можете использовать его, чтобы получить максимум нескольких полей. Я использую его для выполнения запросов к многомерным деревьям, выраженным в виде вложенных наборов.

person Mike N    schedule 31.10.2012