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
query를 실행하면 다음 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
그러나 이것은 매우 비효율적입니다. 동일한 결과를 얻는 다른 방법이 있습니까?
답변
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 August 데이터 덤프 복사본이 있습니다. 벤치마킹에 사용하겠습니다. Posts
테이블 에는 1,114,357 개의 행이 있습니다 . Macbook Pro 2.40GHz의 MySQL 5.0.75에서 실행됩니다 .
주어진 사용자 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)
내 Posts
테이블 의 DDL은 다음과 같습니다 .
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, MySQL 버전 5.7.5 에서는 ONLY_FULL_GROUP_BY 스위치를 기본적으로 활성화했습니다 (따라서 비 결정적 GROUP BY 쿼리는 비활성화 됨). 또한 GROUP BY 구현을 업데이트했으며 비활성화 된 스위치로도 솔루션이 더 이상 예상대로 작동하지 않을 수 있습니다. 확인해야합니다.
작품 벌금 위 빌 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)에서 작동하더라도 언제 완료 될지 알 수 없습니다 .
동일한 아이디어를 기반으로하는 다른 솔루션이 몇 가지 있습니다.
- 기본 인덱스가 BTREE 인덱스 (일반적으로 경우) 인 경우 가장 큰
(group_id, item_value)
쌍은 각 내에서 마지막 값입니다 . 인덱스를 내림차순으로 걸면group_id
첫 번째 값입니다group_id
. - 인덱스로 커버되는 값을 읽으면 인덱스 순서대로 값을 읽습니다.
- 각 인덱스에는 암시 적으로 추가 된 기본 키 열이 포함됩니다 (즉, 기본 키는 적용 범위 인덱스에 있음). 아래 솔루션에서 기본 키에서 직접 작동하므로 결과에 기본 키 열을 추가하면됩니다.
- 많은 경우 하위 쿼리에서 필요한 행 ID를 필요한 순서로 수집하고 하위 쿼리의 결과를 id에 결합하는 것이 훨씬 저렴합니다. 하위 쿼리 결과의 각 행에 대해 MySQL은 기본 키를 기반으로 한 단일 가져 오기가 필요하므로 하위 쿼리가 조인에 먼저 들어가고 하위 쿼리의 ID 순서대로 행이 출력됩니다 (명시 적 ORDER BY를 생략 한 경우) 가입)
MySQL이 인덱스를 사용하는 3 가지 방법 은 몇 가지 세부 사항을 이해하기위한 훌륭한 기사입니다.
해결책 1
이것은 엄청나게 빠르며 18M + 행에서 약 0.8 초가 걸립니다.
SELECT test_id, MAX(request_id) AS request_id
FROM testresults
GROUP BY test_id DESC;
순서를 ASC로 변경하려면 하위 쿼리에 넣고 ID 만 반환하고 하위 쿼리로 사용하여 나머지 열에 조인하십시오.
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 개의 최대 행을 출력하는 방법을 알려줍니다.
쿼리의 단점은 쿼리 캐시로 결과를 캐시 할 수 없다는 것입니다.
답변
중간에 있으므로 하위 쿼리 를 사용하여 올바른 그룹화를 반환하십시오.
이 시도:
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
이렇게하면 매우 느리거나 비효율적 인 하위 쿼리의 상관 된 하위 쿼리 및 / 또는 순서를 피할 수 있습니다.
답변
다른 솔루션에 도달했습니다. 각 그룹의 마지막 게시물에 대한 ID를 얻은 다음 첫 번째 쿼리의 결과를 WHERE x IN
구문 의 인수로 사용하여 메시지 테이블에서 선택합니다 .
SELECT id, name, other_columns
FROM messages
WHERE id IN (
SELECT MAX(id)
FROM messages
GROUP BY name
);
다른 솔루션과 비교하여 이것이 어떻게 수행되는지는 모르지만 3 백만 행 이상의 테이블에서 훌륭하게 작동했습니다. (1200+ 결과로 4 초 실행)
이것은 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 *
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
답변
다음은 두 가지 제안입니다. 먼저, 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 순서를 의미합니다. 그렇지 않은 경우 ROW_NUMBER () 창의 ORDER BY 절을 적절히 변경하십시오. 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 메시지가없는 메시지를 선택하십시오.