Существует таблица 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
Но это выглядит очень неэффективно. Любые другие способы достижения такого же результата?
Я пишу решение таким образом:
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 August. Я буду использовать это для бенчмаркинга. В таблице Posts
имеется 1114357 строк. Это работает на MySQL 5.0.75 на моем MacBook Pro 2.40 ГГц.
Я напишу запрос, чтобы найти самую последнюю запись для данного ID пользователя (мой).
Сначала используя , используя @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;
UPD: 2017-03-31, версия 5.7.5 MySQL включила ONLY_FULL_GROUP_BY переключатель по умолчанию (следовательно, не детерминированный Запросы GROUP BY отключены). Более того, они обновили реализацию GROUP BY, и решение может работать не так, как ожидалось, даже с отключенным коммутатором. Нужно проверить.
Решение Bill Karwin выше работает отлично, когда количество элементов внутри групп довольно мало, но производительность запроса становится плохой, когда группы довольно велики, так как для решения требуется около n*n/2 + n/2
только сравнений IS NULL
.
Я провел тесты в таблице InnoDB строк 18684446
с группами 1182
. Таблица содержит тестовые результаты для функциональных тестов и имеет (test_id, request_id)
в качестве первичного ключа. Таким образом, test_id
является группой, и я искал последний request_id
для каждого test_id
.
Решение Bill уже работает несколько часов на моем dell e4310, и я не знаю, когда он закончит, даже если он работает с индексом покрытия (следовательно, using index
в EXPLAIN).
У меня есть несколько других решений, основанных на тех же идеях:
(group_id, item_value)
- это последнее значение в каждом group_id
, которое является первым для каждого group_id
, если мы пройдем по индексу в по убыванию;3 способа использования индексов MySQL - отличная статья, чтобы понять некоторые детали.
Решение 1
Это невероятно быстро, он занимает около 0,8 секунды на моих 18M + строках:
SELECT test_id, MAX(request_id), request_id
FROM testresults
GROUP BY test_id DESC;
Если вы хотите изменить заказ на ASC, поместите его в подзапрос, верните только идентификаторы и используйте это как подзапрос, чтобы присоединиться к остальным столбцам:
SELECT test_id, request_id
FROM (
SELECT test_id, MAX(request_id), 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 максимальных строк для каждой группы.
Недостатком запроса является то, что его результат не может быть кэширован кэшем запросов.
Используйте подзапрос, чтобы вернуть правильную группировку, потому что вы на полпути.
Попробуйте следующее:
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
Таким образом, вы избегаете коррелированных подзапросов и/или упорядочивания в ваших подзапросах, которые, как правило, очень медленны/неэффективны.
other_col
: если этот столбец не уникален, вы можете получить несколько записей с одним и тем же name
, если они связываются для max(other_col)
. Я нашел этот пост, который описывает решение для моих нужд, где мне нужно ровно одна запись на name
.
Я пришел к другому решению, которое должно получить идентификаторы для последнего сообщения в каждой группе, а затем выбрать из таблицы сообщений, используя результат из первого запроса в качестве аргумента для конструкции 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.
Решение по подзапросу скрипка Ссылка
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 уже предоставлен в других ответах.
Я еще не тестировал большую БД, но я думаю, что это может быть быстрее, чем объединение таблиц:
SELECT *, Max(Id) FROM messages GROUP BY Name
Вот два предложения. Во-первых, если 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. Если нет, измените предложение 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 с тем же именем.
Вот мое решение:
SELECT
DISTINCT NAME,
MAX(MESSAGES) OVER(PARTITION BY NAME) MESSAGES
FROM MESSAGE;
SELECT
column1,
column2
FROM
table_name
WHERE id IN
(SELECT
MAX(id)
FROM
table_name
GROUP BY column1)
ORDER BY column1 ;
Вы также можете посмотреть и здесь.
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 ;
Вот еще один способ получить последнюю связанную запись с помощью 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
над этим списком, выберем первый
Попробуйте следующее:
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 )
Привет @Vijay Dev, если в вашей таблице сообщения содержится Id, который автоматически увеличивает первичный ключ, а затем для получения последней записи основы первичного ключа, который ваш запрос должен читать, как показано ниже
SELECT m1.* FROM messages m1 INNER JOIN (SELECT max(Id) as lastmsgId FROM messages GROUP BY Name) m2 ON m1.Id=m2.lastmsgId
Следующий запрос будет работать в соответствии с вашим вопросом.
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;
Можно ли использовать этот метод для удаления дубликатов в таблице? Набор результатов в основном представляет собой набор уникальных записей, поэтому, если мы можем удалить все записи не в результирующем наборе, у нас фактически не будет дубликатов? Я пробовал это, но 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 (временная переменная)? @Bill благодарит за очень полезное решение.
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);
select * from messages group by name desc
Если вам нужна последняя строка для каждого Name
, вы можете присвоить номер строки каждой группе строк Name
и упорядочить по Id
в порядке убывания.
QUERY
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;
Как насчет этого:
SELECT DISTINCT ON (name) *
FROM messages
ORDER BY name, id DESC;
У меня была аналогичная проблема (на postgresql tough) и на таблице записей 1M. Это решение занимает 1,7 с против 44, созданного с помощью LEFT JOIN. В моем случае мне пришлось отфильтровать корреспондент вашего поля имени со значениями NULL, что привело к еще лучшим результатам на 0,2 секунды