[sql] 각 그룹의 상위 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   |

테이블은 DocumentIDDateCreated 내림차순 으로 정렬됩니다 . 각각에 대해 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

하루에 2 개의 항목이 필요한 경우 임의로 선택합니다. 하루에 두 항목을 모두 얻으려면 대신 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


답변

여기에서 다양한 권장 사항에 대한 몇 가지 타이밍을 수행했으며 결과는 실제로 관련된 테이블의 크기에 따라 다르지만 가장 일관된 솔루션은 CROSS APPLY를 사용하는 것입니다. 이러한 테스트는 SQL Server 2008-R2에서 6,500 개의 레코드와 1 억 3,700 만 개의 레코드가있는 다른 (동일한 스키마). 쿼리되는 열은 테이블의 기본 키의 일부이며 테이블 너비는 매우 작습니다 (약 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 APPLY의 시간이 얼마나 일관성이 있었는지 생각합니다.


답변

나는 이것이 오래된 스레드라는 것을 알고 있지만 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)

TOP 절에 대한 자세한 내용은 여기를 참조하십시오 .


답변

성능이 걱정된다면 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, DateCreatedDocuments 테이블에 이미 열 이 있으면 DocumentStatusLogs( 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 1overOver