Отримання останнього запису в кожній групі - MySQL


952

Існує таблиця, messagesяка містить дані, як показано нижче:

Id   Name   Other_Columns
-------------------------
1    A       A_data_1
2    A       A_data_2
3    A       A_data_3
4    B       B_data_1
5    B       B_data_2
6    C       C_data_1

Якщо я запускаю запит select * from messages group by name, отримаю результат у вигляді:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

Який запит поверне наступний результат?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

Тобто останній запис у кожній групі повинен бути повернутий.

В даний час це запит, який я використовую:

SELECT
  *
FROM (SELECT
  *
FROM messages
ORDER BY id DESC) AS x
GROUP BY name

Але це виглядає вкрай неефективно. Будь-які інші способи досягти того ж результату?


2
дивіться прийняту відповідь у stackoverflow.com/questions/1379565/… для більш ефективного рішення
eyaler


7
Чому ви не можете просто додати DESC, тобто вибрати * з групи повідомлень на ім’я DESC
Кім Принс


2
@KimPrince Схоже, відповідь, яку ви пропонуєте, не робить того, що очікується! Я просто спробував ваш метод, і він взяв ПЕРШИЙ рядок для кожної групи і замовив DESC. НЕ приймає останній ряд кожної групи
Айрат

Відповіді:


967

MySQL 8.0 тепер підтримує функції вікон, як і майже всі популярні реалізації SQL. За допомогою цього стандартного синтаксису ми можемо записувати найбільші n-по-групі запити:

WITH ranked_messages AS (
  SELECT m.*, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id DESC) AS rn
  FROM messages AS m
)
SELECT * FROM ranked_messages WHERE rn = 1;

Нижче наведено оригінальну відповідь, яку я написав на це запитання у 2009 році:


Я пишу рішення таким чином:

SELECT m1.*
FROM messages m1 LEFT JOIN messages m2
 ON (m1.name = m2.name AND m1.id < m2.id)
WHERE m2.id IS NULL;

Щодо ефективності, то одне чи інше рішення може бути кращим, залежно від характеру ваших даних. Таким чином, ви повинні перевірити обидва запити і використовувати той, який краще працює за вашою базою даних.

Наприклад, у мене є копія дампів даних серпня StackOverflow . Я буду використовувати це для тестування. У Postsтаблиці 1114357 рядків . Це працює на MySQL 5.0.75 на моєму Macbook Pro 2,40 ГГц.

Я напишу запит, щоб знайти останню публікацію для даного ідентифікатора користувача (моя).

Спочатку використовуйте техніку, показану @Eric з GROUP BYпідзапитом:

SELECT p1.postid
FROM Posts p1
INNER JOIN (SELECT pi.owneruserid, MAX(pi.postid) AS maxpostid
            FROM Posts pi GROUP BY pi.owneruserid) p2
  ON (p1.postid = p2.maxpostid)
WHERE p1.owneruserid = 20860;

1 row in set (1 min 17.89 sec)

Навіть EXPLAINаналіз займає більше 16 секунд:

+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
| id | select_type | table      | type   | possible_keys              | key         | key_len | ref          | rows    | Extra       |
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL                       | NULL        | NULL    | NULL         |   76756 |             | 
|  1 | PRIMARY     | p1         | eq_ref | PRIMARY,PostId,OwnerUserId | PRIMARY     | 8       | p2.maxpostid |       1 | Using where | 
|  2 | DERIVED     | pi         | index  | NULL                       | OwnerUserId | 8       | NULL         | 1151268 | Using index | 
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
3 rows in set (16.09 sec)

Тепер створіть той же результат запиту, використовуючи мою техніку з LEFT JOIN:

SELECT p1.postid
FROM Posts p1 LEFT JOIN posts p2
  ON (p1.owneruserid = p2.owneruserid AND p1.postid < p2.postid)
WHERE p2.postid IS NULL AND p1.owneruserid = 20860;

1 row in set (0.28 sec)

На EXPLAINаналіз показує , що обидві таблиці мають можливість використовувати свої індекси:

+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
| id | select_type | table | type | possible_keys              | key         | key_len | ref   | rows | Extra                                |
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
|  1 | SIMPLE      | p1    | ref  | OwnerUserId                | OwnerUserId | 8       | const | 1384 | Using index                          | 
|  1 | SIMPLE      | p2    | ref  | PRIMARY,PostId,OwnerUserId | OwnerUserId | 8       | const | 1384 | Using where; Using index; Not exists | 
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
2 rows in set (0.00 sec)

Ось DDL для моєї Postsтаблиці:

CREATE TABLE `posts` (
  `PostId` bigint(20) unsigned NOT NULL auto_increment,
  `PostTypeId` bigint(20) unsigned NOT NULL,
  `AcceptedAnswerId` bigint(20) unsigned default NULL,
  `ParentId` bigint(20) unsigned default NULL,
  `CreationDate` datetime NOT NULL,
  `Score` int(11) NOT NULL default '0',
  `ViewCount` int(11) NOT NULL default '0',
  `Body` text NOT NULL,
  `OwnerUserId` bigint(20) unsigned NOT NULL,
  `OwnerDisplayName` varchar(40) default NULL,
  `LastEditorUserId` bigint(20) unsigned default NULL,
  `LastEditDate` datetime default NULL,
  `LastActivityDate` datetime default NULL,
  `Title` varchar(250) NOT NULL default '',
  `Tags` varchar(150) NOT NULL default '',
  `AnswerCount` int(11) NOT NULL default '0',
  `CommentCount` int(11) NOT NULL default '0',
  `FavoriteCount` int(11) NOT NULL default '0',
  `ClosedDate` datetime default NULL,
  PRIMARY KEY  (`PostId`),
  UNIQUE KEY `PostId` (`PostId`),
  KEY `PostTypeId` (`PostTypeId`),
  KEY `AcceptedAnswerId` (`AcceptedAnswerId`),
  KEY `OwnerUserId` (`OwnerUserId`),
  KEY `LastEditorUserId` (`LastEditorUserId`),
  KEY `ParentId` (`ParentId`),
  CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`PostTypeId`) REFERENCES `posttypes` (`PostTypeId`)
) ENGINE=InnoDB;

8
Дійсно? Що станеться, якщо у вас є кількість записів? Наприклад, якщо ви працюєте з внутрішнім контролем версій, скажімо, і у вас є тонна версій на файл, результат приєднання буде масовим. Ви коли-небудь орієнтували метод підзапиту з цим? Мені досить цікаво знати, хто виграв би, але недостатньо цікавий, щоб не запитати вас спочатку.
Ерік

2
Зробив тестування. На невеликій таблиці (~ 300k записів, ~ 190k груп, так що не масові групи чи що-небудь), запити пов'язані (8 секунд кожна).
Ерік

1
@BillKarwin: Див. Meta.stackexchange.com/questions/123017 , особливо коментарі нижче відповіді Адама Ракіса. Повідомте мене, якщо ви хочете отримати свою відповідь на нове запитання.
Роберт Харві

3
@Tim, ні, <=не допоможе, якщо у вас є унікальна колонка. Потрібно використовувати унікальний стовпчик як краватку.
Білл Карвін

2
Продуктивність знижується експоненціально, коли кількість рядків збільшується або коли групи стають більшими. Наприклад, група, що складається з 5 дат, отримає 4 + 3 + 2 + 1 + 1 = 11 рядків через ліве з'єднання, з якого один ряд фільтрується в кінці. Продуктивність приєднання до згрупованих результатів майже лінійна. Ваші тести виглядають вадами.
Салман

145

UPD: 2017-03-31, версія 5.7.5 MySQL дозволила включити перемикач ONLY_FULL_GROUP_BY за замовчуванням (отже, недетерміновані запити GROUP BY стали відключені). Крім того, вони оновили реалізацію GROUP BY, і рішення може більше не працювати, як очікувалося, навіть із вимкненим комутатором. Потрібно перевірити.

Вище рішення Білла Карвіна працює чудово, коли кількість елементів у групах досить мала, але ефективність запиту стає поганою, коли групи досить великі, оскільки для вирішення потрібні n*n/2 + n/2лише IS NULLпорівняння.

Я зробив свої тести на InnoDB таблиці 18684446рядків з 1182групами. Таблиця містить тестові результати функціональних тестів і є (test_id, request_id)основним ключем. Таким чином, test_idце група, і я шукав останнього request_idдля кожного test_id.

Рішення Білла вже працює протягом декількох годин на моєму dell e4310, і я не знаю, коли він закінчиться, хоча він працює за індексом покриття (звідси using indexв EXPLAIN).

У мене є кілька інших рішень, заснованих на одних і тих же ідеях:

  • якщо базовим індексом є індекс BTREE (що зазвичай буває), найбільша (group_id, item_value)пара - це останнє значення всередині кожного group_id, тобто перше для кожного, group_idякщо ми проходимо через індекс у порядку зменшення;
  • якщо ми читаємо значення, які охоплені індексом, значення зчитуються в порядку індексу;
  • кожен індекс неявно містить стовпчики первинного ключа, додані до цього (тобто первинний ключ знаходиться в індексі покриття). У рішеннях нижче я працюю безпосередньо на первинному ключі, у вашому випадку вам просто потрібно буде додати стовпці первинного ключа в результат.
  • у багатьох випадках набагато дешевше зібрати потрібні ідентифікатори рядків у потрібному порядку в підзапит і приєднати результат підзапросу на id. Оскільки для кожного рядка в результаті запиту MySQL знадобиться один вибір, заснований на первинному ключі, підзапит буде поставлений спочатку в об'єднанні, а рядки будуть виведені в порядку ідентифікаторів у підзапиті (якщо опустити явний ORDER BY для приєднання)

3 способи MySQL використовує індекси - це чудова стаття для розуміння деяких деталей.

Рішення 1

Це надзвичайно швидко, на моїх 18М + рядках потрібно близько 0,8 сек:

SELECT test_id, MAX(request_id) AS request_id
FROM testresults
GROUP BY test_id DESC;

Якщо ви хочете змінити порядок на ASC, введіть його в підзапит, поверніть лише ідентифікатори та використовуйте його як підзапит, щоб приєднатися до решти стовпців:

SELECT test_id, request_id
FROM (
    SELECT test_id, MAX(request_id) AS request_id
    FROM testresults
    GROUP BY test_id DESC) as ids
ORDER BY test_id;

Ця інформація займає приблизно 1,2 секунди на моїх даних.

Рішення 2

Ось ще одне рішення, яке займає для мого столу приблизно 19 секунд:

SELECT test_id, request_id
FROM testresults, (SELECT @group:=NULL) as init
WHERE IF(IFNULL(@group, -1)=@group:=test_id, 0, 1)
ORDER BY test_id DESC, request_id DESC

Він також повертає тести у порядку зменшення. Це набагато повільніше, оскільки він виконує повне сканування індексу, але саме тут ви даєте уявлення про те, як вивести N max рядків для кожної групи.

Недоліком запиту є те, що його результат не може кешуватися кешем запитів.


Будь ласка, посилайтесь на дамп своїх таблиць, щоб люди могли перевірити його на своїх платформах.
Pacerier

3
Рішення 1 не може працювати, ви не можете вибрати request_id, не маючи цього в групі за пунктом,
gi

2
@ giò, це відповідь 5 років. До MySQL 5.7.5 ONLY_FULL_GROUP_BY був відключений за замовчуванням , і це рішення працювало з коробки dev.mysql.com/doc/relnotes/mysql/5.7/en / ... . Зараз я не впевнений, чи вирішення все-таки спрацьовує при відключенні режиму, оскільки реалізація GROUP BY була змінена.
newtover

Якби ви хотіли ASC у першому рішенні, чи спрацювало б воно, якщо повернути MAX до MIN?
Джин

@JinIzzraeel, у вас є за замовчуванням MIN у верхній частині кожної групи (це порядок індексу покриття): SELECT test_id, request_id FROM testresults GROUP BY test_id;повертав би мінімальний request_id для кожного test_id.
newtover

101

Скористайтеся своїм підзапитом, щоб повернути правильне угрупування, оскільки ви там на півдорозі.

Спробуйте це:

select
    a.*
from
    messages a
    inner join 
        (select name, max(id) as maxid from messages group by name) as b on
        a.id = b.maxid

Якщо це не idви хочете максимум:

select
    a.*
from
    messages a
    inner join 
        (select name, max(other_col) as other_col 
         from messages group by name) as b on
        a.name = b.name
        and a.other_col = b.other_col

Таким чином, ви уникаєте співвіднесених підзапитів та / або замовлень у своїх підзапитах, які, як правило, дуже повільні / неефективні.


1
Зауважте застереження щодо рішення other_col: якщо цей стовпець не є унікальним, ви можете отримати кілька записів назад з однаковими name, якщо вони пов'язані max(other_col). Я знайшов цю публікацію, яка описує рішення для моїх потреб, де мені потрібно рівно один запис на кожного name.
Ерік Сімонтон

У деяких ситуаціях ви можете використовувати це рішення, але прийняте.
tom10271

На мій досвід, це групування цілої проклятої таблиці повідомлень, яка, як правило, повільна / неефективна! Іншими словами, зауважте, що підзапит вимагає повного сканування таблиці та виконує групування для цього для завантаження ... якщо тільки ваш оптимізатор не робить щось, що моє, це не так. Тож це рішення значною мірою залежить від того, щоб зберігати весь стіл в пам’яті.
Тімо

Ці виграли б INDEX(name, id)іINDEX(name, other_col)
Рік Джеймс

55

Я прийшов до іншого рішення, яке полягає в тому, щоб отримати ідентифікатори для останньої публікації в кожній групі, а потім вибрати з таблиці повідомлень, використовуючи результат з першого запиту як аргумент для WHERE x INконструкції:

SELECT id, name, other_columns
FROM messages
WHERE id IN (
    SELECT MAX(id)
    FROM messages
    GROUP BY name
);

Я не знаю, як це працює порівняно з деякими іншими рішеннями, але це вражаюче спрацювало для моєї таблиці з 3+ мільйонами рядків. (4 секунди виконання з 1200+ результатами)

Це має працювати як на MySQL, так і на SQL Server.


Просто переконайтеся, що у вас є індекс (ім'я, ідентифікатор).
Самуель Ослунд

1
Набагато краще, що приєднується до себе
anwerj

Я дізнався щось у вас, що це гарна робота, і цей запит швидше
Хамфрі,

33

Рішення по підзапиту fiddle Посилання

select * from messages where id in
(select max(id) from messages group by Name)

Рішення За допомогою посилання на умову приєднання

select m1.* from messages m1 
left outer join messages m2 
on ( m1.id<m2.id and m1.name=m2.name )
where m2.id is null

Причиною цієї публікації є надання лише скрипкового посилання. Той самий SQL вже представлений в інших відповідях.


1
@AlexanderSuraphel mysql5.5 зараз не доступний у скрипці, скриптове посилання створено за допомогою цього. Тепер скрипка підтримує mysql5.6, я змінив базу даних на mysql 5.6, і я в змозі скласти схему і запустити sql.
Vipin

8

Підхід із значною швидкістю полягає в наступному.

SELECT * 
FROM messages a
WHERE Id = (SELECT MAX(Id) FROM messages WHERE a.Name = Name)

Результат

Id  Name    Other_Columns
3   A   A_data_3
5   B   B_data_2
6   C   C_data_1

Це передбачає id, що впорядковано так, як вам потрібно. У загальному випадку потрібен інший стовпець.
Рік Джеймс

6

Ось дві пропозиції. По-перше, якщо mysql підтримує ROW_NUMBER (), це дуже просто:

WITH Ranked AS (
  SELECT Id, Name, OtherColumns,
    ROW_NUMBER() OVER (
      PARTITION BY Name
      ORDER BY Id DESC
    ) AS rk
  FROM messages
)
  SELECT Id, Name, OtherColumns
  FROM messages
  WHERE rk = 1;

Я припускаю, що "останнім" ви маєте на увазі останнє в порядку Id. Якщо ні, то відповідно змініть пункт ORDER BY у вікні ROW_NUMBER (). Якщо ROW_NUMBER () недоступний, це ще одне рішення:

По-друге, якщо цього не відбувається, це часто хороший спосіб продовжити:

SELECT
  Id, Name, OtherColumns
FROM messages
WHERE NOT EXISTS (
  SELECT * FROM messages as M2
  WHERE M2.Name = messages.Name
  AND M2.Id > messages.Id
)

Іншими словами, виберіть повідомлення, у яких немає пізнішого повідомлення Id з тим самим Іменем.


8
MySQL не підтримує ROW_NUMBER () або CTE.
Білл Карвін

1
MySQL 8.0 (і MariaDB 10.2) тепер підтримують ROW_NUMBER()та CTE.
Рік Джеймс

6

Я ще не перевіряв велику БД, але думаю, що це може бути швидше, ніж приєднання до таблиць:

SELECT *, Max(Id) FROM messages GROUP BY Name

14
Це повертає довільні дані. Іншими словами, повернені стовпці можуть бути не записані з MAX (Id).
шкода

Корисно вибрати максимум Id з набору записів із умовою WHERE: "SELECT Max (Id) FROM Prod WHERE Pn = '" + Pn + "'" Він повертає max id з набору записів з тим же Pn.In c # використовуйте reader.GetString (0), щоб отримати результат
Нікола

5

Ось ще один спосіб отримати останній пов'язаний запис, використовуючи GROUP_CONCATпорядок, і SUBSTRING_INDEXвибрати один із записів зі списку

SELECT 
  `Id`,
  `Name`,
  SUBSTRING_INDEX(
    GROUP_CONCAT(
      `Other_Columns` 
      ORDER BY `Id` DESC 
      SEPARATOR '||'
    ),
    '||',
    1
  ) Other_Columns 
FROM
  messages 
GROUP BY `Name` 

Вище запит згрупує всі, Other_Columnsщо знаходяться в одній Nameгрупі, і використовуючи ORDER BY id DESCприєднається до всіх Other_Columnsу певній групі у спадному порядку із наданим роздільником у моєму випадку, який я використав ||, за допомогою SUBSTRING_INDEXцього списку виберемо перший

Демо Fiddle


Майте на увазі, що group_concat_max_lenобмежує кількість рядків, які ви можете обробити.
Рік Джеймс

5

Зрозуміло, що існує багато різних способів отримання однакових результатів. Здається, ваше питання полягає в тому, що є ефективним способом отримання останніх результатів для кожної групи в MySQL. Якщо ви працюєте з величезною кількістю даних і припускаєте, що використовуєте InnoDB навіть із останніми версіями MySQL (такими як 5.7.21 та 8.0.4-rc), то це може бути не ефективним способом цього зробити.

Нам іноді потрібно робити це за допомогою таблиць із ще більше 60 мільйонами рядків.

Для цих прикладів я буду використовувати дані лише з приблизно 1,5 мільйона рядків, де запити повинні знайти результати для всіх груп у даних. У наших фактичних випадках нам часто потрібно повертати дані приблизно з 2000 груп (що гіпотетично не вимагає вивчення дуже багатьох даних).

Я буду використовувати наступні таблиці:

CREATE TABLE temperature(
  id INT UNSIGNED NOT NULL AUTO_INCREMENT, 
  groupID INT UNSIGNED NOT NULL, 
  recordedTimestamp TIMESTAMP NOT NULL, 
  recordedValue INT NOT NULL,
  INDEX groupIndex(groupID, recordedTimestamp), 
  PRIMARY KEY (id)
);

CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id)); 

Температурна таблиця заповнена приблизно 1,5 мільйона випадкових записів і зі 100 різними групами. Вибрана_група складається з цих 100 груп (у наших випадках це, як правило, менше 20% для всіх груп).

Оскільки ці дані є випадковими, це означає, що кілька рядків можуть мати однакові записані мітки часу. Ми хочемо отримати список усіх виділених груп у порядку groupID з останньою записаною міткою часу для кожної групи, і якщо одна і та ж група має більше одного відповідного рядка, як той, то останній ідентифікаційний збіг цих рядків.

Якщо гіпотетично MySQL мав функцію останнього (), яка повертала значення з останнього рядка в спеціальному пункті ЗАМОВЛЕННЯ ПО, то ми могли б просто зробити:

SELECT 
  last(t1.id) AS id, 
  t1.groupID, 
  last(t1.recordedTimestamp) AS recordedTimestamp, 
  last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;

що в цьому випадку потрібно буде вивчити лише кілька 100 рядків, оскільки він не використовує жодної з звичайних функцій GROUP BY. Це виконується за 0 секунд і, отже, буде дуже ефективним. Зауважте, що зазвичай у MySQL ми бачимо пункт ORDER BY за пунктом GROUP BY, однак цей пункт ORDER BY використовується для визначення ORDER для останньої функції (), якби це було після GROUP BY, тоді він би замовляв групи. Якщо немає пропозиції BY BY, то останні значення будуть однаковими у всіх повернених рядках.

Однак у MySQL цього немає, тому давайте розглянемо різні ідеї того, що він має, і докажемо, що жодне з них не є ефективним.

Приклад 1

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT t2.id
  FROM temperature t2 
  WHERE t2.groupID = g.id
  ORDER BY t2.recordedTimestamp DESC, t2.id DESC
  LIMIT 1
);

Це вивчило 3 099 254 рядків і зайняло ~ 0,859 секунди 5,7,21 і трохи довше 8,0,4-rc

Приклад 2

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
INNER JOIN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
) t5 ON t5.id = t1.id;

Це вивчило 1505,331 рядків і зайняло ~ 1,25 секунди 5,7,21 і трохи довше 8,0,4-rc

Приклад 3

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
WHERE t1.id IN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
)
ORDER BY t1.groupID;

Це досліджувало 3 009 685 рядків і зайняло ~ 1,95 секунди 5,7,21 і трохи довше 8,0,4-rc

Приклад 4

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT max(t2.id)
  FROM temperature t2 
  WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
      SELECT max(t3.recordedTimestamp)
      FROM temperature t3 
      WHERE t3.groupID = g.id
    )
);

Це досліджувало 6,137,810 рядків і займало ~ 2,2 секунди 5,7,21 і трохи довше 8,0,4-rc

Приклад 5

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
  SELECT 
    t2.id, 
    t2.groupID, 
    t2.recordedTimestamp, 
    t2.recordedValue, 
    row_number() OVER (
      PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
    ) AS rowNumber
  FROM selected_group g 
  INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;

Це досліджувало 617808 рядків і займало ~ 4,2 секунди на 8,0,4-rc

Приклад 6

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM (
  SELECT 
    last_value(t2.id) OVER w AS id, 
    t2.groupID, 
    last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp, 
    last_value(t2.recordedValue) OVER w AS recordedValue
  FROM selected_group g
  INNER JOIN temperature t2 ON t2.groupID = g.id
  WINDOW w AS (
    PARTITION BY t2.groupID 
    ORDER BY t2.recordedTimestamp, t2.id 
    RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  )
) t1
GROUP BY t1.groupID;

Це досліджувало 617908 рядків і займало ~ 17,5 секунди на 8,0,4-rc

Приклад 7

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2 
  ON t2.groupID = g.id 
  AND (
    t2.recordedTimestamp > t1.recordedTimestamp 
    OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
  )
WHERE t2.id IS NULL
ORDER BY t1.groupID;

Цей приймав назавжди, тому мені довелося його вбити.


Це інша проблема. І рішення - це величезний запит UNION ALL.
Пол Шпігель

@PaulSpiegel Я думаю, ти жартуєш про величезний СПІЛЬ ВСІХ. Крім того, що потрібно було б знати всі вибрані групи заздалегідь, і що з 2000 вибраних груп, які були б неймовірно величезним запитом, він би виконував ще гірше, ніж найшвидший приклад, наведений вище, так що ні, це не було б рішення.
Yoseph

Я абсолютно серйозний. Я тестував це в минулому з кількома сотнями груп. Коли вам потрібно обробляти зв'язки у великих групах, UNION ALL - це єдиний спосіб у MySQL створити оптимальний план виконання. SELECT DISTINCT(groupID)швидко і надасть усі дані, необхідні для побудови такого запиту. Вам слід добре відповідати розміру запиту до тих пір, поки він не перевищує max_allowed_packet, який за замовчуванням становить 4 Мб в MySQL 5.7.
Пол Шпігель

5

ми розглянемо, як можна використовувати MySQL при отриманні останнього запису в групі записів. Наприклад, якщо у вас є такий набір публікацій.

id category_id post_title

1 1 Title 1

2 1 Title 2

3 1 Title 3

4 2 Title 4

5 2 Title 5

6 3 Title 6

Я хочу, щоб я міг отримати останню публікацію в кожній категорії, що є заголовком 3, заголовком 5 та заголовком 6. Для отримання публікацій за категорією ви будете використовувати клавіатуру MySQL Group By.

select * from posts group by category_id

Але результати, які ми отримуємо від цього запиту, є.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

Група завжди буде повертати перший запис у групі за результатом.

SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );

Це поверне повідомлення з найвищими ідентифікаторами в кожній групі.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Довідка Натисніть тут


4
SELECT 
  column1,
  column2 
FROM
  table_name 
WHERE id IN 
  (SELECT 
    MAX(id) 
  FROM
    table_name 
  GROUP BY column1) 
ORDER BY column1 ;

Не могли б ви детальніше розібратися у своїй відповіді? Чому ваш запит кращий для оригінального запиту Vijays?
janfoeh

4

Ось моє рішення:

SELECT 
  DISTINCT NAME,
  MAX(MESSAGES) OVER(PARTITION BY NAME) MESSAGES 
FROM MESSAGE;

Це не повертає останнє повідомлення на ім’я. І це просто надскладна версія версії SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Пол Шпігель

Крім того, ця рецептура є надзвичайно неефективною.
Рік Джеймс

3

Спробуйте це:

SELECT jos_categories.title AS name,
       joined .catid,
       joined .title,
       joined .introtext
FROM   jos_categories
       INNER JOIN (SELECT *
                   FROM   (SELECT `title`,
                                  catid,
                                  `created`,
                                  introtext
                           FROM   `jos_content`
                           WHERE  `sectionid` = 6
                           ORDER  BY `id` DESC) AS yes
                   GROUP  BY `yes`.`catid` DESC
                   ORDER  BY `yes`.`created` DESC) AS joined
         ON( joined.catid = jos_categories.id )  

3

Привіт @Vijay Dev, якщо повідомлення вашої таблиці містять ідентифікатор, який є первинним ключем автоматичного збільшення, то для отримання останньої бази запису на первинному ключі ваш запит повинен читати, як показано нижче:

SELECT m1.* FROM messages m1 INNER JOIN (SELECT max(Id) as lastmsgId FROM messages GROUP BY Name) m2 ON m1.Id=m2.lastmsgId

Цей найшвидший я знайшов
CORSAIR

3

Ви також можете поглянути звідси.

http://sqlfiddle.com/#!9/ef42b/9

ПЕРШЕ РІШЕННЯ

SELECT d1.ID,Name,City FROM Demo_User d1
INNER JOIN
(SELECT MAX(ID) AS ID FROM Demo_User GROUP By NAME) AS P ON (d1.ID=P.ID);

ДРУГЕ РІШЕННЯ

SELECT * FROM (SELECT * FROM Demo_User ORDER BY ID DESC) AS T GROUP BY NAME ;

3
SELECT * FROM table_name WHERE primary_key IN (SELECT MAX(primary_key) FROM table_name GROUP BY column_name )


2

Чи є якийсь спосіб ми могли використовувати цей метод для видалення дублікатів у таблиці? Набір результатів - це, як правило, сукупність унікальних записів, тож якби ми могли видалити всі записи не в наборі результатів, ми б фактично не мали дублікатів? Я спробував це, але mySQL дав помилку 1093.

DELETE FROM messages WHERE id NOT IN
 (SELECT m1.id  
 FROM messages m1 LEFT JOIN messages m2  
 ON (m1.name = m2.name AND m1.id < m2.id)  
 WHERE m2.id IS NULL)

Чи є спосіб, можливо, зберегти висновок до змінної temp, а потім видалити з NOT IN (temp змінної)? @ Дякую за дуже корисне рішення.

EDIT: Подумайте, я знайшов рішення:

DROP TABLE IF EXISTS UniqueIDs; 
CREATE Temporary table UniqueIDs (id Int(11)); 

INSERT INTO UniqueIDs 
    (SELECT T1.ID FROM Table T1 LEFT JOIN Table T2 ON 
    (T1.Field1 = T2.Field1 AND T1.Field2 = T2.Field2 #Comparison Fields  
    AND T1.ID < T2.ID) 
    WHERE T2.ID IS NULL); 

DELETE FROM Table WHERE id NOT IN (SELECT ID FROM UniqueIDs);

2

Наведений нижче запит спрацює нормально відповідно до вашого питання.

SELECT M1.* 
FROM MESSAGES M1,
(
 SELECT SUBSTR(Others_data,1,2),MAX(Others_data) AS Max_Others_data
 FROM MESSAGES
 GROUP BY 1
) M2
WHERE M1.Others_data = M2.Max_Others_data
ORDER BY Others_data;

2

Якщо ви хочете останній рядок для кожного Name, ви можете вказати номер рядка для кожної групи рядків за Nameпорядком і впорядкувати Idв порядку зменшення.

ПИТАННЯ

SELECT t1.Id, 
       t1.Name, 
       t1.Other_Columns
FROM 
(
     SELECT Id, 
            Name, 
            Other_Columns,
    (
        CASE Name WHEN @curA 
        THEN @curRow := @curRow + 1 
        ELSE @curRow := 1 AND @curA := Name END 
    ) + 1 AS rn 
    FROM messages t, 
    (SELECT @curRow := 0, @curA := '') r 
    ORDER BY Name,Id DESC 
)t1
WHERE t1.rn = 1
ORDER BY t1.Id;

SQL Fiddle


2

Як щодо цього:

SELECT DISTINCT ON (name) *
FROM messages
ORDER BY name, id DESC;

У мене був аналогічний випуск (на postgresql жорсткий) і в таблиці 1М записів. Це рішення займає 1,7s проти 44s, вироблене тим, у кого вліво ПРИЄДНАЙТЕСЬ. У моєму випадку мені довелося відфільтрувати довідника вашого поля імен за значеннями NULL, що призвело до ще кращих показників на 0,2 сек.


1

Якщо продуктивність насправді викликає занепокоєння, ви можете ввести новий стовпець у таблиці під назвою IsLastInGroup типу BIT.

Встановіть його як істинне на останніх стовпцях і підтримуйте його при кожному вставці / оновленні / видаленні рядків. Пишеться буде повільніше, але ви отримаєте користь від читання. Це залежить від вашого випадку використання, і я рекомендую його, лише якщо ви зосереджені на читанні.

Так ваш запит буде виглядати так:

SELECT * FROM Messages WHERE IsLastInGroup = 1

У деяких таблицях Moodle є такий стовпець прапорця.
Лоуренс


0

Ви можете групуватись, рахуючи, а також отримувати останній елемент групи, наприклад:

SELECT 
    user,
    COUNT(user) AS count,
    MAX(id) as last
FROM request 
GROUP BY user

0

Сподіваємось, що запит Oracle може допомогти:

WITH Temp_table AS
(
    Select id, name, othercolumns, ROW_NUMBER() over (PARTITION BY name ORDER BY ID 
    desc)as rank from messages
)
Select id, name,othercolumns from Temp_table where rank=1

0

Інший підхід:

Знайдіть власність з максимальною площею m2_price для кожної програми (n властивостей у 1 програмі):

select * from properties p
join (
    select max(m2_price) as max_price 
    from properties 
    group by program_id
) p2 on (p.program_id = p2.program_id)
having p.m2_price = max_price
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.