2009-06-30 5 views
70

Я хочу, чтобы иметь возможность выбирать кучу строк из таблицы электронной почты и группировать их с отправителя. Мой запрос выглядит следующим образом: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 выбрать эту запись при группировке электронных писем?

ответ

110

Простое решение, чтобы обернуть запрос в подзапрос с 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 documentation» это полезно, прежде всего, когда все значения в каждом неагрегированном столбце, не названном в 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/en/group-by-handling.html

+0

Отличная идея, я бы никогда не подумал об этом. – philwilks

+4

Я пришел с таким же решением несколько лет назад, и это отличное решение. Престижность к b7kich. Здесь есть два вопроса ...GROUP BY нечувствительна к регистру, поэтому LOWER() не требуется, а во-вторых, $ userID представляется переменной непосредственно из PHP, ваш код может быть уязвим для sql-инъекций, если $ userID предоставляется пользователем и не принуждается быть целым числом. – velcrow

+0

Хорошая идея. Большое спасибо –

40

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

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 вы указываете, что последующих строк не может быть. Это дает вам только последнюю строку.

Если может быть несколько писем с одинаковой меткой времени, этот запрос нуждается в уточнении. Если есть инкрементный столбец ID в таблице электронной почты, изменить РЕГИСТРИРУЙТЕСЬ как:

LEFT JOIN incomingEmails next 
    on cur.fromEmail = next.fromEmail 
    and cur.id < next.id 
+0

Сказал, что 'textID' был неоднозначным =/ –

+1

Затем удалите ambuigity и префикс его с именем таблицы, например cur.textID. Изменен и в ответе. – Andomar

+0

Это единственное решение, которое можно сделать с Doctrine DQL. – VisioN

21

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

ONLY_FULL_GROUP_BY

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

+0

MIN (читать) вернет минимальное значение «read». Вероятно, он скорее всего ищет флаг «читать» последнего письма. – Andomar

2

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

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

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

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

24

Делают GROUP BY после ORDER BY, обернув запрос с GROUP BY, как это:

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

Спасибо это работало идеально для меня на simlar запрос, который я делал. – Mark

+0

Итак, GROUP BY' автоматически выбирает последнее 'время', или самое новое' время', или случайное? – xrDDDD

+0

Он выбирает самое новое время, потому что мы заказываем 'time DESC', а затем группа берет первый (последний). – 11101101b

12

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