取数据库中分组后每一个人的第一条数据

本文介绍了一种在 Oracle 数据库中提取特定数据的方法,利用 row_number() 函数配合子查询实现按指定条件选取记录。此外还对比了使用 rank() 函数的情况,并提供了 SQL Server 下类似操作的示例。

转载自:http://www.itpub.net/thread-1736695-1-1.html

如图


取出黄色标记的数据


解决方法:(在oracle成功)

select t.name1,t.name2,t.zx 
from (select row_number()over(partition by name1 order by zx desc)rn, test.* from test)t
where t.rn=1;

用rank的话你zx有值相等的,还是用row_number好了

法:

查询其他的:

sqlServer:类似的情况(未测试)

select * from t1 t where id =  (select top 1 id from t1 where id = t.id order by createtime desc)


<think>我们有一个需求:根据日期和分组,获每天每个最后一条消息。 假设我们有一个消息表(例如:messages),包含以下字段: - id: 消息的唯一标识 - person_id: 发送消息的 - content: 消息内容 - created_at: 消息发送的时间(包括日期和时间) 我们需要按日期(即created_at的日期部分)和person_id分组,然后获每个分组内最后一条消息(即created_at最大的那条消息)。 方法一:使用子查询 我们可以先按日期(使用DATE(created_at))和person_id分组,然后找到每个分组内最大的created_at(即最后时间)。然后通过这个结果去关联原表,获对应的整条消息。 但是注意:同一个的同一天可能有多条消息,我们只需要每个分组(每天每)中最后的那一条。 方法二:使用窗口函数(ROW_NUMBER) 在MySQL 8.0及以上版本,我们可以使用窗口函数ROW_NUMBER()来为每个分组内的记录按时间倒序排列,然后每个分组第一条。 这里我们提供两种方法的SQL语句。 方法一(适用于所有MySQL版本): 步骤: 1. 首先,我们按日期(DATE(created_at))和person_id分组,并找出每个组内最大的created_at(即最后一条消息的时间)。 2. 然后,将原表与这个结果进行连接,条件是相同的person_id、相同的日期以及相同的时间(即created_at相等)。 但是注意:有可能同一个的同一天有两条消息在同一秒(甚至同一毫秒)发送,那么它们会有相同的created_at。这样可能会返回多条记录。但通常,我们假设created_at可以精确到足够区分每条消息(比如使用时间戳或包含微秒的datetime)。如果确实存在重复,那么可能需要其他条件(如最大id)来确保唯一性,但题目要求是最后一条消息,我们按最大时间,如果时间相同,可以再id最大的。 然而,题目要求是最后一条消息,我们按时间最大来,如果时间相同,则任意一条(或者按id最大)?题目没有明确,我们先按时间最大,如果时间相同,则id最大的(即最新插入的那条)。 因此,我们可以先获每个分组(每天每)的最大时间和最大id(如果时间相同,id最大的),但这样需要两个字段。另一种做法是先按时间和id排序,然后每个分组第一条。 方法一改进(使用两个子查询): 先获每个分组(每天每)的最大时间,然后在这个时间下再最大id(如果时间相同,id最大的)。但这样需要两次分组。 但是,更高效的做法是使用一个子查询得到每个分组(每天每)的最大时间,然后通过这个时间和person_id以及日期来关联原表,同时再关联一个条件:如果同一个分组有多个相同时间的记录,我们id最大的。所以我们可以这样: 子查询1:按日期和person_id分组,得到最大时间。 然后原表与子查询1连接,条件是原表.person_id=子查询1.person_id and DATE(原表.created_at)=子查询1.date and 原表.created_at=子查询1.max_created_at 然后,再按日期、person_id和这个时间分组id最大的(因为同一个时间可能有多条,我们id最大的)。 但这样可能比较繁琐。 方法一替代:使用相关子查询(但可能效率不高) 方法二(窗口函数,MySQL 8.0+): 使用ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) 然后序号为1的。 这里我们给出两种方法的SQL。 方法一(使用两个子查询): 步骤: 1. 先按日期(DATE(created_at))和person_id分组,得到每个组的最大时间:max_created_at。 2. 再按日期(DATE(created_at))、person_id和max_created_at分组,得到最大id(因为同一个的同一天同一个时间可能有多条消息,我们id最大的那条)。 3. 然后根据这些条件去原表查询。 或者,我们可以将第一步和第二步合并为一个子查询,即先按日期和person_id分组,然后每个分组内created_at最大且id最大的记录?但是分组一次只能一个最大值,不能同时两个字段的最大值(除非使用group_concat等,但这样复杂)。 另一种思路:使用自连接和左连接排除 这里我们选择使用窗口函数(如果MySQL版本支持)会更简单。 方法二(窗口函数)的SQL: ```sql SELECT id, person_id, content, created_at FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY DATE(created_at), person_id ORDER BY created_at DESC, id DESC) AS rn FROM messages ) AS ranked WHERE rn = 1; ``` 解释: 在子查询中,我们按日期(DATE(created_at))和person_id进行分组(分区),在每个分区内,我们按照created_at降序排列(这样最新的排在最前面),如果created_at相同,则按id降序排列(这样id最大的)。然后我们每个分区的第一行(rn=1)。 方法一(使用自连接和子查询)的SQL: 我们可以先获每个分组(每天每)的最大时间,然后获在这个时间下最大的id(如果同一个时间有多条记录)。然后通过这两个字段去原表查询。 但是这样需要两个子查询,而且如果索引不合适,效率可能不高。 另一种方法:使用内连接,连接条件是同一个的同一天,且原表的created_at等于最大时间,然后在这个基础上再id最大的。我们可以这样: ```sql SELECT m1.* FROM messages m1 INNER JOIN ( SELECT DATE(created_at) AS date, person_id, MAX(created_at) AS max_created_at FROM messages GROUP BY DATE(created_at), person_id ) AS latest ON DATE(m1.created_at) = latest.date AND m1.person_id = latest.person_id AND m1.created_at = latest.max_created_at ``` 但是,这个查询可能返回多行(如果同一个的同一天有多个消息在同一时间)。所以我们需要进一步去重。我们可以再嵌套一层,按日期、person_id和最大时间分组,然后id最大的: ```sql SELECT m1.* FROM messages m1 INNER JOIN ( SELECT DATE(created_at) AS date, person_id, MAX(created_at) AS max_created_at, MAX(id) AS max_id -- 如果同一个时间有多条,我们id最大的 FROM messages GROUP BY DATE(created_at), person_id ) AS latest ON DATE(m1.created_at) = latest.date AND m1.person_id = latest.person_id AND m1.created_at = latest.max_created_at AND m1.id = latest.max_id ``` 但是,这里有一个问题:在分组时,我们的最大id可能不是最大时间对应的那条消息?因为分组后,我们同时了最大时间和最大id,但是这两个最大值可能来自不同的记录。所以这样是不安全的。 因此,我们需要确保最大时间和最大id来自同一条记录。所以不能这样分组。 修正:先按日期和person_id分组得到最大时间,然后在这个分组的基础上,再按日期、person_id和最大时间去原表这个时间点下最大的id。这样就需要两个子查询: ```sql SELECT m1.* FROM messages m1 INNER JOIN ( -- 先得到每个分组(每天每)的最大时间 SELECT DATE(created_at) AS date, person_id, MAX(created_at) AS max_created_at FROM messages GROUP BY DATE(created_at), person_id ) AS latest_time ON DATE(m1.created_at) = latest_time.date AND m1.person_id = latest_time.person_id AND m1.created_at = latest_time.max_created_at -- 然后,再在同一个时间点内,id最大的(因为同一个时间点可能有多条) INNER JOIN ( SELECT created_at, person_id, MAX(id) AS max_id FROM messages GROUP BY DATE(created_at), person_id, created_at -- 注意,这里我们按精确的created_at分组 ) AS latest_id ON m1.created_at = latest_id.created_at AND m1.person_id = latest_id.person_id AND m1.id = latest_id.max_id ``` 但是这样写很复杂,而且效率可能不高。 因此,我们更推荐使用窗口函数的方法(如果MySQL版本是8.0+)。 如果MySQL版本较低(比如5.7),我们可以使用以下方法: 使用相关子查询来获每个分组(每天每)的最后一条消息(按时间倒序,如果时间相同按id倒序,一个): ```sql SELECT * FROM messages m WHERE m.id = ( SELECT id FROM messages WHERE DATE(created_at) = DATE(m.created_at) AND person_id = m.person_id ORDER BY created_at DESC, id DESC LIMIT 1 ) ``` 但是,这个查询会为每一行都执行一次子查询,效率很低,尤其是数据量大时。 另一种方法:使用LEFT JOIN来排除那些不是最后一条的记录。 ```sql SELECT m1.* FROM messages m1 LEFT JOIN messages m2 ON DATE(m1.created_at) = DATE(m2.created_at) AND m1.person_id = m2.person_id AND (m1.created_at < m2.created_at OR (m1.created_at = m2.created_at AND m1.id < m2.id)) WHERE m2.id IS NULL ``` 解释: 我们自连接,条件是同一个日期和同一个person_id,然后m1.created_at小于m2.created_at(说明m2比m1晚),或者时间相同但是m2的id更大(说明m2是更晚插入的)。那么,如果存在这样的m2,就说明m1不是这个分组中最后的一条。所以我们只保留那些没有找到m2的记录(即m2.id IS NULL),这些记录就是每个分组中created_at最大且id最大的。 这种方法在数据量大的时候可能效率也不高,但比相关子查询好一些。 总结:根据MySQL版本选择合适的方法。 如果MySQL版本>=8.0,推荐使用窗口函数。 如果MySQL版本<8.0,可以使用自连接的方法(但要注意性能)。 因此,我们给出两种方法的SQL: 方法1(窗口函数,MySQL>=8.0): ```sql SELECT id, person_id, content, created_at FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY DATE(created_at), person_id ORDER BY created_at DESC, id DESC ) AS rn FROM messages ) AS ranked WHERE rn = 1; ``` 方法2(自连接,适用于所有版本,但需注意性能): ```sql SELECT m1.* FROM messages m1 LEFT JOIN messages m2 ON DATE(m1.created_at) = DATE(m2.created_at) AND m1.person_id = m2.person_id AND ( m1.created_at < m2.created_at OR (m1.created_at = m2.created_at AND m1.id < m2.id) ) WHERE m2.id IS NULL; ``` 注意:自连接方法中,我们使用了严格的比较,确保每个分组只有一条记录被选出。 根据实际情况选择合适的方法。 另外,为了提高查询效率,建议在`created_at`和`person_id`上建立索引。 引用说明:我们在回答中使用了数据库查询的一般方法,没有直接引用外部资料,但可以参考数据库查询优化的通用实践[^1]。 §§相关问题§§ 1. 如果消息表非常大,如何优化这个查询? 2. 在MySQL 5.7中,除了自连接还有哪些方法可以获分组后的最大/最小值对应的行? 3. 窗口函数ROW_NUMBER()和RANK()、DENSE_RANK()在分组查询中有什么区别?</think>### MySQL查询:按日期和分组每天每最后一条消息 假设有一个消息表 `messages`,包含以下字段: - `id`:消息ID(主键) - `person_id`:用户ID - `content`:消息内容 - `created_at`:消息时间戳(包含日期和时间) 以下是两种实现方法: #### ✅ 方法1:使用ROW_NUMBER()窗口函数(MySQL 8.0+推荐) ```sql SELECT id, person_id, content, created_at FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY DATE(created_at), person_id ORDER BY created_at DESC ) AS rn FROM messages ) AS ranked WHERE rn = 1; ``` **说明**: 1. `DATE(created_at)` 提日期部分 2. `PARTITION BY DATE(created_at), person_id` 按日期和分组 3. `ORDER BY created_at DESC` 按时间倒序排列 4. `rn = 1` 每组第一条(即最新消息) #### ✅ 方法2:使用子查询(兼容MySQL 5.7及以下) ```sql SELECT m1.* FROM messages m1 INNER JOIN ( SELECT DATE(created_at) AS msg_date, person_id, MAX(created_at) AS latest_time FROM messages GROUP BY DATE(created_at), person_id ) AS latest ON DATE(m1.created_at) = latest.msg_date AND m1.person_id = latest.person_id AND m1.created_at = latest.latest_time; ``` **说明**: 1. 子查询获每天每的最新时间戳 2. 通过内连接匹配完整消息记录 3. 使用`DATE(created_at)`确保日期匹配 ### 性能优化建议 1. **索引优化**: ```sql CREATE INDEX idx_time_person ON messages (DATE(created_at), person_id, created_at); ``` 2. **分区表**:按日期范围分区可加速查询 3. **避免全表扫描**:确保`created_at`和`person_id`有索引 > 注意:如果同秒内有多条消息,可添加`id DESC`排序确保结果唯一性(如`ORDER BY created_at DESC, id DESC`)[^1]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值