각 그룹의 상위 1개 행 가져오기
저는 각 그룹별로 최신 엔트리를 받고 싶은 테이블이 있습니다.표는 다음과 같습니다.
DocumentStatusLogs
|ID| DocumentID | Status | DateCreated |
| 2| 1 | S1 | 7/29/2011 |
| 3| 1 | S2 | 7/30/2011 |
| 6| 1 | S1 | 8/02/2011 |
| 1| 2 | S1 | 7/28/2011 |
| 4| 2 | S2 | 7/30/2011 |
| 5| 2 | S3 | 8/01/2011 |
| 6| 3 | S1 | 8/02/2011 |
테이블은 다음 기준으로 그룹화됩니다.DocumentID
으로 DateCreated
내림차순으로각각DocumentID
저는 최신 상태를 얻고 싶습니다.
내가 선호하는 출력:
| DocumentID | Status | DateCreated |
| 1 | S1 | 8/02/2011 |
| 2 | S3 | 8/01/2011 |
| 3 | S1 | 8/02/2011 |
각 그룹에서 상위만 받는 집계 함수가 있습니까? 코드 참조
GetOnlyTheTop
아래:아래:아래:SELECT DocumentID, GetOnlyTheTop(Status), GetOnlyTheTop(DateCreated) FROM DocumentStatusLogs GROUP BY DocumentID ORDER BY DateCreated DESC
만약 그런 기능이 없다면, 제가 원하는 출력을 얻을 수 있는 방법은?
- 아니면 애초에, 이것이 비정상적인 데이터베이스에 의해 발생할 수 있습니까?생각해봤는데, 내가 찾고 있는 것은 단지 한 줄이기 때문에, 그것은
status
또한 상위 테이블에 있습니까?
자세한 내용은 상위 표를 참조하십시오.
현의재Documents
| DocumentID | Title | Content | DateCreated |
| 1 | TitleA | ... | ... |
| 2 | TitleB | ... | ... |
| 3 | TitleC | ... | ... |
상위 테이블은 상태에 쉽게 액세스할 수 있도록 이와 같아야 합니까?
| DocumentID | Title | Content | DateCreated | CurrentStatus |
| 1 | TitleA | ... | ... | s1 |
| 2 | TitleB | ... | ... | s3 |
| 3 | TitleC | ... | ... | s1 |
업데이트 "적용"을 사용하여 이러한 문제를 보다 쉽게 해결하는 방법을 방금 배웠습니다.
;WITH cte AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC) AS rn
FROM DocumentStatusLogs
)
SELECT *
FROM cte
WHERE rn = 1
하루에 두 개의 항목을 입력해야 하는 경우 임의로 하나를 선택합니다.하루 동안 두 항목을 모두 가져오려면 대신 DENSE_RANK를 사용합니다.
정규화 여부는 다음과 같은 작업을 원하는지 여부에 따라 결정됩니다.
- 2곳에서 상태를 유지합니다.
- 상태 기록 보존
- ...
현재 상태로는 상태 기록을 보존할 수 있습니다.상위 테이블에도 최신 상태(비정규화)를 적용하려면 상위 테이블에서 "상태"를 유지하기 위한 트리거가 필요합니다.또는 이 상태 기록 테이블을 삭제합니다.
방금 사용법을 배웠습니다.cross apply
이 시나리오에서 사용하는 방법은 다음과 같습니다.
select d.DocumentID, ds.Status, ds.DateCreated
from Documents as d
cross apply
(select top 1 Status, DateCreated
from DocumentStatusLogs
where DocumentID = d.DocumentId
order by DateCreated desc) as ds
인 것은 , 이오스것인알은지만레드래된이것,▁i만알▁but,TOP 1 WITH TIES
솔루션은 매우 유용하며 솔루션을 읽는 데 도움이 될 수 있습니다.
select top 1 with ties
DocumentID
,Status
,DateCreated
from DocumentStatusLogs
order by row_number() over (partition by DocumentID order by DateCreated desc)
그select top 1 with ties
절은 SQL Server에 그룹당 첫 번째 행을 반환할 것임을 알려줍니다.하지만 SQL Server는 데이터를 그룹화하는 방법을 어떻게 알고 있습니까?여기가 바로 그 곳입니다.order by row_number() over (partition by DocumentID order by DateCreated desc
들니다열열. 다음/옵 뒤에 있는 partition by
SQL Server가 데이터를 그룹화하는 방법을 정의합니다.각 그룹 내에서 행은 다음을 기준으로 정렬됩니다.order by
의 맨 위 이 쿼리로 됩니다.정렬되면 각 그룹의 맨 위 행이 쿼리로 반환됩니다.
TOP 조항에 대한 자세한 내용은 여기에서 확인할 수 있습니다.
여기에 나와 있는 다양한 권장 사항에 대해 몇 가지 시간을 할애했는데, 결과는 관련된 테이블의 크기에 따라 다르지만 가장 일관된 솔루션은 CROSS APPLE을 사용하는 것입니다. 이 테스트는 6,500개의 레코드가 있는 테이블과 1억 3,700만 개의 레코드가 있는 다른 테이블(동일 스키마)을 사용하여 SQL Server 2008-R2에 대해 실행되었습니다.쿼리되는 열은 테이블의 기본 키의 일부이며 테이블 너비는 매우 작습니다(약 30바이트).실제 실행 계획에서 시간이 SQL Server에 의해 보고됩니다.
Query Time for 6500 (ms) Time for 137M(ms)
CROSS APPLY 17.9 17.9
SELECT WHERE col = (SELECT MAX(COL)…) 6.6 854.4
DENSE_RANK() OVER PARTITION 6.6 907.1
정말 놀라운 것은 관련된 행의 수에 관계없이 CROSS APPLE의 시간이 얼마나 일관적이었는지였다고 생각합니다.
성능이 걱정되는 경우 이 기능을 사용할 수도 있습니다.MAX()
:
SELECT *
FROM DocumentStatusLogs D
WHERE DateCreated = (SELECT MAX(DateCreated) FROM DocumentStatusLogs WHERE ID = D.ID)
ROW_NUMBER()
에는 당의모행종필다요니합에 합니다.SELECT
에 면에술, 진반.MAX
으로 수 있을 것 같습니다.질문 속도를 대폭 높여야 합니다.
SELECT * FROM
DocumentStatusLogs JOIN (
SELECT DocumentID, MAX(DateCreated) DateCreated
FROM DocumentStatusLogs
GROUP BY DocumentID
) max_date USING (DocumentID, DateCreated)
어떤 데이터베이스 서버?이 코드가 모든 것에 적용되는 것은 아닙니다.
당신의 질문의 후반부에 대해서는, 저는 현황을 칼럼으로 포함하는 것이 타당하다고 생각합니다. 됩니다DocumentStatusLogs
로그로 저장하지만 최신 정보는 기본 테이블에 최신 정보를 저장합니다.
BTW를 은 만당신이가있다면고지미.DateCreated
은 Documents "Documents"에 가입하면 됩니다.DocumentStatusLogs
하는 것 (그을사한것는하용안 ()안▁as것▁long▁(DateCreated
에서 고유합니다.DocumentStatusLogs
).
편집: MsSQL은 USING을 지원하지 않으므로 다음으로 변경합니다.
ON DocumentStatusLogs.DocumentID = max_date.DocumentID AND DocumentStatusLogs.DateCreated = max_date.DateCreated
이것은 주제에서 가장 쉽게 찾을 수 있는 질문 중 하나이므로, 저는 그것에 대해 현대적인 대답을 하고 싶었습니다(제가 참고하고 다른 사람들을 돕기 위해).을 사용하여first_value
그리고.over
위의 쿼리를 간단히 처리할 수 있습니다.
Select distinct DocumentID
, first_value(status) over (partition by DocumentID order by DateCreated Desc) as Status
, first_value(DateCreated) over (partition by DocumentID order by DateCreated Desc) as DateCreated
From DocumentStatusLogs
이 작업은 SQL Server 2008 이상에서 작동합니다. First_value
을 성취하는 방법으로 생각할 수 있습니다.Select Top 1
를 할 때over
절 Over
선택 목록에서 그룹화할 수 있으므로 중첩된 하위 쿼리를 작성하는 대신(기존의 많은 답변과 마찬가지로) 보다 읽기 쉬운 방식으로 그룹화할 수 있습니다.이게 도움이 되길 바랍니다.
다음은 각 쿼리에 대한 최상의 인덱싱 옵션과 함께 당면한 문제에 대한 세 가지 개별적인 접근 방식입니다(인덱스를 직접 사용해 보고 논리적 읽기, 경과 시간, 실행 계획을 확인하십시오).저는 이 특정 문제에 대해 실행하지 않고 이러한 쿼리에 대한 제 경험에서 제안을 제공했습니다.).
접근 1: ROW_NUMBER() 사용.행 저장소 인덱스가 성능을 향상시킬 수 없는 경우 집계 및 그룹화가 포함된 쿼리의 경우 비클러스터형/클러스터형 열 저장소 인덱스를 사용해 볼 수 있으며, 항상 다른 열에 있는 테이블의 경우 열 저장소 인덱스를 사용하는 것이 일반적으로 가장 좋습니다.
;WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
FROM DocumentStatusLogs
)
SELECT ID
,DocumentID
,Status
,DateCreated
FROM CTE
WHERE RN = 1;
접근 2: FIRST_VALUE 사용.행 저장소 인덱스가 성능을 향상시킬 수 없는 경우 집계 및 그룹화가 포함된 쿼리의 경우 비클러스터형/클러스터형 열 저장소 인덱스를 사용해 볼 수 있으며, 항상 다른 열에 있는 테이블의 경우 열 저장소 인덱스를 사용하는 것이 일반적으로 가장 좋습니다.
SELECT DISTINCT
ID = FIRST_VALUE(ID) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
,DocumentID
,Status = FIRST_VALUE(Status) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
,DateCreated = FIRST_VALUE(DateCreated) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
FROM DocumentStatusLogs;
접근 3: CROSS APPLE 사용.쿼리에 사용되는 열을 포함하는 DocumentStatusLogs 테이블에 행 저장소 인덱스를 만들면 열 저장소 인덱스가 필요 없이 쿼리를 포함하기에 충분합니다.
SELECT DISTINCT
ID = CA.ID
,DocumentID = D.DocumentID
,Status = CA.Status
,DateCreated = CA.DateCreated
FROM DocumentStatusLogs D
CROSS APPLY (
SELECT TOP 1 I.*
FROM DocumentStatusLogs I
WHERE I.DocumentID = D.DocumentID
ORDER BY I.DateCreated DESC
) CA;
이것은 꽤 오래된 이야기입니다. 하지만 저는 제 의견에 동의한 답이 저에게 특별히 잘 먹히지 않는 것과 똑같이 2센트를 투자해야겠다고 생각했습니다.대규모 데이터 세트에서 gbn의 솔루션을 사용해 보니 속도가 엄청나게 느렸습니다(SQL Server 2012에서는 500만 개 이상의 레코드에서 45초 이상).실행 계획을 보면 SORT 작업이 필요하여 작업 속도가 크게 느려지는 것이 문제임이 분명합니다.
다음은 SORT 작업이 필요하지 않고 비클러스터형 인덱스 검색을 수행하는 엔티티 프레임워크에서 가져온 대안입니다.이렇게 하면 위의 레코드 세트에서 실행 시간이 2초 미만으로 줄어듭니다.
SELECT
[Limit1].[DocumentID] AS [DocumentID],
[Limit1].[Status] AS [Status],
[Limit1].[DateCreated] AS [DateCreated]
FROM (SELECT DISTINCT [Extent1].[DocumentID] AS [DocumentID] FROM [dbo].[DocumentStatusLogs] AS [Extent1]) AS [Distinct1]
OUTER APPLY (SELECT TOP (1) [Project2].[ID] AS [ID], [Project2].[DocumentID] AS [DocumentID], [Project2].[Status] AS [Status], [Project2].[DateCreated] AS [DateCreated]
FROM (SELECT
[Extent2].[ID] AS [ID],
[Extent2].[DocumentID] AS [DocumentID],
[Extent2].[Status] AS [Status],
[Extent2].[DateCreated] AS [DateCreated]
FROM [dbo].[DocumentStatusLogs] AS [Extent2]
WHERE ([Distinct1].[DocumentID] = [Extent2].[DocumentID])
) AS [Project2]
ORDER BY [Project2].[ID] DESC) AS [Limit1]
지금 저는 원래 질문에 완전히 명시되지 않은 것을 가정하고 있습니다만, 만약 당신의 표 디자인이 당신의 ID 열이 자동 증분 ID이고, 작성 날짜가 각 삽입마다 현재 날짜로 설정되어 있다면,그러면 위의 쿼리로 실행하지 않아도 DateCreated에서 주문하는 대신 ID로 주문하는 것만으로도 gbn의 솔루션에 상당한 성능 향상(실행 시간의 약 절반)을 얻을 수 있습니다. 이렇게 하면 동일한 정렬 순서가 제공되고 더 빠른 정렬이 가능하기 때문입니다.
각 그룹에서 상위 1개를 선택하는 내 코드
#DocumentStatusLogsa에서 a.*를 선택합니다.생성된 날짜(#DocumentStatusLogs b에서 생성된 상위 1개 날짜 선택)어디에a.computid = b.computid생성된 날짜별 주문 desc)
이 솔루션을 사용하여 각 파티션의 최신 행 상위 N개를 가져올 수 있습니다(예: WHERE 문에서 N은 1이고 파티션은 doc_id).
SELECT T.doc_id, T.status, T.date_created FROM
(
SELECT a.*, ROW_NUMBER() OVER (PARTITION BY doc_id ORDER BY date_created DESC) AS rnk FROM doc a
) T
WHERE T.rnk = 1;
CROSS APPLY
솔루션이 저와 고객의 필요에 따라 사용한 방법입니다.그리고 제가 읽은 바로는 데이터베이스가 크게 증가할 경우 전체적으로 최고의 성능을 제공해야 합니다.
위에서 Clint의 멋지고 정확한 답변 확인:
아래 두 쿼리 간의 성능이 흥미롭습니다.52%가 1위입니다.그리고 48%가 두 번째입니다.ORDER BY 대신 DISTINCT를 사용하여 성능 4% 향상그러나 ORDER BY는 여러 열을 기준으로 정렬할 수 있다는 장점이 있습니다.
IF (OBJECT_ID('tempdb..#DocumentStatusLogs') IS NOT NULL) BEGIN DROP TABLE #DocumentStatusLogs END
CREATE TABLE #DocumentStatusLogs (
[ID] int NOT NULL,
[DocumentID] int NOT NULL,
[Status] varchar(20),
[DateCreated] datetime
)
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (2, 1, 'S1', '7/29/2011 1:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (3, 1, 'S2', '7/30/2011 2:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (6, 1, 'S1', '8/02/2011 3:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (1, 2, 'S1', '7/28/2011 4:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (4, 2, 'S2', '7/30/2011 5:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (5, 2, 'S3', '8/01/2011 6:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (6, 3, 'S1', '8/02/2011 7:00:00')
옵션 1:
SELECT
[Extent1].[ID],
[Extent1].[DocumentID],
[Extent1].[Status],
[Extent1].[DateCreated]
FROM #DocumentStatusLogs AS [Extent1]
OUTER APPLY (
SELECT TOP 1
[Extent2].[ID],
[Extent2].[DocumentID],
[Extent2].[Status],
[Extent2].[DateCreated]
FROM #DocumentStatusLogs AS [Extent2]
WHERE [Extent1].[DocumentID] = [Extent2].[DocumentID]
ORDER BY [Extent2].[DateCreated] DESC, [Extent2].[ID] DESC
) AS [Project2]
WHERE ([Project2].[ID] IS NULL OR [Project2].[ID] = [Extent1].[ID])
옵션 2:
SELECT
[Limit1].[DocumentID] AS [ID],
[Limit1].[DocumentID] AS [DocumentID],
[Limit1].[Status] AS [Status],
[Limit1].[DateCreated] AS [DateCreated]
FROM (
SELECT DISTINCT [Extent1].[DocumentID] AS [DocumentID] FROM #DocumentStatusLogs AS [Extent1]
) AS [Distinct1]
OUTER APPLY (
SELECT TOP (1) [Project2].[ID] AS [ID], [Project2].[DocumentID] AS [DocumentID], [Project2].[Status] AS [Status], [Project2].[DateCreated] AS [DateCreated]
FROM (
SELECT
[Extent2].[ID] AS [ID],
[Extent2].[DocumentID] AS [DocumentID],
[Extent2].[Status] AS [Status],
[Extent2].[DateCreated] AS [DateCreated]
FROM #DocumentStatusLogs AS [Extent2]
WHERE [Distinct1].[DocumentID] = [Extent2].[DocumentID]
) AS [Project2]
ORDER BY [Project2].[ID] DESC
) AS [Limit1]
Microsoft SQL Server Management Studio에서: 첫 번째 블록을 강조 표시하고 실행한 후 옵션 1과 옵션 2를 모두 강조 표시하고 -> [예상 실행 계획 표시]를 마우스 오른쪽 버튼으로 클릭합니다.그런 다음 전체를 실행하여 결과를 확인합니다.
옵션 1 결과:
ID DocumentID Status DateCreated
6 1 S1 8/2/11 3:00
5 2 S3 8/1/11 6:00
6 3 S1 8/2/11 7:00
옵션 2 결과:
ID DocumentID Status DateCreated
6 1 S1 8/2/11 3:00
5 2 S3 8/1/11 6:00
6 3 S1 8/2/11 7:00
참고:
저는 가입을 1대 1로 하고 싶을 때 APPLY를 사용하는 경향이 있습니다.
조인을 1대 다 또는 다대 다로 하려면 조인을 사용합니다.
고급 작업이 필요하고 윈도우 설정 성능 패널티가 문제가 되지 않는 한 ROW_NUMBER()가 있는 CTE는 사용하지 않습니다.
저는 또한 WHERE 또는 ON 절의 하위 쿼리에 있는 / 존재하는 것을 피합니다. 왜냐하면 저는 이것이 끔찍한 실행 계획을 야기하는 것을 경험했기 때문입니다.하지만 주행거리는 다양합니다.실행 계획을 검토하고 필요할 때 성능을 프로파일링합니다!
SELECT o.*
FROM `DocumentStatusLogs` o
LEFT JOIN `DocumentStatusLogs` b
ON o.DocumentID = b.DocumentID AND o.DateCreated < b.DateCreated
WHERE b.DocumentID is NULL ;
작성일까지 최근 문서 주문만 반환하려면 문서별 상위 1개 문서만 반환합니다.아이디
저는 이것이 바로 이렇게 될 수 있다고 믿습니다.조정이 필요할 수 있지만 그룹에서 최대값을 선택하면 됩니다.
이 대답들은 과잉 살상입니다.
SELECT
d.DocumentID,
MAX(d.Status),
MAX(d1.DateCreated)
FROM DocumentStatusLogs d, DocumentStatusLogs d1
USING DocumentID
GROUP BY 1
ORDER BY 3 DESC
row_count()를 사용하지 않으려는 시나리오에서는 왼쪽 조인을 사용할 수도 있습니다.
select ds.DocumentID, ds.Status, ds.DateCreated
from DocumentStatusLogs ds
left join DocumentStatusLogs filter
ON ds.DocumentID = filter.DocumentID
-- Match any row that has another row that was created after it.
AND ds.DateCreated < filter.DateCreated
-- then filter out any rows that matched
where filter.DocumentID is null
예제 스키마의 경우 일반적으로 왼쪽 조인과 동일한 출력으로 컴파일되는 "not in subquery"를 사용할 수도 있습니다.
select ds.DocumentID, ds.Status, ds.DateCreated
from DocumentStatusLogs ds
WHERE ds.ID NOT IN (
SELECT filter.ID
FROM DocumentStatusLogs filter
WHERE ds.DocumentID = filter.DocumentID
AND ds.DateCreated < filter.DateCreated)
테이블에 하나 이상의 단일 열 고유 키/제약 조건/색인이 없으면 하위 쿼리 패턴이 작동하지 않습니다. 이 경우 기본 키 "Id".
이 두 쿼리 모두 row_count() 쿼리보다 "비싼" 경향이 있습니다(쿼리 분석기에서 측정).그러나 결과를 더 빨리 반환하거나 다른 최적화를 사용하도록 설정하는 시나리오가 발생할 수 있습니다.
SELECT documentid,
status,
datecreated
FROM documentstatuslogs dlogs
WHERE status = (SELECT status
FROM documentstatuslogs
WHERE documentid = dlogs.documentid
ORDER BY datecreated DESC
LIMIT 1)
일부 데이터베이스 엔진*은 다음을 지원하기 시작했습니다.QUALIFY
허용된 응답이 사용하는 창 함수의 결과를 필터링할 수 있는 절입니다.
그래서 받아들여진 답은
SELECT *, ROW_NUMBER() OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC) AS rn
FROM DocumentStatusLogs
QUALIFY rn = 1
자세한 설명은 다음 문서를 참조하십시오. https://jrandrews.net/the-joy-of-qualify
이 도구를 사용하여 이 절을 지원하는 데이터베이스를 확인할 수 있습니다. https://www.jooq.org/translate/ 대상 방언이 지원하지 않는 경우 한정 절을 변환하는 옵션이 있습니다.
*테라데이터, 빅쿼리, H2, 눈송이...
사용해 보십시오.
SELECT [DocumentID]
,[tmpRez].value('/x[2]', 'varchar(20)') AS [Status]
,[tmpRez].value('/x[3]', 'datetime') AS [DateCreated]
FROM (
SELECT [DocumentID]
,cast('<x>' + max(cast([ID] AS VARCHAR(10)) + '</x><x>' + [Status] + '</x><x>' + cast([DateCreated] AS VARCHAR(20))) + '</x>' AS XML) AS [tmpRez]
FROM DocumentStatusLogs
GROUP BY DocumentID
) AS [tmpQry]
언급URL : https://stackoverflow.com/questions/6841605/get-top-1-row-of-each-group
'programing' 카테고리의 다른 글
Node.js로 현재 운영 체제를 확인하는 방법 (0) | 2023.05.27 |
---|---|
다중 행 문자열 변수 (0) | 2023.05.27 |
각도 2: 반응형 폼 컨트롤을 반복합니다. (0) | 2023.05.27 |
비공유 멤버에 대한 참조를 사용하려면 공용 하위를 호출할 때 개체 참조가 발생해야 합니다. (0) | 2023.05.27 |
배열에 항목을 추가하는 가장 빠른 방법 (0) | 2023.05.27 |