本文详细介绍了SQL Server中分组取第一条数据的几种高效方法,主要涵盖了利用窗口函数ROW_NUMBER()、TOP子查询结合GROUP BY、以及变量排序等技巧,这些方法能有效解决数据分组后的去重与排序问题,显著提升查询性能,避免全表扫描。
在数据库开发中,我们经常遇到这样的需求:在 SQL Server 中,我们需要根据某个字段(如 UserID)对数据进行分组,然后从每一组中只取一条记录(例如每组的第一笔订单、最早的一条记录等)。
直接使用 GROUP BY 会导致多行数据合并为一行,从而丢失我们需要的信息,要解决这个问题,SQL Server 提供了多种强大的窗口函数和查询技巧,以下是几种最常用的实现方法,按推荐程度排序。
场景模拟
为了方便演示,假设我们有一张表 UserOrders(用户订单表),包含以下字段:

ID:主键(自增ID)UserName:用户名OrderDate:下单时间Amount:订单金额
我们的需求是:查询每个用户最早下单的那一条记录。
使用 ROW_NUMBER() 窗口函数(最推荐)
这是处理“分组取第一条”最标准、最灵活的方法。ROW_NUMBER() 会根据指定的排序规则为每一组数据生成一个唯一的序号(从1开始)。
语法逻辑:
- 使用
PARTITION BY指定分组的依据(UserName)。 - 使用
ORDER BY指定组内的排序规则(OrderDateASC)。 - 将生成的序号筛选为
1。
代码示例:
WITH RankedOrders AS (
SELECT
ID,
UserName,
OrderDate,
Amount,
-- PARTITION BY 将数据按 UserName 分组
-- ORDER BY 决定了谁是组内的“第一行”
ROW_NUMBER() OVER(PARTITION BY UserName ORDER BY OrderDate ASC) AS RowNum
FROM UserOrders
)
SELECT *
FROM RankedOrders
WHERE RowNum = 1;
优点: 灵活性极高,可以按任意字段排序(如 ID、金额、时间),不仅仅是主键。
使用 MIN(ID) 或 MAX(ID) 配合子查询
如果你的“第一行”是指主键 ID 最小(通常代表时间最早或插入顺序最早)的那一行,可以使用这种子查询的方法,这种方法在 SQL Server 2005 之前的版本中更为常见,性能通常也非常好。
逻辑: 先找出每组中 ID 最小的值,再根据这个 ID 查询完整行。
代码示例:
SELECT t.*
FROM UserOrders t
WHERE t.ID = (
-- 子查询:找出每个用户 ID 最小的记录
SELECT MIN(ID)
FROM UserOrders
GROUP BY UserName
);
优点: 逻辑直观,执行计划通常非常高效。 缺点: 仅适用于主键 ID 能代表“第一行”业务含义的情况(ID 是自增时间戳),ID 是乱序的,这种方法就会失效。
使用 DISTINCT
如果你的业务需求仅仅是“去掉重复的行”,并且不需要特定的排序,可以使用 DISTINCT。
代码示例:
SELECT DISTINCT UserName, OrderDate, Amount FROM UserOrders;
注意: DISTINCT 是对结果集进行去重,而不是真正的分组取第一条,它不能保证你拿到的是“最早”的那条,只是去掉了重复值。
在 SQL Server 中,处理“分组取第一行”的需求时:
- 首选
ROW_NUMBER():

