生活中的例子
想象你管理一家网店,同一个订单(order_number)中的同一商品(product)可能有多次更新记录(比如库存变化、价格调整)。你只想查看每个订单商品的最新状态,这时就需要用到"分组取最新记录"的操作。
原理解析:给数据分组并编号
select *, row_number() over ( partition by order_number, product, craft, trade_name order by create_time desc ) as rn from client_product
这个查询的核心是row_number()
函数,它像老师给学生排队一样:
- 分组(partition by):把相同订单+产品+工艺+贸易名称的记录分成一组
- 排序(order by):每组内按创建时间倒序排列(最新时间排第一)
- 编号(rn):给每组内的记录标记序号(1,2,3…)
完整查询解析
select * from ( -- 步骤1:给所有记录标记组内序号 select *, row_number() over ( partition by order_number, product, craft, trade_name order by create_time desc ) as rn from client_product where production_order_number is not null -- 排除生产订单号为空 and order_number is not null -- 排除订单号为空 and craft != '' -- 排除工艺为空 and del_flag = '0' -- 只取未删除记录 and deliver_status != '0' -- 排除未交付状态 ) as ranked -- 步骤2:只取每组最新记录 where rn = 1
关键步骤拆解
1.数据过滤(where)
只处理有效数据:非空订单号、有生产订单号、工艺不为空、未删除、已交付
2.分组标记(row_number)
订单号 | 产品 | 创建时间 | 组内序号(rn) |
---|---|---|---|
a1001 | 手机壳 | 2023-01-05 | 1(最新) |
a1001 | 手机壳 | 2023-01-03 | 2 |
b2002 | 数据线 | 2023-01-04 | 1(最新) |
3.筛选结果(where rn=1)
只保留每组中rn=1的记录,即每个组合的最新数据
实际应用场景
- 订单管理:获取每个订单的最新状态
- 设备监控:读取每个传感器的最新读数
- 用户行为:提取每个用户最近一次登录记录
- 价格跟踪:查看每个商品的最新定价
性能小贴士
当数据量很大时:
- 在
order_number, product, craft, trade_name
上创建索引 - 在
create_time
上创建降序索引 - 定期清理历史数据
方法补充
以下是几种去重的sql写法
在 sql 中,数据去重有多种实现方式,以下是几种常见写法及其适用场景:
1. 使用 distinct
关键字
语法:
select distinct column1 [, column2, ...] from table_name;
说明:直接对指定字段组合进行唯一性筛选,仅保留首次出现的记录。
示例:
select distinct address from student; -- 获取不重复的地址
局限性:
- 若对多字段去重,需所有字段值完全相同才视为重复。
- 无法同时返回非去重字段的原始值,仅能展示去重字段。
2. 使用 group by
子句
语法:
select column1 [, aggregate_function(column2), ...] from table_name group by column1 [, column2, ...];
说明:按指定字段分组,结合聚合函数(如 max
、min
、count
等)获取其他字段信息。
示例:
select min(id), address from student group by address; -- 按地址去重,返回每组最小 id
注意:非聚合字段可能来自不同记录,导致数据逻辑上不一致(如不同 id
对应同一 address
时,聚合函数外的字段取值无明确规律)。
3. 使用窗口函数(如 row_number()
)
语法:
select * from ( select *, row_number() over (partition by column1 order by column2) as rn from table_name ) as t where rn = 1;
说明:先按 partition by
分组,再按 order by
排序并生成行号,筛选行号为 1
的记录。
示例:
select id, name, address from ( select *, row_number() over (partition by address order by id asc) as rn from student ) as a where a.rn = 1; -- 按地址去重,保留每组 id 最小的记录
优势:可精准控制保留哪条记录(如按时间、id 排序取最新或最旧),但低版本 mysql 不支持窗口函数。
4. 使用 in
子查询
语法:
select * from table_name where id in (select max(id) from table_name group by column1);
说明:通过子查询找到每组唯一标识字段(如自增 id
)的最大值,再筛选主表中对应记录。
示例:
select * from student where id in (select max(id) from student group by address); -- 按地址去重,取每组最大 id 的记录
适用场景:表中存在唯一标识字段(如 id
),且需保留特定条件(如最大 / 最小 id
)的记录。
5. 使用 not exists
语法:
select a.* from table_name a where not exists ( select 1 from table_name b where a.column1 = b.column1 and a.id < b.id );
示例:
select a.* from student a where not exists (select 1 from student b where a.address = b.address and a.id < b.id); -- 按地址去重,保留每组 id 最大的记录
逻辑:对于每一行 a
,若不存在 b
行(同 column1
且 id
更大),则保留 a
。
6. 使用 union
去重
语法:
select column1 [, column2, ...] from table_name1 union select column1 [, column2, ...] from table_name2;
说明:合并多个查询结果并自动去重(union all
保留全部记录,不进行去重)。
示例:
select address from student union select address from teacher; -- 合并两表地址并去重
注意:大数据量时效率较低,建议先用 union all
再结合其他方法去重。
7. 使用 inner join + group by
语法:
select a.* from table_name a inner join ( select column1, max(id) as max_id from table_name group by column1 ) b on a.column1 = b.column1 and a.id = b.max_id;
示例:
select a.* from student a inner join (select address, max(id) as max_id from student group by address) b on a.address = b.address and a.id = b.max_id; -- 按地址去重,取每组最大 id 的记录
逻辑:先通过子查询获取每组最大 id
,再与主表关联筛选。
实际应用中,可根据数据库特性(如是否支持窗口函数)、数据规模、业务需求(如保留特定记录)选择合适的方法。例如,简单单字段去重优先用 distinct
;需保留其他字段且数据一致性要求不高时用 group by
;需精准控制保留记录时用窗口函数或 in
/not exists
等。
总结
这个查询就像给每个分组内的记录按时间倒序排队,然后只取排在第一位的记录
通过这个技巧,你可以轻松地从重复数据中提取最新记录,让数据清洗和分析变得更高效!下次遇到类似需求时,不妨试试这个强大的row_number()
函数吧!
(注:实际使用时需根据业务需求调整分组字段和排序规则)
到此这篇关于一文详解小白也能懂的sql高效去重技巧的文章就介绍到这了,更多相关sql去重内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论