Nhận 1 hàng đầu của mỗi nhóm


528

Tôi có một bảng mà tôi muốn nhận mục mới nhất cho mỗi nhóm. Đây là bảng:

DocumentStatusLogs Bàn

|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   |

Bảng sẽ được nhóm theo DocumentIDvà sắp xếp theo DateCreatedthứ tự giảm dần. Đối với mỗi DocumentID, tôi muốn có được trạng thái mới nhất.

Đầu ra ưa thích của tôi:

| DocumentID | Status | DateCreated |
| 1          | S1     | 8/02/2011   |
| 2          | S3     | 8/01/2011   |
| 3          | S1     | 8/02/2011   |
  • Có bất kỳ chức năng tổng hợp để chỉ nhận đầu từ mỗi nhóm? Xem mã giả GetOnlyTheTopdưới đây:

    SELECT
      DocumentID,
      GetOnlyTheTop(Status),
      GetOnlyTheTop(DateCreated)
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ORDER BY DateCreated DESC
    
  • Nếu chức năng đó không tồn tại, có cách nào tôi có thể đạt được đầu ra tôi muốn không?

  • Hoặc tại nơi đầu tiên, điều này có thể được gây ra bởi cơ sở dữ liệu không chuẩn hóa? Tôi đang nghĩ, vì những gì tôi đang tìm kiếm chỉ là một hàng, nên nó statuscũng được đặt trong bảng cha mẹ?

Vui lòng xem bảng cha để biết thêm thông tin:

DocumentsBảng hiện tại

| DocumentID | Title  | Content  | DateCreated |
| 1          | TitleA | ...      | ...         |
| 2          | TitleB | ...      | ...         |
| 3          | TitleC | ...      | ...         |

Bảng cha có nên như thế này để tôi có thể dễ dàng truy cập trạng thái của nó không?

| DocumentID | Title  | Content  | DateCreated | CurrentStatus |
| 1          | TitleA | ...      | ...         | s1            |
| 2          | TitleB | ...      | ...         | s3            |
| 3          | TitleC | ...      | ...         | s1            |

CẬP NHẬT Tôi mới học cách sử dụng "ứng dụng" giúp giải quyết các vấn đề như vậy dễ dàng hơn.


2
Để thảo luận chi tiết hơn và so sánh các giải pháp có thể, tôi khuyên bạn nên đọc câu hỏi tương tự trên dba.se: Truy xuất n hàng trên mỗi nhóm .
Vladimir Baranov

Tôi nhìn vào bài viết và thử nó. Sử dụng nhóm của StoreID đã tạo ra một lỗi.
UltraJ

Câu trả lời:


755
;WITH cte AS
(
   SELECT *,
         ROW_NUMBER() OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC) AS rn
   FROM DocumentStatusLogs
)
SELECT *
FROM cte
WHERE rn = 1

Nếu bạn mong đợi 2 mục mỗi ngày, thì điều này sẽ tùy ý chọn một mục. Để có được cả hai mục trong một ngày, hãy sử dụng DENSE_RANK thay thế

Đối với bình thường hóa hay không, nó phụ thuộc nếu bạn muốn:

  • duy trì trạng thái ở 2 nơi
  • lưu giữ lịch sử trạng thái
  • ...

Khi nó đứng, bạn bảo tồn lịch sử trạng thái. Nếu bạn cũng muốn trạng thái mới nhất trong bảng cha mẹ (đó là sự không chuẩn hóa), bạn cần một trình kích hoạt để duy trì "trạng thái" trong cha mẹ. hoặc thả bảng lịch sử trạng thái này.


5
Và ... là Partition Bygì? Withtôi cũng mới :( Tôi đang sử dụng mssql 2005.
dpp

6
@domanokz: Phân vùng bằng cách đặt lại số đếm. Vì vậy, trong trường hợp này, nó nói sẽ tính theo DocumentID
gbn

1
Hừm, tôi lo lắng về hiệu suất, tôi sẽ truy vấn hàng triệu hàng. CHỌN * TỪ (CHỌN ...) có ảnh hưởng đến hiệu suất không? Ngoài ra, có phải là ROW_NUMBERmột loại truy vấn con cho mỗi hàng?
dpp

1
@domanokz: không, nó không phải là một câu hỏi phụ. Nếu bạn có chỉ số chính xác thì hàng triệu không nên là một vấn đề. Dù sao, chỉ có 2 cách dựa trên tập hợp: cách này và tổng hợp (giải pháp của Ariel). Vì vậy, hãy thử cả hai ...
gbn

1
@domanokz: Chỉ cần thay đổi ĐẶT HÀNG B DateNG DateCreated DESC thành ORDER BY ID DESC
gbn

184

Tôi chỉ học cách sử dụng cross apply. Đây là cách sử dụng nó trong kịch bản này:

 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

2
Điều đó thực sự không có sự khác biệt vì vấn đề vẫn được giải quyết.
dpp

19
Tôi vừa đăng kết quả kiểm tra thời gian của tôi chống lại tất cả các giải pháp được đề xuất và bạn đã đưa ra đầu trang. Cho bạn một phiếu bầu lên :-)
John Fairbanks

3
+1 để cải thiện tốc độ rất lớn. Điều này nhanh hơn nhiều so với chức năng cửa sổ, chẳng hạn như ROW_NUMBER (). Sẽ thật tuyệt nếu SQL nhận ra ROW_NUMBER () = 1 như các truy vấn và tối ưu hóa chúng thành Áp dụng. Lưu ý: Tôi đã sử dụng OUTER ỨNG DỤNG khi tôi cần kết quả, ngay cả khi chúng không tồn tại trong ứng dụng.
TamusJRoyce

8
@TamusJRoyce bạn không thể ngoại suy điều đó chỉ vì nó nhanh hơn một khi điều này luôn luôn như vậy. Nó phụ thuộc. Như được mô tả ở đây sqlmag.com/database-development/optimizing-top-n-group-queries
Martin Smith

2
Nhận xét của tôi là về việc có nhiều hàng và chỉ mong muốn một trong nhiều hàng đó cho mỗi nhóm. Tham gia là khi bạn muốn một đến nhiều. Áp dụng cho khi bạn có một đến nhiều, nhưng muốn lọc ra tất cả ngoại trừ một đến một. Kịch bản: Đối với 100 thành viên, hãy cho tôi mỗi số điện thoại tốt nhất của họ (trong đó mỗi số có thể có nhiều số). Đây là nơi Áp dụng vượt trội. Ít đọc hơn = ít truy cập đĩa hơn = hiệu suất tốt hơn. Theo kinh nghiệm của tôi là với cơ sở dữ liệu không chuẩn hóa được thiết kế kém.
TamusJRoyce

53

Tôi đã thực hiện một số thời gian qua các đề xuất khác nhau ở đây và kết quả thực sự phụ thuộc vào kích thước của bảng có liên quan, nhưng giải pháp phù hợp nhất là sử dụng CROSS ỨNG DỤNG Các thử nghiệm này được chạy với SQL Server 2008-R2, sử dụng bảng với 6.500 hồ sơ và một bản ghi khác (lược đồ giống hệt nhau) với 137 triệu bản ghi. Các cột được truy vấn là một phần của khóa chính trên bảng và chiều rộng của bảng rất nhỏ (khoảng 30 byte). Thời gian được SQL Server báo cáo từ kế hoạch thực hiện thực tế.

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

Tôi nghĩ rằng điều thực sự tuyệt vời là thời gian phù hợp cho CROSS ỨNG DỤNG bất kể số lượng hàng liên quan như thế nào.


8
Tất cả phụ thuộc vào phân phối dữ liệu và các chỉ mục có sẵn. Nó đã được thảo luận tại độ dài lớn trên dba.se .
Vladimir Baranov

48

Tôi biết đây là một chủ đề cũ nhưng các TOP 1 WITH TIESgiải pháp khá hay và có thể hữu ích cho một số người đọc qua các giải pháp.

select top 1 with ties
   DocumentID
  ,Status
  ,DateCreated
from DocumentStatusLogs
order by row_number() over (partition by DocumentID order by DateCreated desc)

Thông tin thêm về mệnh đề TOP có thể được tìm thấy ở đây .


7
Đây là giải pháp thanh lịch nhất imo
George Menoutis 16/10/18

1
đã đồng ý - điều này sao chép tốt nhất những gì rất dễ thực hiện trong các phiên bản khác của SQL và các ngôn ngữ khác imo
Chris Umphlett

27

Nếu bạn lo lắng về hiệu suất, bạn cũng có thể làm điều này với MAX ():

SELECT *
FROM DocumentStatusLogs D
WHERE DateCreated = (SELECT MAX(DateCreated) FROM DocumentStatusLogs WHERE ID = D.ID)

ROW_NUMBER () yêu cầu một loại tất cả các hàng trong câu lệnh CHỌN của bạn, trong khi MAX thì không. Nên tăng tốc độ truy vấn của bạn.


2
Các vấn đề về hiệu năng với ROW_NUMBER () có thể được giải quyết với việc lập chỉ mục thích hợp không? (Tôi cảm thấy điều đó nên được thực hiện bằng mọi cách)
Kristoffer L

8
Với datetime, bạn không thể đảm bảo hai mục nhập sẽ không được thêm vào cùng ngày và giờ. Độ chính xác không đủ cao.
TamusJRoyce

+1 cho đơn giản. @TamusJRoyce nói đúng. Thế còn? 'select * từ DocumentStatusLog D trong đó ID = (chọn ID từ DocumentsStatusLog trong đó D.DocumentID = DocumentID đặt hàng theo giới hạn DESC của DateCreated 1);'
cibercitizen1

SELECT * FROM EventScheduleTbl D Ở ĐÂU DatesPicked = (SELECT top 1 phút (DatesPicked) TỪ EventScheduleTbl ĐÂU EventIDf = D.EventIDf và DatesPicked> = convert (ngày, getdate ()))
Arun Prasad ES

Chắc chắn có những trường hợp điều này sẽ tốt hơn row_number()ngay cả với việc lập chỉ mục thích hợp. Tôi thấy nó đặc biệt có giá trị trong các kịch bản tự tham gia. Mặc dù vậy, điều cần nhận thức là phương pháp này thường sẽ mang lại số lượng đọc và quét logic cao hơn, mặc dù báo cáo chi phí cây con thấp. Bạn sẽ cần cân nhắc chi phí / lợi ích trong trường hợp cụ thể của mình để xác định xem nó có thực sự tốt hơn không.
pimbrouwers

26
SELECT * FROM
DocumentStatusLogs JOIN (
  SELECT DocumentID, MAX(DateCreated) DateCreated
  FROM DocumentStatusLogs
  GROUP BY DocumentID
  ) max_date USING (DocumentID, DateCreated)

Máy chủ cơ sở dữ liệu nào? Mã này không hoạt động trên tất cả chúng.

Về nửa sau của câu hỏi của bạn, có vẻ hợp lý với tôi để bao gồm trạng thái như một cột. Bạn có thể để lại DocumentStatusLogsnhư một bản ghi, nhưng vẫn lưu trữ thông tin mới nhất trong bảng chính.

BTW, nếu bạn đã có DateCreatedcột trong bảng Tài liệu, bạn chỉ có thể tham gia DocumentStatusLogsbằng cách sử dụng (miễn DateCreatedlà duy nhất trong DocumentStatusLogs).

Chỉnh sửa: MsQuery không hỗ trợ SỬ DỤNG, vì vậy hãy đổi nó thành:

ON DocumentStatusLogs.DocumentID = max_date.DocumentID AND DocumentStatusLogs.DateCreated = max_date.DateCreated

5
Manh mối nằm trong tiêu đề: MSSQL. SQL Server không có SỬ DỤNG nhưng ý tưởng vẫn ổn.
gbn

7
@gbn Người điều hành ngu ngốc thường xóa các từ khóa quan trọng khỏi tiêu đề, như họ đã làm ở đây. Làm cho rất khó để tìm câu trả lời chính xác trong kết quả tìm kiếm hoặc Google.
NickG

2
Jus chỉ ra rằng "giải pháp" này vẫn có thể cung cấp cho bạn nhiều hồ sơ nếu bạn có một chiếc cà vạt trênmax(DateCreated)
MoonKnight

12

Đây là một trong những câu hỏi dễ tìm thấy nhất về chủ đề này, vì vậy tôi muốn đưa ra một câu trả lời hiện đại cho nó (cả để tôi tham khảo và giúp đỡ người khác). Bằng cách sử dụng first_valueoverbạn có thể thực hiện công việc ngắn của truy vấn trên:

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

Điều này sẽ hoạt động trong Sql Server 2008 trở lên. First_valuecó thể được coi là một cách để hoàn thành Select Top 1khi sử dụng một overmệnh đề. Overcho phép nhóm trong danh sách chọn để thay vì viết các truy vấn con lồng nhau (giống như nhiều câu trả lời hiện có), điều này thực hiện theo cách dễ đọc hơn. Hi vọng điêu nay co ich.


2
Điều này không hoạt động trong SQL Server 2008 R2. Tôi nghĩ First_value đã được giới thiệu vào năm 2012!
ufo

1
Rất nhanh! Tôi đã sử dụng giải pháp Áp dụng chéo được cung cấp bởi @dpp, nhưng giải pháp này nhanh hơn.
MattSlay

11

Đây là một chủ đề khá cũ, nhưng tôi nghĩ rằng tôi đã ném hai xu của mình giống như câu trả lời được chấp nhận không hoạt động đặc biệt tốt với tôi. Tôi đã thử giải pháp của gbn trên một tập dữ liệu lớn và thấy nó chậm kinh khủng (> 45 giây trên 5 triệu bản ghi trong SQL Server 2012). Nhìn vào kế hoạch thực hiện, rõ ràng vấn đề là nó đòi hỏi một hoạt động SORT làm mọi thứ chậm lại đáng kể.

Đây là một giải pháp thay thế mà tôi đã nâng lên từ khung thực thể không cần hoạt động SORT và thực hiện tìm kiếm Chỉ mục không cụm. Điều này giúp giảm thời gian thực hiện xuống <2 giây trên bộ hồ sơ đã nói ở trên.

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]

Bây giờ tôi đang giả sử một cái gì đó không hoàn toàn được chỉ định trong câu hỏi ban đầu, nhưng nếu thiết kế bảng của bạn sao cho cột ID của bạn là ID tăng tự động và DateCreated được đặt thành ngày hiện tại với mỗi lần chèn, thì thậm chí mà không cần chạy với truy vấn của tôi ở trên, bạn thực sự có thể tăng hiệu suất đáng kể cho giải pháp của gbn (khoảng một nửa thời gian thực hiện) chỉ từ việc đặt hàng trên ID thay vì đặt hàng trên DateCreated vì điều này sẽ cung cấp một thứ tự sắp xếp giống hệt nhau và đó là một cách sắp xếp nhanh hơn.


5

Mã của tôi để chọn top 1 từ mỗi nhóm

chọn một. * từ #DocumentStatusLogs một nơi 
 datecreated in (chọn top 1 datecreated từ #DocumentStatusLogs b
Ở đâu 
a.documentid = b.documentid
đặt hàng bởi descreated desc
)

3

Xác minh câu trả lời tuyệt vời và chính xác của Clint từ phía trên:

Hiệu suất giữa hai truy vấn dưới đây là thú vị. 52% là hàng đầu. Và 48% là người thứ hai. Cải thiện 4% hiệu suất bằng cách sử dụng DISTINCT thay vì ORDER BY. Nhưng ORDER BY có lợi thế để sắp xếp theo nhiều cột.

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')

Lựa chọn 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])

Lựa chọn 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]

Studio quản lý của M $: Sau khi tô sáng và chạy khối đầu tiên, hãy tô sáng cả Tùy chọn 1 và Tùy chọn 2, Nhấp chuột phải -> [Hiển thị Kế hoạch thực hiện ước tính]. Sau đó chạy toàn bộ để xem kết quả.

Kết quả lựa chọn 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

Tùy chọn 2 Kết quả:

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

Ghi chú:

Tôi có xu hướng sử dụng ÁP DỤNG khi tôi muốn tham gia là 1-to- (1 trong số nhiều).

Tôi sử dụng THAM GIA nếu tôi muốn tham gia là 1-nhiều hoặc nhiều-nhiều.

Tôi tránh CTE với ROW_NUMBER () trừ khi tôi cần làm gì đó nâng cao và ổn với hình phạt hiệu suất cửa sổ.

Tôi cũng tránh các truy vấn con EXISTS / IN trong mệnh đề WHERE hoặc ON, vì tôi đã trải nghiệm điều này gây ra một số kế hoạch thực hiện khủng khiếp. Nhưng số dặm khác nhau. Xem lại kế hoạch thực hiện và hiệu suất hồ sơ ở đâu và khi cần thiết!


3

Giải pháp này có thể được sử dụng để lấy TOP N hàng gần đây nhất cho mỗi phân vùng (trong ví dụ, N là 1 trong câu lệnh WHERE và phân vùng là doc_id):

SELECT doc_id, status, date_created FROM 
(
    SELECT a.*, ROW_NUMBER() OVER (PARTITION BY doc_id ORDER BY date_created DESC) AS rnk FROM doc a
)
WHERE rnk = 1;

2
SELECT o.*
FROM `DocumentStatusLogs` o                   
  LEFT JOIN `DocumentStatusLogs` b                   
  ON o.DocumentID = b.DocumentID AND o.DateCreated < b.DateCreated
 WHERE b.DocumentID is NULL ;

Nếu bạn muốn chỉ trả lại đơn đặt hàng tài liệu gần đây của DateCreated, nó sẽ chỉ trả lại tài liệu top 1 của DocumentID


2

CROSS APPLYlà phương pháp tôi sử dụng cho giải pháp của mình, vì nó hiệu quả với tôi và cho nhu cầu của khách hàng. Và từ những gì tôi đã đọc, nên cung cấp hiệu suất tổng thể tốt nhất nếu cơ sở dữ liệu của họ tăng trưởng đáng kể.


1

Dưới đây là 3 cách tiếp cận riêng biệt cho vấn đề cùng với các lựa chọn tốt nhất về lập chỉ mục cho từng truy vấn đó (vui lòng tự mình thử các chỉ mục và xem cách đọc logic, thời gian trôi qua, kế hoạch thực hiện. Tôi đã cung cấp các đề xuất từ ​​kinh nghiệm của tôi về truy vấn như vậy mà không thực hiện cho vấn đề cụ thể này).

Cách tiếp cận 1 : Sử dụng ROW_NUMBER (). Nếu chỉ mục của cửa hàng không thể nâng cao hiệu suất, bạn có thể thử chỉ mục nhà kho cột không bao gồm / cụm như đối với các truy vấn có tổng hợp và nhóm và cho các bảng được sắp xếp theo các cột khác nhau mọi lúc, chỉ mục nhà kho thường là lựa chọn tốt nhất.

;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;

Cách tiếp cận 2 : Sử dụng FIRST_VALUE. Nếu chỉ mục của cửa hàng không thể nâng cao hiệu suất, bạn có thể thử chỉ mục nhà kho cột không bao gồm / cụm như đối với các truy vấn có tổng hợp và nhóm và cho các bảng được sắp xếp theo các cột khác nhau mọi lúc, chỉ mục nhà kho thường là lựa chọn tốt nhất.

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;

Cách tiếp cận 3 : Sử dụng CROSS ỨNG DỤNG. Tạo chỉ mục kho hàng trên bảng DocumentStatusLogs bao gồm các cột được sử dụng trong truy vấn phải đủ để bao phủ truy vấn mà không cần chỉ mục của nhà kho.

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;

1

Tôi tin rằng điều này có thể được thực hiện như thế này. Điều này có thể cần một số điều chỉnh nhưng bạn chỉ có thể chọn tối đa từ nhóm.

Những câu trả lời này là quá mức cần thiết ..

SELECT
  d.DocumentID,
  MAX(d.Status),
  MAX(d1.DateCreated)
FROM DocumentStatusLogs d, DocumentStatusLogs d1
USING(DocumentID)
GROUP BY d.DocumentID
ORDER BY DateCreated DESC

0

Trong các trường hợp bạn muốn tránh sử dụng row_count (), bạn cũng có thể sử dụng nối trái:

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 

Đối với lược đồ ví dụ, bạn cũng có thể sử dụng "không trong truy vấn con", thường biên dịch thành cùng một đầu ra như nối trái:

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)

Lưu ý, mẫu truy vấn con sẽ không hoạt động nếu bảng không có ít nhất một khóa / ràng buộc / chỉ mục duy nhất một cột, trong trường hợp này là khóa chính "Id".

Cả hai truy vấn này có xu hướng "đắt" hơn truy vấn row_count () (được đo bởi Trình phân tích truy vấn). Tuy nhiên, bạn có thể gặp các tình huống trong đó chúng trả về kết quả nhanh hơn hoặc cho phép tối ưu hóa khác.


0
SELECT documentid, 
       status, 
       datecreated 
FROM   documentstatuslogs dlogs 
WHERE  status = (SELECT status 
                 FROM   documentstatuslogs 
                 WHERE  documentid = dlogs.documentid 
                 ORDER  BY datecreated DESC 
                 LIMIT  1) 

0

Thử cái này:

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]

Bạn phải luôn mô tả câu lệnh SQL của mình cách nó sẽ hoạt động và giải quyết truy vấn của OP.
Suraj Kumar

-1

Đây là TSQL vanilla nhất mà tôi có thể đưa ra

    SELECT * FROM DocumentStatusLogs D1 JOIN
    (
      SELECT
        DocumentID,MAX(DateCreated) AS MaxDate
      FROM
        DocumentStatusLogs
      GROUP BY
        DocumentID
    ) D2
    ON
      D2.DocumentID=D1.DocumentID
    AND
      D2.MaxDate=D1.DateCreated

Thật không may, MaxDate không phải là duy nhất. Có thể có hai ngày được nhập vào cùng một thời điểm chính xác. Vì vậy, điều này có thể dẫn đến trùng lặp mỗi nhóm. Tuy nhiên, bạn có thể sử dụng cột nhận dạng hoặc GUID. Cột danh tính sẽ giúp bạn nhập cái mới nhất (danh tính mặc định calc đang được sử dụng, 1 ... x bước 1).
TamusJRoyce

Vâng, tôi đồng ý, nhưng tác giả đã yêu cầu mục mới nhất - trừ khi bạn bao gồm một cột nhận dạng tự động tăng có nghĩa là hai mục được thêm vào cùng một lúc đều là 'mới nhất'
giàu có vào

Kỷ lục mới nhất sẽ là một kỷ lục. Vì vậy, có. Bạn cần xem xét cột nhận dạng tự động tăng.
TamusJRoyce

-2

Nó được kiểm tra trong SQLite rằng bạn có thể sử dụng truy vấn đơn giản sau với GROUP BY

SELECT MAX(DateCreated), *
FROM DocumentStatusLogs
GROUP BY DocumentID

Ở đây MAX giúp đỡ để có được DateCreated tối đa TỪ mỗi nhóm.

Nhưng có vẻ như MYSQL không liên kết * -columns với giá trị tối đa DateCreated :(

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.