如何在 SQL 中获取每组的最后一条记录
Posted
技术标签:
【中文标题】如何在 SQL 中获取每组的最后一条记录【英文标题】:How to get the last record per group in 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 具有类似的功能。 【参考方案1】:您可以使用ranking function 和common 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
【讨论】:
以上是关于如何在 SQL 中获取每组的最后一条记录的主要内容,如果未能解决你的问题,请参考以下文章