场景:
例如成绩,需要得到第二名的同学的名字;或者例如薪水,想要查询到薪水第三高的员工的名字;类似种种问题,做个总结。
———————————————————————————
1.Oracle语境
Oracle语境下,这里按照成绩为例先建表。
create table test_data( id varchar2(32), name varchar2(32), score float ) insert into test_data values('xxx1', '小明', 97); insert into test_data values('xxx2', '小军', 93); insert into test_data values('xxx3', '小红', 97); insert into test_data values('xxx4', '小黑', 95);
查询得到数据如下:
(4行被选择)
我们想要得到目前小黑排名第二的成绩,考虑到Oracle提供一个rownum的属性,我Order by一下然后取rownum为2不就行了嘛,于是可以得到如下sql:
select name from (select t.* from test_data t order by t.score desc) where rownum = 2
得到结果:
(0行被选择)
诶?这是为什么呢?我明明先进行了排序,然后取rownum为2不就应该查出来小黑的数据嘛?
这里我查询了一下Oracle的rownum属性,其实是一个很神奇的属性,它是对于每一条数据进行比对的,比如这里选择了rownum = 2,其实每一行的rownum都是1,所以并不会得到想要的结果。详细的解释见https://blog.csdn.net/haijiege/article/details/78856307
如何解决?——取一个别名
对没错,就是取一个别名,如下:
select name from (select name, rownum rn from (select t.* from test_data t order by t.score desc)) where rn = 2
这样查询到的结果为:
(1行被选择)
诶?我明明想得到小黑的名字,怎么会得到小红的呢?其实这里存在一个相同元素排序的问题,由于前面两个人成绩都是97,所以排在第二位的也是97的小红(id小明的ASCII码稍微比小红的小一些),这种情况该怎么解决呢?
这就需要对于每一行的成绩进行比对了,查询大于目前该行成绩结果的总行数作为目前的排名,可以得到如下SQL:
select name from (select name, (select count(distinct score) from test_data where score >= s.score) as rn from test_data s order by score desc) where rn = 2
查询结果:
(1行被选择)
这样就可以得到预期结果啦!
2.Mysql语境
思路大致与上述相同,只不过语句不太一样,这里使用存储过程(函数)来说明
CREATE FUNCTION getNthScore ( n INT ) RETURNS INT BEGIN SET n = n - 1; RETURN ( SELECT NAME FROM test_data WHERE score = ( SELECT DISTINCT score FROM test_data ORDER BY score DESC LIMIT n, 1 ) ); END
按照limit查询排名第几的成绩,然后反向去查找对应的名字
这样输入n=2,查询得到结果为:
(第1条记录,共1条)
———————————————————————————
至此!