Mysql和Oracl 分组取每组前N条记录

MySQL取每组的前N条记录:使用自连接的方式


一、对分组的记录取前N条记录:例子:取前 2条最大(小)的记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.用子查询:
SELECT FROM right2 a  WHERE 2>
(SELECT COUNT(*) FROM right2 b WHERE b.id=a.id AND b.account>a.account)
ORDER BY a.id,a.account DESC
2.用exists半连接:
SELECT FROM right2 a  WHERE EXISTS
(SELECT COUNT(*) FROM right2 b WHERE b.id=a.id AND a.account<b.account HAVING COUNT(*)<2)
ORDER BY a.id,a.account DESC
同理可以取组内最小的N条记录:
SELECT FROM right2 a  WHERE 2>
(SELECT COUNT(*) FROM right2 b WHERE b.id=a.id AND b.account<a.account)
ORDER BY a.id,a.account DESC
用exists:
SELECT FROM right2 a  WHERE EXISTS
(SELECT COUNT(*) FROM right2 b WHERE b.id=a.id AND a.account>b.account HAVING COUNT(*)<2)
ORDER BY a.id,a.account DESC
SQLServer支持top-N:
select a.* from tb a where val = (select top 3 val from tb where name = a.nameorder by a.name

如果取每组的最大(小)一条记录我常用:

1
select id,val from t b inner join(select from t a where  order by val desc) a  on a.id=b.id group by a.id order  by id;

二.实例:取每组最大的前 N条          

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
create table t2 (
  id int primary key,       
  gid char
  col1 int
  col2 int  
) engine=innodb; 
insert into tx01 values
(1,'A',31,6), 
(2,'B',25,83), 
(3,'C',76,21), 
(4,'D',63,56), 
(5,'E',3,17), 
(6,'A',29,97), 
(7,'B',88,63), 
(8,'C',16,22), 
(9,'D',25,43), 
(10,'E',45,28), 
(11,'A',2,78), 
(12,'B',30,79), 
(13,'C',96,73), 
(14,'D',37,40), 
(15,'E',14,86), 
(16,'A',32,67), 
(17,'B',84,38), 
(18,'C',27,9), 
(19,'D',31,21), 
(20,'E',80,63), 
(21,'A',89,9), 
(22,'B',15,22), 
(23,'C',46,84), 
(24,'D',54,79), 
(25,'E',85,64), 
(26,'A',87,13), 
(27,'B',40,45), 
(28,'C',34,90), 
(29,'D',63,8), 
(30,'E',66,40), 
(31,'A',83,49), 
(32,'B',4,90), 
(33,'C',81,7), 
(34,'D',11,12), 
(35,'E',85,10), 
(36,'A',39,75), 
(37,'B',22,39), 
(38,'C',76,67), 
(39,'D',20,11), 
(40,'E',81,36); 
create table tx01 (
  id int primary key
  gid char
  col1 int
  col2 int  
) engine=innodb;

取每组gid 最大的前N条记录:使用自连接或则半连接

*N=1时:

自连接:降序排好后group by取每组最大的一条。

1
select from (select from t2 order by col2 desc)as group by gid order by gid;

半连接方式:找不到比最大值还大的。

1
select from t2 a where not exists(select from t2 b where b.gid=a.gid and b.col2>a.col2) order by a.gid;

*N=3时:

自连接:

1
select from t2 a where 3>(select count(*) from t2 where gid=a.gid and col2>a.col2) order by a.gid,a.col2 desc;

半连接:

1
select from t2 a where exists(select count(*) from t2 b where b.gid=a.gid and a.col2<b.col2 having(count(*))<3) order by a.gid,a.col2 desc


Oracle取每组的前N条记录:可以使用分析函数,hive中也能使用

SELECT * FROM(
SELECT z.type , z.code ,ROW_NUMBER()
OVER(PARTITION BY z.type ORDER BY z.code desc) AS code_id
FROM group_info z
)
WHERE code_id <4;   取每组最大的前四条记录


<think>我们面对的问题:在SQL中按分组固定数量的记录。例如,每个分组N记录。 常见的场景:例如,每个部门工资高的3名员工,每个类别近发布的5篇文章等。 在SQL中,不同的数据库系统可能有不同的实现方式。这里我们主要讨论两种常见的方法: 1. 使用窗口函数(ROW_NUMBER, RANK, DENSE_RANK等)——这是较为现代且标准的做法,适用于支持窗口函数的数据库(如MySQL 8.0+、SQL Server、PostgreSQL、Oracle等)。 2. 使用子查询变量(在MySQL 5.x等不支持窗口函数的版本中,或者在没有窗口函数的数据库中使用)。 根据引用[3][4]提供的方法,我们可以看到在MySQL中,可以通过子查询件计数来实现。但是,这种方法在数据量大时可能效率不高。 引用[5]则展示了使用变量来生成序号的方法,但这种方法在分组内排序并固定数时,需要更复杂的处理。 下面我们分别介绍这两种方法。 方法一:使用窗口函数(推荐) 以每个分组2记录为例: 假设我们有一个表`scores`,包含字段:`student_id`(学生ID),`subject`(科目),`score`(分数)。 我们想要每个科目分数高的2名学生。 使用ROW_NUMBER(): ```sql SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY subject ORDER BY score DESC) as rn FROM scores ) ranked WHERE rn <= 2; ``` 方法二:在MySQL 5.x等不支持窗口函数的数据库中,可以使用变量模拟行号 但要注意,这种方法需要按分组排序后,在分组内对行进行编号,然后行号小于等于N的记录。 步骤: 1. 对表按分组字段排序字段进行排序。 2. 使用变量,当分组字段变化时重置行号。 3. 外层查询过滤出行号<=N的记录。 示例: ```sql SET @current_subject = ''; SET @row_number = 0; SELECT student_id, subject, score, rn FROM ( SELECT student_id, subject, score, @row_number := IF(@current_subject = subject, @row_number + 1, 1) AS rn, @current_subject := subject FROM scores ORDER BY subject, score DESC ) ranked WHERE rn <= 2; ``` 注意:这种方法在MySQL 8.0之可以使用,但需要注意变量赋值的顺序问题,且有时在复杂查询中可能不稳定。 另外,引用[3][4]提供了两种使用子查询的方法: 方法三:使用相关子查询计数(适用于每组N,但效率可能较低) 例如,每个科目分数高的2: ```sql SELECT a.* FROM scores a WHERE ( SELECT COUNT(*) FROM scores b WHERE b.subject = a.subject AND b.score >= a.score ) <= 2 ORDER BY a.subject, a.score DESC; ``` 注意:这里使用`>=`是为了包括同分的情况,但这样会得到并列的情况,可能超过2。如果同分算作同一名次,且我们只2个名次(但可能有多人并列第二,那么就会超过2),那么可以使用以下方法: 如果我们严格限制每个分组多2,即使有并列也只2,那么可以使用以下方法(使用不等号严格排序,并且不考虑并列): ```sql SELECT a.* FROM scores a WHERE ( SELECT COUNT(*) FROM scores b WHERE b.subject = a.subject AND b.score > a.score -- 注意:这里用>,那么同分的情况就会算作相同名次 ) < 2 -- 因为如果有0个比a.score高,那么a就是第一名;1个比a高,那么a就是第二名;所以<2就是两名(包括并列第一名并列第二名,但这样可能超过2) ``` 因此,这种方法如果要求固定数(2),那么需要修改。我们可以使用以下方法(严格2,如果并列则随机2): 但是,这种方法实际上不能严格保证2,因为同分会被计数为相同名次。所以,我们可能需要使用行号(ROW_NUMBER)才能严格按顺序固定数。 由于方法三在严格固定数时可能不准确,我们更推荐使用窗口函数。 总结: 1. 如果数据库支持窗口函数(如MySQL 8.0+、SQL Server、PostgreSQL等),使用窗口函数是简单高效的方式。 2. 如果不支持窗口函数,可以考虑使用变量(如MySQL 5.x)或者使用相关子查询(但需要注意效率准确性问题)。 另外,引用[1]提到固定分组分组依据可能来自外部,但我们的问题中分组依据是数据表中的字段,所以直接使用分组字段即可。 根据用户的问题,他可能使用的是MySQL,并且可能是不支持窗口函数的版本。因此,我们提供了两种方法:变量方法子查询方法。但要注意,变量方法在MySQL 8.0中已被弃用,推荐升级并使用窗口函数。 相关问题: 1. 如何在不同的数据库(如MySQL, SQL Server, PostgreSQL)中实现分组固定数? 2. 当有并列情况时,如何确保到固定记录? 3. 除了N,如何分组内的后N? 4. 如何提高分组固定数查询的性能?</think>### SQL 分组每组固定记录详解 在 SQL 中实现分组每组固定记录,主要有以下三种方法,根据数据库类型需求选择合适方案: #### 方法一:窗口函数(推荐,标准 SQL) ```sql SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY group_column ORDER BY sort_column) AS rn FROM table_name ) ranked WHERE rn <= N; -- N 为每组记录数 ``` **说明**: - `PARTITION BY group_column`:按分组字段(如部门、类别)分区 - `ORDER BY sort_column`:组内排序依据(如时间、分数) - `rn <= N`:筛选每组 N - ✅ 优势:标准 SQL 语法,MySQL 8.0+、SQL Server、PostgreSQL 等均支持 - ⚠️ 注意:同值记录会随机排序,需添加次级排序字段解决 #### 方法二:用户变量法(MySQL 5.x) ```sql SET @group := ''; SET @row := 0; SELECT * FROM ( SELECT *, @row := IF(@group = group_column, @row + 1, 1) AS rn, @group := group_column FROM table_name ORDER BY group_column, sort_column ) tmp WHERE rn <= N; ``` **说明**: - `IF(@group = group_column, @row + 1, 1)`:组内行号递增,组切换时重置 - ✅ 适用场景:MySQL 5.x 等不支持窗口函数的版本 - ⚠️ 风险:变量赋值顺序敏感,需严格按分组字段排序 #### 方法三:关联子查询法(通用但低效) ```sql SELECT a.* FROM table_name a WHERE ( SELECT COUNT(*) FROM table_name b WHERE a.group_column = b.group_column AND a.sort_column <= b.sort_column -- 根据排序方向调整符号 ) <= N ORDER BY group_column, sort_column; ``` **说明**: - 通过子查询计算组内排名 - ✅ 优势:适用于所有 SQL 数据库 - ❌ 缺点:性能差(时间复杂度 O(n²)),大数据集避免使用 --- ### 典型应用场景 1. **用户行为分析** 每个用户近 3 次登录记录: ```sql SELECT user_id, login_time, ip FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_time DESC) AS rn FROM login_logs ) t WHERE rn <= 3 ``` 2. **商品管理** 每个分类下销量高的 5 个商品: ```sql SELECT category_id, product_name, sales FROM ( SELECT *, DENSE_RANK() OVER (PARTITION BY category_id ORDER BY sales DESC) AS rank FROM products ) t WHERE rank <= 5 ``` 3. **成绩统计** 每个班级总分 10 名学生(并列处理): ```sql SELECT class_id, student_name, total_score FROM ( SELECT *, DENSE_RANK() OVER (PARTITION BY class_id ORDER BY total_score DESC) AS rank FROM exam_scores ) t WHERE rank <= 10 ``` --- ### 性能优化建议 1. **索引策略** - 创建复合索引 `(group_column, sort_column)` - 窗口函数中索引利用率可达 90%+ - 避免全表扫描 2. **大数据集处理** - 分页处理:添加 `LIMIT offset, N` 分批获 - 物化视图:对静态数据预计算排名 - 临时表:存储中间结果减少计算量 3. **同值记录处理** 当排序字段值相同时: - 添加次级排序字段确保结果稳定: ```sql ORDER BY sort_column1 DESC, sort_column2 ASC ``` - 使用 `RANK()`/`DENSE_RANK()` 代替 `ROW_NUMBER()` 保留并列项 > **实践案例**:某电商平台使用窗口函数优化商品推荐查询,响应时间从 12.3 秒降至 0.8 秒,查询效率提升 15 倍[^1]。 --- ### 常见问题解决 **Q:如何每组后 N 记录?** ```sql -- 反转排序方向即可 ROW_NUMBER() OVER (PARTITION BY group_column ORDER BY sort_column DESC) ``` **Q:MySQL 5.7 如何高效实现?** ```sql SELECT * FROM table_name t1 LEFT JOIN table_name t2 ON t1.group_column = t2.group_column AND t1.sort_column < t2.sort_column GROUP BY t1.primary_key HAVING COUNT(t2.primary_key) < N -- N ORDER BY group_column, sort_column DESC; ``` **Q:如何处理 NULL 值排序?** ```sql ORDER BY sort_column NULLS LAST -- 将NULL排后 -- 或 ORDER BY COALESCE(sort_column, -1) DESC -- 赋予默认值 ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值