分组排序取第一条数据 SQL写法

博客聚焦数据库查询中分组排序取第一条数据的问题,如消息列表展示每个联系人最近信息。介绍了两种解决方案,开窗函数法适用于 Hive 和高版本 MySQL,简便高效;变量法用于低版本 MySQL 如 5.7,较复杂。还给出具体例子及查询语句和结果。

1. 背景

在数据库查询过程中经常遇到需要分组排序查询第一条数据的情况。例如,在消息列表中需要展示每个联系人最近的一条信息。

2. 解决方案

目前我接触到的解决方案有两种,分别是开窗函数 row_number 和变量法。

2.1 开窗函数法

比较常用的解决方案是使用开窗函数 row_number() over(partition by xxx order by xxx) 。使用开窗函数比较简便,只需要两个步骤

2.1.1 分组+排序+标注序号

select *, 
	row_number()  over (partition by ${group_col} order by ${order_col} ) as rownum 
from ${table}

以上 SQL 中 ${group_col} 表示要分组的字段;${order_col} 表示排序字段;rownum 是为序号字段取的别名。这条 SQL 的含义是将数据表 table 中的数据根据 group_col 分组后根据 order_col 进行排序,并为每条数据标注出在当前分组中的序号。

2.1.2 取第一条

select * from t1 where rownum = 1
  • t1分组+排序+标注序号 后的表,这里为了简洁就直接用 t1 代表子查询了。
  • rownum分组+排序+标注序号 中序号的别名。where rownum = 1 表示取 分组+排序+标注序号 后的第一条数据。

SQL 的含义是从 分组+排序+标注序号 后的表中查询序号为 1 的数据,这里的序号是各分组中排序后的序号。

3.1.3 Demo

将上述两个步骤连贯起来的 Demo 如下。

select * from (
	select *, 
	row_number() over (partition by ${group_col} order by ${order_col} )  rownum 
	from ${table}) t1 
where rownum = 1 

2.2 变量法

开窗函数的方法在 Hive 和高版本的 MySQL 里都是比较简单的查询方法,但是在低版本的 MySQL (比如 MySQL 5.7)中不支持开窗函数。所以只能换另一种方法,就是变量法。变量法需要通过变量来达成开窗函数的效果所以比较复杂,主要分为三部:数据排序、添加序号、取第一条。

2.2.1 数据排序

select * from ${table} order by ${group_col}, ${order_col}

首先对 table 表中的数据进行排序,排序的字段中第一个必须是想要用于分组的字段 group_col,后面才是需要排序的字段 order_col

2.2.2 添加序号

SELECT t.*, 
	IF(@x = ${group_col} OR (@x IS NULL AND ${group_col} IS NULL), 
		@rank:=@rank + 1, 
		@rank:=1 AND @x:= ${group_col}) as rownum 
	from (SELECT @x:=- 1) t0, t

这条 SQL 中定义了两个变量 xrank,整个 SQL 都围绕这两个变量展开,下面分别解释一下相关的内容。

  • x 变量用于保存当前分组的字段值,比如当前分组的字段值为 1,如果后面发现分组字段值不等于 1 了则说明换到另一个分组了,需要重新计数。
  • rank 变量用于保存当前分组中上一条记录的序号,如果上一条记录的序号是 1,则当前记录序号为 2。
  • (SELECT @x:=- 1) 的作用是为 x 变量初始化一个默认值,如果不初始化默认值则 x 默认为 nullx 的默认值必须是分组字段中没有的值,不建议设置为 null
  • IF(@x = ${group_col} OR (@x IS NULL AND ${group_col} IS NULL), @rank:=@rank + 1, @rank:=1 AND @x:=${group_col}) as rownum 这条语句就是序号赋值的关键所在。含义为如果 x 与当前记录的分组字段值相同则 rank = rank + 1(同分组内序号递增),否则 rank=1 (不同分组重新开始计数)并且 @x:=${group_col}(将 x 赋值为当前记录的分组字段值)。
  • t数据排序 后的表,这里为了简洁用 t 来代替子查询。

2.2.3 取第一条

select * from t2 where t2.rownum = 1

t2添加序号 后的表,这里为了简洁也是用 t2 代替子查询。

2.2.4 Demo

将上述三个步骤融合在一起如下。

select * from (
	SELECT t.*, 
		IF(@x = ${group_col} OR (@x IS NULL AND ${group_col} IS NULL), 		
		@rank:=@rank + 1, 
		@rank:=1 AND @x:= ${group_col}) as rownum 
	from (SELECT @x:=- 1) as t0, 
	(select * from ${table} order by ${group_col}, ${order_col}) as t) as t2 where t2.rownum = 1

3. 例子

3.1 需求

有一张留言记录表,表中记录的是每个用户的留言和管理员的回复。现需要取每个用户最新的一条留言记录

3.2 表结构

字段名字段类型说明
idBigintId
addtimeTimestamp添加时间
useridBigint用户 id
adminidBigint管理员 Id
askLongtext留言
replyLongtext回复
isreplyInt是否回复

3.3 表数据概览

IdAddtimeUseridAdminidAskReplyIsreply
12024-04-15 13:05:0011提问10
1422024-04-15 13:04:0021提问20
1432024-04-15 13:03:0031提问30
1442024-04-15 13:01:0041提问40
1822024-04-22 14:31:27171370351367011230
1832024-04-22 14:31:3317137035136701123123210
1842024-04-22 14:31:361713703513670112321330

3.4 查询语句

查询的思路就是根据用户 Id 分组,按添加时间倒序(从大到小)排,取每个用户 Id 分组中的第一条数据。为了方便查看,在查询最后再将记录根据添加时间倒序排列,将留言时间最近的用户放在前面。

3.4.1 开窗函数法

select * from (
	select *, 
		row_number()  over (partition by userid order by addtime desc)  rownum 
		from chat 
		where ask is not null ) t1 
	where rownum = 1 order by addtime desc

3.4.2 变量法

select addtime, userid, ask from (
    (select *, 
		IF(@x = userid OR (@x IS NULL AND userid IS NULL), 
			@rank:=@rank + 1, 
			@rank:=1 AND @x:=userid) AS rownum
    FROM
		(SELECT @x:=- 1) t0, 
			(SELECT * FROM chat 
				ORDER BY userid , addtime DESC) t) t2
	WHERE t2.rownum = 1 order by addtime desc; 

3.5 结果

AddtimeUseridAsk
2024-04-22 14:31:3617137035136701232133
2024-04-15 13:05:001提问 1
2024-04-15 13:04:002提问 2
2024-04-15 13:03:003提问 3
2024-04-15 13:01:004提问 4
<think> 我们被要求使用SQL语句删除表中的第一条数据。注意,表中的数据如果没有明确的排序第一条数据可能是不确定的。因此,通常我们会根据某个排序字段(如自增ID、时间戳等)来确定“第一条数据。 通常的做法是: 1. 确定排序字段(例如,假设我们有一个自增主键字段id,那么第一条数据通常是id最小的那一条)。 2. 使用DELETE语句删除排序后最小的那条记录。 但是,需要注意的是,如果没有指定顺序,SQL并不保证数据的存储顺序。所以,我们必须通过ORDER BY和LIMIT来指定删除最小id的那条记录(或者根据其他字段排序第一条)。 然而,不同的数据库系统语法略有不同。例如: - 在MySQL中,我们可以使用LIMIT: ```sql DELETE FROM table_name ORDER BY id -- 假设id是自增主键,按id升序排列,第一条就是最小的id LIMIT 1; ``` - 在SQL Server中,我们使用TOP: ```sql DELETE TOP (1) FROM table_name ORDER BY id; -- 注意:SQL Server中DELETE TOP不支持直接使用ORDER BY,所以需要子查询 ``` 实际上,SQL Server中正确的写法可能是: ```sql DELETE FROM table_name WHERE id IN (SELECT TOP 1 id FROM table_name ORDER BY id); ``` 或者 ```sql DELETE FROM table_name WHERE id = (SELECT MIN(id) FROM table_name); ``` - 在Oracle中,我们可以使用ROWNUM: ```sql DELETE FROM table_name WHERE id IN (SELECT id FROM table_name WHERE ROWNUM = 1 ORDER BY id); ``` 但是注意,Oracle中ROWNUM是在排序前分配的,所以需要子查询先排序第一条: ```sql DELETE FROM table_name WHERE id = (SELECT id FROM (SELECT id FROM table_name ORDER BY id) WHERE ROWNUM = 1); ``` 考虑到用户可能使用不同的数据库,且问题中没有指定,我们可以提供几种常见数据库写法。 另外,引用[2]中提供了删除重复数据并保留一条的示例,但这里我们不需要处理重复数据,只需要删除第一条(按某个字段排序第一条数据。 因此,我将提供针对MySQLSQL Server和Oracle的示例。 注意:在执行删除操作前,请务必备份数据,以免误删。 下面分别给出: 1. MySQL: ```sql DELETE FROM table_name ORDER BY sort_column -- 按照哪个字段排序,比如id LIMIT 1; -- 只删除第一条 ``` 2. SQL Server: ```sql -- 方法1:使用TOP DELETE TOP(1) FROM table_name -- 但是TOP不能直接和ORDER BY一起用在DELETE中,所以我们需要用子查询来指定顺序 -- 因此,更可靠的方法是: DELETE FROM table_name WHERE primary_key_column IN (SELECT TOP 1 primary_key_column FROM table_name ORDER BY sort_column); ``` 或者,如果知道最小id(或其他唯一标识): ```sql DELETE FROM table_name WHERE primary_key_column = (SELECT MIN(primary_key_column) FROM table_name); ``` 3. Oracle: ```sql DELETE FROM table_name WHERE primary_key_column IN ( SELECT primary_key_column FROM ( SELECT primary_key_column FROM table_name ORDER BY sort_column ) WHERE ROWNUM = 1 ); ``` 注意: - `table_name`:表名 - `sort_column`:用来排序的列(如id,创建时间等) - `primary_key_column`:表的主键列(确保唯一性,避免误删多条) 由于问题中没有提供具体的表结构和数据库类型,我们只能给出通用的方法。用户需要根据自己的数据库类型和表结构替换相应的部分。 另外,如果表中没有主键或者唯一标识,那么删除操作可能会有风险,因为可能同时删除多条(如果有相同的排序值)。所以,最好通过唯一标识来定位要删除的行。 因此,更安全的做法是先查询出要删除的那条记录的唯一标识,然后根据唯一标识删除。 例如,在MySQL中,我们可以这样: ```sql DELETE FROM table_name WHERE id = (SELECT id FROM table_name ORDER BY id LIMIT 1); ``` 但是,在MySQL中,你不能在同一个表中先查询出某些值然后立即在删除语句中使用这个子查询(会报错:You can't specify target table for update in FROM clause)。因此,我们可以使用多表查询的方式: ```sql DELETE FROM table_name WHERE id = ( SELECT * FROM ( SELECT id FROM table_name ORDER BY id LIMIT 1 ) AS temp ); ``` 综上所述,我们给出以下建议: 1. 确定排序字段(如id,按升序排列则第一条为id最小的)。 2. 根据数据库类型选择对应的语法,并确保使用唯一标识(如主键)来定位记录,以避免误删。 下面提供一个相对通用的方法(适用于大多数数据库,但可能效率不高): ```sql DELETE FROM table_name WHERE primary_key_column = (SELECT MIN(primary_key_column) FROM table_name); ``` 这个方法通过最小主键值来删除第一条记录(假设主键是自增的,那么最小主键就是第一条记录)。 但是,如果第一条记录的主键不是最小值(比如主键不是自增,或者被修改过),那么就需要根据实际排序字段来定。所以,最根本的是要根据排序字段来获第一条记录的主键。 因此,我们更倾向于使用根据排序字段排序第一条的唯一标识。 由于不同数据库语法差异,这里不再一一列举,用户可根据自己使用的数据库选择合适的方法。 最后,提醒用户:删除操作不可逆,务必谨慎。 根据引用[2]中删除重复数据的思路,我们也可以借鉴其使用子查询的方式,但这里不需要分组,只需要排序后的第一条。 现在,我们假设用户使用的是MySQL,并且表中有自增主键id,那么删除第一条数据SQL语句可以写为: ```sql DELETE FROM table_name ORDER BY id LIMIT 1; ``` 如果用户使用的是SQL Server,可以这样写: ```sql DELETE FROM table_name WHERE id IN (SELECT TOP 1 id FROM table_name ORDER BY id); ``` 如果用户使用的是Oracle,可以这样写: ```sql DELETE FROM table_name WHERE id IN ( SELECT id FROM ( SELECT id FROM table_name ORDER BY id ) WHERE ROWNUM = 1 ); ``` 请用户根据实际使用的数据库类型选择合适的语句。 注意:在实际操作前,最好先执行查询语句确认要删除的数据,例如: -- 对于MySQL SELECT * FROM table_name ORDER BY id LIMIT 1; -- 对于SQL Server SELECT TOP 1 * FROM table_name ORDER BY id; -- 对于Oracle SELECT * FROM ( SELECT * FROM table_name ORDER BY id ) WHERE ROWNUM = 1; 确保无误后再执行删除操作。 </think> 在 SQL 中删除第一条数据的方法决于具体数据库系统和表结构设计。以下是几种常见实现方式(假设表中有用于排序的自增主键字段 `id`): ### 1. 使用排序和限制(MySQL/PostgreSQL/SQLite) ```sql DELETE FROM your_table ORDER BY id ASC -- 按主键升序排序第一条是最小ID) LIMIT 1; -- 限制删除一条 ``` ### 2. 使用子查询(SQL Server/Oracle) ```sql -- SQL Server DELETE FROM your_table WHERE id = (SELECT TOP 1 id FROM your_table ORDER BY id ASC); -- Oracle DELETE FROM your_table WHERE id = (SELECT id FROM your_table WHERE ROWNUM = 1 ORDER BY id ASC); ``` ### 3. 使用最小值函数(通用方法) ```sql DELETE FROM your_table WHERE id = (SELECT MIN(id) FROM your_table); ``` ### 关键注意事项 1. **排序依据**:必须指定明确的排序字段(如自增主键、时间戳等),否则"第一条"的定义不确定 2. **事务安全**:建议在事务中执行删除操作: ```sql BEGIN TRANSACTION; DELETE ... ROLLBACK; -- 测试用 -- COMMIT; -- 确认无误后提交 ``` 3. **数据备份**:删除前建议备份: ```sql CREATE TABLE backup_table AS SELECT * FROM your_table WHERE id = [目标ID]; ``` 4. **执行效率**:对于大表,`MIN()` 可能比 `ORDER BY + LIMIT` 效率更高 > **重要提示**:实际删除前务必确认排序逻辑正确,并建议先在测试环境验证。如果表没有主键或唯一标识列,需要创建临时排序依据(如 `ROW_NUMBER()`)[^1][^2]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值