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 выбрать эту запись при группировке сообщений электронной почты?

Ответ 1

Простое решение состоит в том, чтобы сначала обернуть запрос в подвыбор с помощью оператора ORDER, а затем применить GROUP BY:

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.6.21 я заметил проблемы с GROUP BY во временной таблице, возвращающие сортировку ORDER 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/ен/группы по-handling.html

Ответ 2

Здесь один подход:

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

Ответ 3

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

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from

Ответ 4

Как уже указывалось в ответе, текущий ответ неверен, поскольку 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(), который в большинстве случаев не будет использоваться.

Ответ 5

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

ONLY_FULL_GROUP_BY

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

Ответ 6

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

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

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

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