【问题标题】:How to get the last record per group in SQL如何在 SQL 中获取每组的最后一条记录
【发布时间】:2011-09-06 06:33:18
【问题描述】:

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

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

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

第二个要求是能够按Application 对事件进行分组。换句话说,如果ApplicationId 重复多次,则显示所有事件,只获取每个应用程序的最后一个条目。此时,此查询/视图中不再需要 Event (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 之类的函数将返回(子)记录集中给定列的最大值。不需要拉最后一条记录!

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

【问题讨论】:

  • 使用窗口函数(在 Oracle 中,类似于 row_number() over (partition by...)),AFAIK SQL server 具有类似的功能。

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


【解决方案1】:

您可以使用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

【讨论】:

  • 您不能只按日期和时间排序而不转换为日期时间值,因为mm/dd/yyyy 格式无法正确排序为字符串。
  • 谢谢@Anthony Faull。这可行,但我不明白如何。
  • @damien 很好。我更新了 ORDER BY 子句以将美国日期(月-日-年)转换为可排序的日期。
  • 即使这是一个非常晚的评论并且不再帮助@bleepzter,它可能会帮助其他人理解它是如何工作的:分区将数据(事件)划分为每个子集都有的子集相同的应用程序ID。每个子集按日期和时间排序。然后每一行得到一个行号。这描述了“与”部分。下面的 select 获取“with”语句的结果,并输出所有行号为 1 的条目。
【解决方案2】:

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

【讨论】:

  • FIRST_VALUE 与 OVER - 非常令人印象深刻!今天学到了新东西!!谢谢。 SQL 版本参考也是一个加号!
【解决方案3】:

您可以使用带有 group by 的子查询 - group by 参数不需要在选择中。这假设 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)

【讨论】:

    【解决方案4】:
    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)))
    

    【讨论】:

      【解决方案5】:

      您可以使用 subqery 或 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
      

      【讨论】:

        【解决方案6】:

        因为那里没有 where 子句,所以记录子集就是所有记录。但是我认为你把 max 放在了错误的列上。此查询将为您提供所需的内容。

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

        【讨论】:

          【解决方案7】:

          我认为它适用于许多愿意获取最后插入的记录的人,它应该按以下方式分组:

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

          它适用于以下情况:

          表结构 ID名称状态 1 朱奈德 是的 2贾瓦德没有 3 法赫德 是的 4朱奈德没有 5 Kashif 是的

          以上查询后的结果 ID名称状态 4朱奈德没有 2贾瓦德没有 3 法赫德 是的 4 Kashif 是的

          这只是根据名称生成最后的分组记录。

          【讨论】:

            【解决方案8】:

            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 解决方案,因为它独特的功能更易于输入且效率更高:

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

            【讨论】:

              【解决方案9】:

              一开始我使用 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 意味着较大的日期 + 时间(否则我会使用转换为日期时间,但这不是 SARGable)。此查询将找到最年轻的记录 - 不存在较年轻的记录。如果索引设置正确,这将使用索引查找。具有排名功能的替代方案通常使用表扫描,因为它对所有记录进行排名。

              【讨论】:

                【解决方案10】:

                我有同样的问题。现在,我不想让 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
                

                【讨论】:

                  猜你喜欢
                  • 2023-01-28
                  • 2017-12-15
                  • 2011-12-17
                  • 2016-09-03
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-07-11
                  相关资源
                  最近更新 更多