如何获取SQL中每个组的最后一条记录

时间:2011-06-01 12:36:56

标签: sql-server-2008 tsql sql-server-2005 greatest-n-per-group

我面临一个相当有趣的问题。我有一个具有以下结构的表:

CREATE TABLE [dbo].[Event]
(
    Id int IDENTITY(1,1) NOT NULL,
    ApplicationId nvarchar(32) NOT NULL,
    Name nvarchar(128) NOT NULL,
    Description nvarchar(256) NULL,
    Date nvarchar(16) NOT NULL,
    Time nvarchar(16) NOT NULL,
    EventType nvarchar(16) NOT NULL,
    CONSTRAINT Event_PK PRIMARY KEY CLUSTERED ( Id ) WITH (
        PAD_INDEX = OFF, 
        STATISTICS_NORECOMPUTE = OFF, 
        IGNORE_DUP_KEY = OFF, 
        ALLOW_ROW_LOCKS = ON, 
        ALLOW_PAGE_LOCKS  = ON
    )
)

所以问题是我必须在网格中显示这些数据。有两个要求。第一个是显示所有事件,而不管是什么应用程序抛出它们。这很简单 - 选择语句可以很容易地完成工作。

第二个要求是能够按Application对事件进行分组。换句话说,以一种方式显示所有事件,如果ApplicationId重复多次,则只抓取每个应用程序的最后一个条目。此查询/视图中不再需要此时事件(Id)的主键。

您可能还注意到事件日期和时间是字符串格式。这没关系,因为它们遵循标准的日期时间格式:mm / dd / yyyy和hh:mm:ss。我可以按如下方式提取:

Convert( DateTime, (Date + ' ' +  Time)) AS 'TimeStamp'

我的问题是,如果我在其余列上使用AGGREGATE函数,我不知道它们会如何表现:

SELECT
    ApplicationId,
    MAX(Name),
    MAX(Description),
    MAX( CONVERT(DateTime, (Date + ' ' + Time))) AS 'TimeStamp',
    MAX( EventType )
FROM
    Event
GROUP BY
    ApplicationId

我犹豫不决的原因是因为MAX之类的函数会从(子)记录集返回给定列的最大值。没必要拉最后一条记录!

关于如何根据每个应用程序仅选择最后一条记录的任何想法?

10 个答案:

答案 0 :(得分:42)

您可以使用ranking functioncommon table expression

WITH e AS
(
     SELECT *,
         ROW_NUMBER() OVER
         (
             PARTITION BY ApplicationId
             ORDER BY CONVERT(datetime, [Date], 101) DESC, [Time] DESC
         ) AS Recency
     FROM [Event]
)
SELECT *
FROM e
WHERE Recency = 1

答案 1 :(得分:7)

从SQL Server 2012开始,您可以简单地

SELECT 
    [Month]
    , [First] = FIRST_VALUE(SUM([Clicks])) OVER (ORDER BY [Month])
    , [Last]  = FIRST_VALUE(SUM([Clicks])) OVER (ORDER BY [Month] DESC)
FROM 
    [dbo].[Table]
GROUP BY [Month]
ORDER BY [Month]

答案 2 :(得分:0)

SELECT
    E.ApplicationId,
    E.Name,
    E.Description,
    CONVERT(DateTime, (E.Date + ' ' + E.Time)) AS 'TimeStamp',
    E.EventType
FROM
    Event E
    JOIN (SELECT ApplicationId,
                 MAX(CONVERT(DateTime, (Date + ' ' + Time))) AS max_date
            FROM Event
        GROUP BY ApplicationId) EM 
      on EM.ApplicationId = E.ApplicationId
     and EM.max_date = CONVERT(DateTime, (E.Date + ' ' + E.Time)))

答案 3 :(得分:0)

因为那里没有where子句,所以记录子集都是记录。但是你认为最大限度地放在了错误的列上。此查询将为您提供所需内容。

Select max(applicationid), name, description, CONVERT(DateTime, (Date + ' ' + Time)) 
from event
group by name, description, CONVERT(DateTime, (Date + ' ' + Time)) 

答案 4 :(得分:0)

您可以使用子查询或CTE表来执行此操作:

;WITH CTE_LatestEvents as (
SELECT
    ApplicationId,    
    MAX( CONVERT(DateTime, (Date + ' ' + Time))) AS 'LatestTimeStamp',
FROM
    Event
GROUP BY
    ApplicationId
)
SELECT
    ApplicationId,
    Name,
    Description,
    CONVERT(DateTime, (Date + ' ' + Time))) AS 'TimeStamp',
    EventType
FROM
    Event e
    Join CTE_LatestEvents le 
        on e.applicationid = le.applicationid
        and CONVERT(DateTime, (e.Date + ' ' + e.Time))) = le.LatestTimeStamp

答案 5 :(得分:0)

您可以将子查询与group by一起使用 - group by参数不需要在select中。假设Id是自动递增的,因此最大的是最新的。

SELECT
    ApplicationId,
    Name,
    Description,
    CONVERT(DateTime, (Date + ' ' + Time)) AS 'TimeStamp',
    EventType
FROM
    Event e
WHERE
    Id in (select max(Id) from Event GROUP BY ApplicationId)

答案 6 :(得分:0)

我认为这对那些愿意获取最后插入记录的人很有用,它应该分组:

select * from(select * from TableName ORDER BY id DESC)AS x GROUP BY FieldName

它适用于以下内容:

表格结构 ID名称状态 1 Junaid是的 2 Jawad No. 3法赫德是的 4 Junaid No. 5 Kashif是的

查询上方的结果 ID名称状态 4 Junaid No. 2 Jawad No. 3法赫德是的 4 Kashif是的

这只是按名称生成最后一组记录。

答案 7 :(得分:0)

6年后SQL Server的另一个答案:

select t1.[Id], t2.[Value]  
from [dbo].[Table] t1  
  outer apply (  
    select top 1 [Value]  
      from [dbo].[Table] t2  
        where t2.[Month]=t1.[Month]  
      order by [dbo].[Date] desc  
  )  

虽然我更喜欢Postgresql解决方案,它具有独特的on功能,键入效果更好,效率更高:

select distinct on (id),val  
from tbl  
order by id,val  

答案 8 :(得分:0)

起初我曾经将CTE与row_number一起使用,但是SQL Server认证课程中的一个示例向我展示了一个更好的示例(通过不断获得更好的执行计划来判断):

SELECT
  ApplicationId,
  Name,
  Description,
  CONVERT(DateTime, (Date + ' ' + Time)) AS 'TimeStamp',
  EventType
FROM
  Event AS E
WHERE
  NOT EXISTS(SELECT * FROM Event AS Newer WHERE Newer.ApplicationId = E.ApplicationId AND Newer.Id > E.Id)
GROUP BY
  ApplicationId

我假设更大的ID意味着更大的Date + Time(否则,我将使用convert转换为datetime,尽管这不能SARGable)。该查询将查找最年轻的记录-不存在较年轻的记录。如果索引设置正确,将使用索引查找。具有排名功能的替代方法通常使用表扫描,因为它可以对所有记录进行排名。

答案 9 :(得分:0)

我遇到了同样的问题。现在,我不想让 CTE 和“OVER”过于复杂。这是一个简单的例子。我用 MAX(DateEntered) 写了一个带有组的子查询。例如,如果它是 int,您可能想要通过 ID 来做,这比日期/时间更准确。在任何情况下,一旦您有了这个子查询,您只需将它与您的主查询内联以充当记录的过滤器。就这么简单。

a 是我的用户表。表 b 是子查询,表 c 是我想要“过滤”的表。

SELECT DISTINCT a.FirstName,a.LastName,a.ImagePath, c.MessageText
        FROM [AuthUsers] a 
            INNER JOIN (SELECT MessageFromId,MAX(DateEntered) AS LastEntered FROM ChatRoomConversation GROUP BY MessageFrom) AS b
                ON a.Id=b.MessageFromId
            INNER JOIN ChatRoomConversation c
                ON b.LastEntered=c.DateEntered