MySQL之表中重复字段只取第一个值

本文介绍了一种使用SQL的DISTINCT关键字来去除表中重复数据的方法,并详细解释了如何仅保留每组重复数据中的第一条记录。

有时在我们的SQL表中包含很多同名的数据,这样可以将多维度的数据保存的一个表中,但是对于查询会带来一些麻烦。

如上图,如果我们只想取每个学校的第一条数据:我们可以使用DISTINCT去重

SQL:

select DISTINCT history.school From history

这样查询到的是第一个学校名,仅是学校名,想保留该行全部,见 MySQL之保留重复数据第一行

<think>我们面对的问题:从MySQL中根据某一字段去重,并获去重后每组第一条(按某种顺序)的完整记录。 参考引用中提供了几种方法,我们将结合这些方法进行解答。 方法一:使用子查询和GROUP BY(可能结合ORDER BY和LIMIT) 方法二:使用自连接(通过比较主键或唯一标识) 方法三:使用窗口函数(MySQL 8.0以上支持) 由于问题要求获每组的第一条完整记录,我们需要明确“第一条”是按什么顺序?通常我们会指定一个排序字段(如时间、自增ID等)。下面提供几种常见方法: 假设我们有一个`user_companys`,我们想根据`user_id`去重,并且对于每个`user_id`,我们想获最新(按`create_time`降序)的一条记录。 方法1:使用子查询和GROUP BY(适用于大多数情况,但需要注意MySQL的GROUP BY的宽松模式问题) 注意:在MySQL 5.7及以上,如果启用了ONLY_FULL_GROUP_BY模式,那么SELECT的列必须出现在GROUP BY中或者是聚合函数的列。因此,我们可以使用子查询先排序,然后分组。 示例(参考引用[3]中的第一种方法): ```sql SELECT a.* FROM ( SELECT * FROM user_companys ORDER BY create_time DESC ) a GROUP BY a.user_id; ``` 但是,请注意,这种方法在MySQL 5.7及以上版本中,如果启用了ONLY_FULL_GROUP_BY,可能会报错。因此,我们可以使用聚合函数来避免(如方法2)。 方法2:使用MAX()结合GROUP BY,然后通过连接获整行(需要两次查询) 步骤: 1. 先找到每个user_id的最大create_time。 2. 然后根据user_id和这个最大create_time去连接原。 示例: ```sql SELECT uc.* FROM user_companys uc INNER JOIN ( SELECT user_id, MAX(create_time) as max_time FROM user_companys GROUP BY user_id ) grouped ON uc.user_id = grouped.user_id AND uc.create_time = grouped.max_time; ``` 但是,如果同一个user_id在同一时间有两条记录,则会出现重复。此时,我们可以使用其他唯一字段(如主键)来进一步去重。或者,我们可以使用子查询再按主键一次最大(如果存在主键)?或者使用其他方法。 方法3:使用窗口函数(ROW_NUMBER())——MySQL 8.0以上支持(推荐,效率较高且清晰) 我们可以为每个user_id分组内的记录按时间降序排序,然后第一条。 示例: ```sql SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC) as rn FROM user_companys ) t WHERE rn = 1; ``` 这种方法简单明了,且效率较高(如果数据量大,建议在分区字段和排序字段上建立索引)。 方法4:使用自连接(适用于没有窗口函数的老版本) 思路:对于每个user_id,我们选择那些不存在比它create_time更大的记录(或者存在更大则排除)。 示例: ```sql SELECT uc1.* FROM user_companys uc1 LEFT JOIN user_companys uc2 ON uc1.user_id = uc2.user_id AND uc1.create_time < uc2.create_time WHERE uc2.user_id IS NULL; ``` 解释:通过左连接,将同一user_id下所有比当前记录create_time更大的记录连接出来,然后只保留那些没有更大记录的行(即uc2.user_id为NULL)。这样就能得到每个user_id的最新一条记录。 注意:如果同一个user_id有多个相同create_time的记录,那么这些记录都会被保留(因为uc1.create_time < uc2.create_time条件不满足,所以uc2.user_id为NULL)。所以这种方法可能返回多条记录(当create_time相同时)。如果要去重,可以再结合其他条件(比如再按主键ID比较)。 改进(确保每个user_id只返回一条): 我们可以使用一个唯一字段(如自增主键id)来确保唯一性。假设我们想要create_time最大的同时,如果create_time相同,则id最大的: ```sql SELECT uc1.* FROM user_companys uc1 LEFT JOIN user_companys uc2 ON uc1.user_id = uc2.user_id AND (uc1.create_time < uc2.create_time OR (uc1.create_time = uc2.create_time AND uc1.id < uc2.id)) WHERE uc2.user_id IS NULL; ``` 总结:根据MySQL版本和需求选择合适的方法。 推荐: - 如果使用MySQL 8.0+,建议使用窗口函数(方法3)。 - 如果使用旧版本,可以考虑方法2(使用聚合函数和连接)或方法4(自连接),但要注意重复问题。 另外,引用[4]中提到了一种删除重复记录的方法,使用自连接删除重复记录(保留最小id的记录): ```sql DELETE t1 FROM table_name t1 JOIN table_name t2 ON t1.column1 = t2.column1 AND t1.column2 = t2.column2 AND t1.id > t2.id; ``` 这种思路也可以用于查询,比如查询每个重复组中id最小的记录: ```sql SELECT t1.* FROM table_name t1 LEFT JOIN table_name t2 ON t1.column1 = t2.column1 AND t1.column2 = t2.column2 AND t1.id > t2.id WHERE t2.id IS NULL; ``` 这其实就是方法4的思路。 因此,针对去重获每组第一条完整记录,我们可以灵活运用以上方法。 最后,注意:在实际使用中,请根据结构和索引情况选择效率最高的方法,并测试性能。</think>### MySQL 根据字段去重并获每组第一条完整记录 在 MySQL 中根据字段去重并获每组第一条完整记录,常见方法如下(假设名为 `your_table`,去重字段为 `group_field`,排序字段为 `order_field`): --- #### 方法一:子查询 + 窗口函数(推荐,MySQL 8.0+) ```sql SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY group_field ORDER BY order_field DESC -- 按排序字段第一条 ) AS rn FROM your_table ) t WHERE rn = 1; ``` **说明**: 1. `ROW_NUMBER()` 为每组 `group_field` 生成序号(按 `order_field` 排序)。 2. 外层查询筛选序号为 1 的记录(即每组第一条)。 **优点**:高效清晰,支持复杂排序[^3]。 --- #### 方法二:GROUP BY + 聚合函数(兼容低版本) ```sql SELECT t1.* FROM your_table t1 INNER JOIN ( SELECT group_field, MAX(order_field) AS max_order -- 最大/最小的排序 FROM your_table GROUP BY group_field ) t2 ON t1.group_field = t2.group_field AND t1.order_field = t2.max_order; ``` **说明**: 1. 子查询获每组 `group_field` 对应的最大/最小 `order_field`。 2. 通过 JOIN 关联原完整记录。 **注意**:若排序字段重复,可能返回多条记录[^2]。 --- #### 方法三:自连接排除法 ```sql SELECT t1.* FROM your_table t1 LEFT JOIN your_table t2 ON t1.group_field = t2.group_field AND t1.order_field < t2.order_field -- 排除非第一条记录 WHERE t2.group_field IS NULL; ``` **原理**:左连接后,若某记录在组内没有更晚/更早的记录,则 `t2` 字段为 `NULL`,即该记录为组内第一条[^4]。 --- ### 关键注意事项 1. **去重字段选择**:确保 `group_field` 能正确分组(如用户 ID、分类 ID)。 2. **排序字段**:明确 `order_field` 定义“第一条”(如时间戳、自增 ID)。 3. **重复处理**:若排序字段可能重复,需添加第二排序条件(如主键): ```sql ORDER BY order_field DESC, id DESC -- 时间相同则 ID 更大的 ``` 4. **索引优化**:为 `group_field` 和 `order_field` 创建联合索引提升性能。 > ⚠️ 方法二在 `order_field` 重复时可能返回多行,建议优先使用方法一[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值