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 първо и прилагане на 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.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
ВАЖНАТА АКТУАЛИЗАЦИЯ се отнася и за MariaDB: mariadb .com/kb/en/mariadb/ - person Arthur Shipkowski; 18.06.2017
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 се изхвърля от подзаявките, редът, избран от вложената заявка, е случаен. Понякога може да работи, добавяйки към объркването, но това ще доведе до кошмарен бъг. Правилният отговор е тук stackoverflow. com/questions/1066453/mysql-group-by-and-order-by/ - 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
Това не работи, когато се опитвате да се присъедините толкова добре за няколко колони. Т.е., когато се опитвате да намерите най-новия имейл и най-новото потребителско име и имате нужда от няколко самостоятелни леви присъединявания, за да изпълните тази операция в една заявка. - 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

Първо трябва да изберете от Имейл, MIN(четене), а след това, с втората заявка (или подзаявка) - Тема.

person noonex    schedule 30.06.2009
comment
MIN(read) ще върне минималната стойност на read. Вероятно вместо това търси флага за четене на последния имейл. - person Andomar; 01.07.2009

Борих се и с двата подхода за по-сложни заявки от показаните, защото подходът на подзаявката беше ужасно неефективен, без значение какви индекси поставих, и защото не можах да получа външното самосъединяване чрез Hibernate

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

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

Ако можете да получите събитие за вмъкване/актуализиране на am (като тригер), за да изчислите предварително конкатенацията на полетата, можете да го индексирате и заявката ще бъде толкова бърза, сякаш групирането по е само над полето, което всъщност искате да МАКСИМАЛНО ( ). Можете дори да го използвате, за да получите максимума от множество полета. Използвам го, за да правя заявки срещу многоизмерни дървета, изразени като вложени набори.

person Mike N    schedule 31.10.2012