CREATE TABLE student
(
id
int(11) NOT NULL AUTO_INCREMENT,
name
varchar(32) DEFAULT NULL,
age
int(11) DEFAULT NULL,
class
int(11) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=11
数据:
mysql> select * from student ;
±—±---------±-----±------+
| id | name | age | class |
±—±---------±-----±------+
| 1 | xiaoming | 11 | 1 |
| 2 | xiao1 | 12 | 1 |
| 3 | xiao2 | 13 | 1 |
| 4 | xiao3 | 15 | 1 |
| 5 | xiao4 | 9 | 1 |
| 6 | xiao4 | 9 | 2 |
| 7 | xiao455 | 11 | 2 |
| 8 | xiao455 | 31 | 2 |
| 9 | xiao434 | 22 | 2 |
| 10 | xxx | 11 | 1 |
±—±---------±-----±------+
10 rows in set (0.00 sec)
方式1:通过笛卡尔迪,实现组内关联后,按照组内排序字段进行聚合。
需求:求出每个班级的学生年龄分布,按照年龄/ID 升序显示,并显示行号。
:SELECT a.* ,count(1) cc FROM student a LEFT JOIN student b ON a.class = b.class AND (a.age > b.age or (a.age=b.age and a.id>=b.id )) GROUP BY a.class, a.id order by a.class,cc ;
±—±---------±-----±------±—+
| id | name | age | class | cc |
±—±---------±-----±------±—+
| 5 | xiao4 | 9 | 1 | 1 |
| 1 | xiaoming | 11 | 1 | 2 |
| 10 | xxx | 11 | 1 | 3 |
| 2 | xiao1 | 12 | 1 | 4 |
| 3 | xiao2 | 13 | 1 | 5 |
| 4 | xiao3 | 15 | 1 | 6 |
| 6 | xiao4 | 9 | 2 | 1 |
| 7 | xiao455 | 11 | 2 | 2 |
| 9 | xiao434 | 22 | 2 | 3 |
| 8 | xiao455 | 31 | 2 | 4 |
±—±---------±-----±------±—+
10 rows in set (0.00 sec)
分析:我们可以让student表left join student表,做一个笛卡尔乘积,但是关联条件,一定是分组字段,然后因为用年龄作为排序,所以可以用年龄筛选,判断a.age大于b.age,因为最小的为9,因此0个大于9,而等于9的要求ID>=当前ID,结果age=9,的count=1。
对于id=1的,age=11,关联条件1为a.age>b.age,结果为1个,关联条件2:a.age=b.age 且a.id>=b.id,因此对于id=1 ,该条件可以总2条关联。
对于id=10的age=11,第一个条件可以拿到1条,第二个条件可以拿到2条,因此总共为3.
为什么不能直接使用关联a.age>=b.age??
因为这样的话,当age出现重复的数据时,会得到一样的count(1),因此需要判断当age一样的时候,a.id>=b.id,这样也可以把重复的数据再次按照id排序出count的值来。
方式二:使用变量:
select @rownum:=@rownum+1,
if(@class=b.class,@rank:=@rank+1,@rank:=1),
@class:=b.class,
b.*
FROM ( SELECT * FROM student ORDER BY class ,age ) b, ( SELECT @rownum := 0, @class := NULL, @rank := 0) a;
原理:先按照我们要分组的字段,以及组内排序字段,进行整体排序:然后从头到尾进行遍历,一个组一个组的排序,递增rank的值。当分组字段的值和上次不一样了,就重置rank的值。
±—±---------±-----±------+
| id | name | age | class |
±—±---------±-----±------+
| 5 | xiao4 | 9 | 1 |
| 1 | xiaoming | 11 | 1 |
| 10 | xxx | 11 | 1 |
| 2 | xiao1 | 12 | 1 |
| 3 | xiao2 | 13 | 1 |
| 4 | xiao3 | 15 | 1 |
| 6 | xiao4 | 9 | 2 |
| 7 | xiao455 | 11 | 2 |
| 9 | xiao434 | 22 | 2 |
| 8 | xiao455 | 31 | 2 |
±—±---------±-----±------+
然后设置三个变量,实际上只需要设置两个即可,即@class,@rank即可,@class存储上一行的clas是什么,rank表示上一次rank的值。
:select @rownum:=@rownum+1,
if(@class=b.class,@rank:=@rank+1,@rank:=1),
@class:=b.class,
b.*
FROM ( SELECT * FROM student ORDER BY class ,age ) b, ( SELECT @rownum := 0, @class := NULL, @rank := 0) a;
±-------------------±-------------------------------------------±----------------±—±---------±-----±------+
| @rownum:=@rownum+1 | if(@class=b.class,@rank:=@rank+1,@rank:=1) | @class:=b.class | id | name | age | class |
±-------------------±-------------------------------------------±----------------±—±---------±-----±------+
| 1 | 1 | 1 | 5 | xiao4 | 9 | 1 |
| 2 | 2 | 1 | 1 | xiaoming | 11 | 1 |
| 3 | 3 | 1 | 10 | xxx | 11 | 1 |
| 4 | 4 | 1 | 2 | xiao1 | 12 | 1 |
| 5 | 5 | 1 | 3 | xiao2 | 13 | 1 |
| 6 | 6 | 1 | 4 | xiao3 | 15 | 1 |
| 7 | 1 | 2 | 6 | xiao4 | 9 | 2 |
| 8 | 2 | 2 | 7 | xiao455 | 11 | 2 |
| 9 | 3 | 2 | 9 | xiao434 | 22 | 2 |
| 10 | 4 | 2 | 8 | xiao455 | 31 | 2 |
±-------------------±-------------------------------------------±----------------±—±---------±-----±------+
10 rows in set, 7 warnings (0.00 sec)
这样就先当于先排序,然后在从头到尾进行遍历,一组一组的设置行号+1,当组变了的时候,rank重置为1.