获取每组的前 1 行

Get top 1 row of each group

提问人:kazinix 提问时间:7/27/2011 最后编辑:Dale Kkazinix 更新时间:5/30/2023 访问量:970278

问:

我有一个表格,我想获取每个组的最新条目。下表如下:

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   |

该表将按降序分组和排序。对于每个,我都想获得最新状态。DocumentIDDateCreatedDocumentID

我的首选输出:

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

更新我刚刚学会了如何使用“apply”,这使得解决此类问题变得更加容易。

sql-server sql-server-2005 greatest-n-per-group

评论

5赞 Vladimir Baranov 11/6/2016
有关可能解决方案的更详细讨论和比较,我建议阅读 dba.se 上的类似问题:每组检索 n 行
1赞 UltraJ 9/7/2018
我看了帖子并尝试了一下。使用 group by StoreID 会生成错误。
1赞 Vadzim 6/14/2020
相关新闻: 选择每个 GROUP BY 组中的第一行?
1赞 Michael Freidgeim 5/17/2022
这个问题是针对MS SQL Server的,问题选择每个GROUP BY组中的第一行是针对Postgres的。语言结构可以不同。

答:

957赞 gbn 7/27/2011 #1
;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 个地方保持状态
  • 保留状态历史记录
  • ...

就目前而言,您可以保留状态历史记录。如果您也希望在父表中显示最新状态(即非规范化),则需要一个触发器来维护父表中的“状态”。或删除此状态历史记录表。

评论

5赞 kazinix 7/27/2011
和。。。什么? 对我来说也是新的:(无论如何,我正在使用 mssql 2005。Partition ByWith
9赞 gbn 7/27/2011
@domanokz:分区依据重置计数。因此,在本例中,它表示按 DocumentID 计数
2赞 kazinix 7/27/2011
嗯,我担心性能,我将查询数百万行。SELECT * FROM (SELECT ...) 会影响性能吗?另外,每行是否有某种子查询?ROW_NUMBER
1赞 gbn 7/27/2011
@domanokz:不,这不是子查询。如果你有正确的索引,那么数百万应该不是问题。无论如何,只有 2 种基于集合的方法:this 和聚合(Ariel 的解决方案)。所以试试他们俩......
1赞 gbn 7/27/2011
@domanokz:只需将 ORDER BY DateCreated DESC 更改为 ORDER BY ID DESC
30赞 Ariel 7/27/2011 #2
SELECT * FROM
DocumentStatusLogs JOIN (
  SELECT DocumentID, MAX(DateCreated) DateCreated
  FROM DocumentStatusLogs
  GROUP BY DocumentID
  ) max_date USING (DocumentID, DateCreated)

什么数据库服务器?此代码不适用于所有这些代码。

关于你问题的后半部分,在我看来,将状态作为一列包括在内似乎是合理的。您可以保留为日志,但仍将最新信息存储在主表中。DocumentStatusLogs

顺便说一句,如果您在“文档”表中已经有该列,则可以使用该列进行联接(只要在 中是唯一的)。DateCreatedDocumentStatusLogsDateCreatedDocumentStatusLogs

编辑:MsSQL不支持USING,因此将其更改为:

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

评论

5赞 gbn 7/27/2011
线索就在标题中:MSSQL。SQL Server 没有 USING,但这个想法是可以的。
8赞 NickG 9/24/2015
@gbn 愚蠢的版主通常会从标题中删除重要的关键字,就像他们在这里所做的那样。这使得在搜索结果或谷歌中找到正确答案变得非常困难。
2赞 MoonKnight 11/17/2017
需要指出的是,如果你在max(DateCreated)
0赞 faheem khan 12/26/2020
删除 Using (在 MS SQL 中 ) 并完成 Join 代码 ,然后它就可以工作了。
252赞 kazinix 8/30/2012 #3

我刚刚学会了如何使用.以下是在此方案中使用它的方法: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

评论

2赞 kazinix 9/5/2012
这实际上没有区别,因为问题仍在解决中。
37赞 John Fairbanks 3/7/2015
我刚刚发布了针对所有建议的解决方案的计时测试结果,您的解决方案名列前茅。给你投赞成票:-)
5赞 TamusJRoyce 10/19/2015
+1 可大幅提高速度。这比 ROW_NUMBER() 等窗口函数要快得多。如果 SQL 识别出 ROW_NUMBER() = 1 类似的查询并将它们优化为 Applies,那就太好了。注意:我使用了 OUTER APPLY,因为我需要结果,即使它们在应用中不存在。
9赞 Martin Smith 6/4/2016
@TamusJRoyce你不能仅仅因为它更快就推断出这一点,一旦情况总是如此。这要视情况而定。如上所述 sqlmag.com/database-development/optimizing-top-n-group-queries
7赞 Extragorey 8/3/2020
当您已经有一个单独的表,该表在输出中根据需要为每组提供一行时,这很有效。但是,如果您只使用一个表(在本例中),则首先必须对(或、等)执行某种操作,从而失去所有获得的性能。DocumentsDocumentStatusLogsDISTINCTDocumentIDROW_NUMBER()MAX(ID)
0赞 BitwiseMan 9/5/2012 #4

在想要避免使用 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 

对于示例架构,您还可以使用“不在子查询中”,它通常编译为与左联接相同的输出:

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() 查询(由查询分析器测量)更“昂贵”。但是,您可能会遇到它们更快地返回结果或启用其他优化的情况。

5赞 AnuPrakash 9/23/2012 #5

我的代码从每个组中选择前 1 名

select a.* from #DocumentStatusLogs a where 
 datecreated in( select top 1 datecreated from #DocumentStatusLogs b
where 
a.documentid = b.documentid
order by datecreated desc
)
44赞 Daniel Cotter 1/16/2013 #6

如果您担心性能,也可以使用以下方法执行此操作:MAX()

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

ROW_NUMBER()需要语句中的所有行,而不需要。应该会大大加快您的查询速度。SELECTMAX

评论

2赞 Kristoffer L 10/22/2013
ROW_NUMBER()的性能问题不能通过适当的索引来解决吗?(我觉得无论如何都应该这样做)
12赞 TamusJRoyce 10/19/2015
使用 datetime 时,您不能保证不会在同一日期和时间添加两个条目。精度不够高。
1赞 cibercitizen1 6/11/2017
为简单起见,+1。@TamusJRoyce是对的。怎么样?'select * from DocumentStatusLog D where ID =(select ID from DocumentsStatusLog where D.DocumentID = DocumentID order by DateCreated DESC limit 1);'
0赞 Arun Prasad E S 2/1/2018
SELECT * FROM EventScheduleTbl D WHERE DatesPick = (SELECT top 1 min(DatesPicked) FROM EventScheduleTbl WHERE EventIDf = D.EventIDf and DatesPicked>= convert(date,getdate()) )
1赞 Scott 10/7/2019
就我而言,由于引入了子查询,这种方法比使用 ROW_NUMBER() 慢。您应该测试不同的方法,看看哪种方法最适合您的数据。
11赞 Clint 6/3/2014 #7

这是一个相当古老的线程,但我认为我会投入两美分,因为接受的答案对我来说不是特别有效。我在大型数据集上尝试了 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 进行排序而不是在 DateCreated 上排序来获得 gbn 解决方案的相当大的性能提升(大约一半的执行时间)因为这将提供相同的排序顺序,并且排序速度更快。

62赞 John Fairbanks 3/7/2015 #8

我在这里对各种建议做了一些计时,结果实际上取决于所涉及的表的大小,但最一致的解决方案是使用 CROSS APPLY 这些测试是针对 SQL Server 2008-R2 运行的,使用一个包含 6,500 条记录的表和另一个包含 1.37 亿条记录的表(相同的架构)。被查询的列是表上主键的一部分,表宽度非常小(大约 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 的时间都是如此一致。

评论

8赞 Vladimir Baranov 11/6/2016
这完全取决于数据分布和可用索引。在 dba.se 上对此进行了长时间的讨论。
5赞 TamusJRoyce 10/29/2015 #9

从上面验证克林特令人敬畏的正确答案:

下面两个查询之间的性能很有意思。52%位居榜首。48%是第二个。使用 DISTINCT 而不是 ORDER BY 将性能提高了 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]

在 SQL Server Management Studio Microsoft:突出显示并运行第一个块后,突出显示选项 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 of many )时,我倾向于使用 APPLY。

如果我希望联接是一对多或多对多,则使用 JOIN。

我避免使用 ROW_NUMBER() 进行 CTE,除非我需要做一些高级操作并且对窗口性能损失感到满意。

我还避免在 WHERE 或 ON 子句中使用 EXISTS / IN 子查询,因为我经历过这种情况导致一些糟糕的执行计划。但里程数各不相同。在需要的时间和地点查看执行计划并分析性能!

-1赞 gng 11/5/2016 #10

试试这个:

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]

评论

4赞 Suraj Kumar 1/8/2020
您应该始终描述您的 SQL 语句将如何工作并解决 OP 的查询。
2赞 cho 12/19/2016 #11
SELECT o.*
FROM `DocumentStatusLogs` o                   
  LEFT JOIN `DocumentStatusLogs` b                   
  ON o.DocumentID = b.DocumentID AND o.DateCreated < b.DateCreated
 WHERE b.DocumentID is NULL ;

如果只想按 DateCreated 返回最近的文档顺序,它将仅按 DocumentID 返回前 1 个文档

评论

1赞 Extragorey 8/3/2020
这只会返回表中的所有内容。
33赞 Randall 1/18/2018 #12

这是关于这个话题最容易找到的问题之一,所以我想给它一个现代的答案(既供我参考,也为了帮助他人)。通过使用 and,您可以对上述查询进行简短的工作:first_valueover

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 2012 及更高版本。 可以认为是使用子句时完成的一种方式。 允许在选择列表中进行分组,因此无需编写嵌套子查询(就像许多现有答案那样),而是以更具可读性的方式进行。First_valueSelect Top 1overOver

评论

3赞 ufo 1/25/2018
这在 SQL Server 2008 R2 中不起作用。我想first_value是在 2012 年推出的!
4赞 MattSlay 9/1/2019
非常快!我正在使用 @dpp 提供的 Cross Apply 解决方案,但这个解决方案更快。
0赞 Extragorey 8/3/2020
对于大量列(Status、DateCreated 等),是为每个列单独分区/排序,还是优化为一个?
234赞 Josh Gilfillan 1/24/2018 #13

我知道这是一个旧线程,但解决方案非常好,可能对一些阅读解决方案有所帮助。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)

该子句告知 SQL Server 要返回每个组的第一行。但是,SQL Server 如何知道如何对数据进行分组呢?这就是它的用武之地。后面的列定义 SQL Server 如何对数据进行分组。在每个组中,将根据列对行进行排序。排序后,将在查询中返回每个组中的第一行。select top 1 with tiesorder by row_number() over (partition by DocumentID order by DateCreated descpartition byorder by

有关 TOP 条款的更多信息,请参见此处

评论

3赞 Chris Umphlett 11/13/2019
同意 - 这最好地复制了在其他版本的 SQL 和其他语言 IMO 中非常容易做到的事情
0赞 N8allan 8/20/2020
我同意这是一个优雅的解决方案。在我的特定查询和 SQL Server 2019 上,这比与前 1 个解决方案交叉应用慢两倍,但请自行衡量。
4赞 TK Bruin 10/28/2020
嗯,“With Ties”可能会导致返回的行数多于表达式 (TOP 1) 中指定的值。如果 OP 只想要 1,那么你需要删除这个短语,对吧?
3赞 Josh Gilfillan 10/28/2020
@TKBruin这就是为什么需要 row_number() 排序的原因。这允许检索每个分区的顶级记录。
1赞 Jürgen Zornig 5/17/2022
这当之无愧是最好的答案......它的速度绝对可以与使用带有窗口功能的 CTE 相媲美,但它的可维护性要高得多......我的 DataVault 模型中有数百个附属表,使用此解决方案,我不必一次又一次地为每个表重新键入属性投影来查看其最新条目。此外,此解决方案通常比加入 PIT 表以获取最新条目更快。对我来说真的是一个游戏规则的改变者
5赞 praveen 10/16/2018 #14

此解决方案可用于获取每个分区的 TOP 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;
17赞 san 6/17/2019 #15

以下是解决手头问题的 3 种独立方法,以及每个查询的最佳索引选择(请自己尝试索引并查看逻辑读取、运行时间、执行计划。我已经根据我的经验提供了关于此类查询的建议,但没有针对此特定问题执行)。

方法 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 APPLY。在涵盖查询中使用的列的 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;

评论

1赞 Pedro Ludovico Bozzini 3/20/2021
我有一个 100M 行表,我在其中获得了每组的第一条和最后一条记录。前两种方法需要几分钟才能执行。方法 3 用时不到一秒钟。
-1赞 Koshal Garg 9/19/2019 #16
SELECT documentid, 
       status, 
       datecreated 
FROM   documentstatuslogs dlogs 
WHERE  status = (SELECT status 
                 FROM   documentstatuslogs 
                 WHERE  documentid = dlogs.documentid 
                 ORDER  BY datecreated DESC 
                 LIMIT  1) 

评论

0赞 Dale K 9/12/2023
那是MySQL语法,而不是SQL Server
4赞 S8Tony 2/13/2020 #17

CROSS APPLY是我用于解决方案的方法,因为它对我和我的客户需求都有效。从我所读到的内容来看,如果他们的数据库大幅增长,应该提供最佳的整体性能。

0赞 Union find 3/25/2020 #18

我相信这可以像这样完成。这可能需要一些调整,但您可以从组中选择最大值。

这些答案是矫枉过正的。

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

评论

0赞 PedroC88 4/22/2021
这是 t-sql 吗? 不支持那样...Using
0赞 Union find 4/22/2021
MySQL 8 应该支持 @PedroC88
1赞 PedroC88 4/24/2021
是的,我提到它是因为 OP 指定了 sql-server
0赞 Union find 4/24/2021
@PedroC88这个问题似乎已经改变,因此它不再引用sql-server。所以这是一个好的答案。
2赞 PedroC88 4/25/2021
它在标签上
-1赞 MasterKiller 2/1/2022 #19

一些数据库引擎*开始支持允许过滤窗口函数结果的子句(接受的答案使用该子句)。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/ 当目标方言不支持限定子句时,可以选择转换该子句。

*Teradata、BigQuery、H2、Snowflake...