获得每个组的前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进行sorting。 对于每个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

  • 如果这样的function不存在,有什么办法可以实现我想要的输出吗?

  • 或者首先,这可能是由非规范化的数据库引起的吗? 我在想,因为我在找的只是一行,如果这个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条,那么这将任意select一个。 要获得一天的两个条目,请改用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 

我在这里对各种build议做了一些计时,结果实际上取决于所涉及的表的大小,但最一致的解决scheme是使用CROSS APPLY这些testing是针对SQL Server 2008-R2运行的, 6,500条logging,另一条(相同的规划),有1.37亿条logging。 被查询的列是表中主键的一部分,表宽度非常小(约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 

我认为真正令人惊奇的是,无论涉及多less行,交叉应用的时间都是一致的。

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

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

关于你的问题的后半部分,我认为列入这个地位似乎是合理的。 您可以将DocumentStatusLogs作为日志保留,但仍将最新信息存储在主表中。

顺便说一句,如果你已经在Documents表中有DateCreated列,那么你可以使用它joinDocumentStatusLogs (只要DateCreatedDocumentStatusLogs是唯一的)。

编辑:MsSQL不支持USING,所以更改为:

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

如果你担心性能,你也可以用MAX()来做到这一点:

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

ROW_NUMBER()需要你的SELECT语句中的所有行,而MAX则不需要。 应该大大加快你的查询。

这是一个相当古老的线索,但我想我会把我的两分钱,就像接受的答案不一样对我特别好。 我在一个大的数据集上尝试了gbn的解决scheme,发现它非常慢(在SQL Server 2012的500万条以上的logging上> 45秒)。 看看执行计划,显然问题是它需要一个SORT操作,显着减慢事物的速度。

这里有一个替代scheme,我从entity framework中解脱出来,不需要SORT操作,并进行非聚集索引search。 这样可以将执行时间缩短到上述logging集的<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] 

现在,我假定原始问题中没有完全指定的东西,但是如果您的表格devise是这样的,即您的ID列是一个自动递增的ID,并且DateCreated设置为每个插入点的当前date,那么偶数如果没有用上面的查询运行,你实际上可以得到一个巨大的性能提升gbn的解决scheme(大约一半的执行时间),只是从IDsorting而不是在DateCreated上sorting,因为这将提供一个相同的sorting顺序,这是一个更快的sorting。

我的代码从每个组中select顶部1

 从#DocumentStatusLogs中select一个* 
  datecreated(从#DocumentStatusLogs中select最上面的1个date)
哪里 
 a.documentid = b.documentid
 order by datecreated desc
 )

从上面validation克林特真棒和正确的答案:

下面两个查询之间的性能很有意思。 52%是最高的。 第二个是48%。 使用DISTINCT而不是ORDER BY,性能提高了4%。 但ORDER BY的优点是可以按多列进行sorting。

 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] 

M $的pipe理工作室:突出显示并运行第一个模块后,突出显示选项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 

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

请注意,如果表中没有至less一个单列唯一键/约束/索引(在本例中为主键“Id”),则子查询模式将不起作用。

这两个查询往往比row_count()查询(由查询分析器测量)更“昂贵”。 但是,您可能会遇到更快返回结果或启用其他优化的情况。

尝试这个:

  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] 
 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个文档

这是我能想出的最香草的TSQL

  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 

它在SQLite中检查,您可以使用以下简单的查询与GROUP BY

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

这里MAX帮助获得每个组的最大DateCreated

但似乎MYSQL不关联*列与最大DateCreated的值:(