MySQL “Группировать по” и “Упорядочивать по”
-
21-08-2019 - |
Вопрос
Я хочу иметь возможность выбрать несколько строк из таблицы электронных писем и сгруппировать их по отправителю from.Мой запрос выглядит примерно так:
SELECT
`timestamp`, `fromEmail`, `subject`
FROM `incomingEmails`
GROUP BY LOWER(`fromEmail`)
ORDER BY `timestamp` DESC
Запрос работает почти так, как я хочу — он выбирает записи, сгруппированные по электронной почте.Проблема в том, что тема и временная метка не соответствуют самой последней записи для конкретного адреса электронной почты.
Например, он может возвращать:
fromEmail: john@example.com, subject: hello
fromEmail: mark@example.com, subject: welcome
Когда записи в базе данных являются:
fromEmail: john@example.com, subject: hello
fromEmail: john@example.com, subject: programming question
fromEmail: mark@example.com, subject: welcome
Если тема "вопрос по программированию" является самой последней, как я могу заставить MySQL выбирать эту запись при группировании электронных писем?
Решение
Простое решение состоит в том, чтобы обернуть запрос в подвыборку с инструкцией ORDER Первый и применяя ГРУППУ ПО позже:
SELECT * FROM (
SELECT `timestamp`, `fromEmail`, `subject`
FROM `incomingEmails`
ORDER BY `timestamp` DESC
) AS tmp_table GROUP BY LOWER(`fromEmail`)
Это похоже на использование join, но выглядит намного приятнее.
Использование неагрегированных столбцов в предложении SELECT с предложением GROUP BY является нестандартным.MySQL обычно возвращает значения первой найденной строки и отбрасывает остальные.Любые предложения ORDER BY будут применяться только к возвращаемому значению столбца, а не к отброшенным.
ВАЖНОЕ ОБНОВЛЕНИЕ Выбор неагрегированных столбцов используется для работы на практике, но на него не следует полагаться.В соответствии с Документация MySQL "это полезно в первую очередь, когда все значения в каждом неагрегированном столбце, не названном в GROUP BY, одинаковы для каждой группы.Сервер находится свободно выбирать любое значение от каждой группы, так что если они не совпадают, выбранные значения являются неопределенными."
Начиная с версии 5.6.21, я заметил проблемы с GROUP BY во временной таблице, отменяющей ПОРЯДОК сортировки.
По состоянию на 5.7.5 ONLY_FULL_GROUP_BY включен по умолчанию, т.е.невозможно использовать неагрегированные столбцы.
Видишь 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
Другие советы
Вот один из подходов:
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 вы указываете, что более поздних строк быть не может.Это дает вам только последнюю строку.
Если может быть несколько электронных писем с одной и той же временной меткой, этот запрос потребует уточнения.Если в таблице электронной почты есть столбец с добавочным идентификатором, измените соединение следующим образом:
LEFT JOIN incomingEmails next
on cur.fromEmail = next.fromEmail
and cur.id < next.id
Выполните GROUP BY после ORDER BY, обернув ваш запрос GROUP BY следующим образом:
SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from
Как уже указывалось в ответе, текущий ответ неверен, потому что 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()
, который в большинстве случаев использоваться не будет.
Согласно стандарту SQL, вы не можете использовать неагрегированные столбцы в списке выбора.MySQL допускает такое использование (без использования режима ONLY_FULL_GROUP_BY), но результат непредсказуем.
ONLY_FULL_GROUP_BY ЕДИНСТВЕННАЯ_ГРУППА_BY
Сначала вы должны выбрать fromEmail, MIN(читать), а затем, с помощью второго запроса (или подзапроса) - Subject.
Я боролся с обоими этими подходами для более сложных запросов, чем те, что показаны, потому что подход с подзапросами был ужасно неэффективным, независимо от того, какие индексы я ввел, и потому что я не мог получить внешнее самосоединение через Hibernate
Лучший (и самый простой) способ сделать это - сгруппировать по чему-либо, что сконструировано так, чтобы содержать конкатенацию требуемых вам полей, а затем извлечь их, используя выражения в предложении SELECT .Если вам нужно выполнить функцию MAX(), убедитесь, что поле, над которым вы хотите выполнить функцию MAX(), всегда находится в самом значимом конце объединенного объекта.
Ключом к пониманию этого является то, что запрос может иметь смысл только в том случае, если эти другие поля инвариантны для любого объекта, который удовлетворяет Max() , поэтому с точки зрения сортировки другие части конкатенации могут быть проигнорированы.В самом низу этой ссылки объясняется, как это сделать. http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html
Если вы можете получить событие insert / update (например, триггер) для предварительного вычисления конкатенации полей, вы можете проиндексировать его, и запрос будет таким же быстрым, как если бы group by была только над полем, которое вы на самом деле хотели MAX().Вы даже можете использовать его, чтобы получить максимум из нескольких полей.Я использую его для выполнения запросов к многомерным деревьям, выраженным как вложенные наборы.