今天处理门禁记录排序的时候,发现使用rownum得到的结果并不正确,于是仔细研究了一下。

    因为oracle没有top关键字,所以不能像SQL Server一样通过top取结果集中的前N条记录。Oralce中提供了一个伪列rownum,可以在返回查询结果集中返回自动添加的行号。于是想通过rownum获取排名前10的入馆记录。

    1. select gate from reader
    2. where
    3. rownum <= 10
    4. order by gate desc

    但是,通过对比不使用rownum得到的结果,发现此语句得到的并不是排名前10 的入馆记录:
    oracle_rownum1.png
    正确的排序结果应为:
    oracle_rownum2.png

    分析后发现原因是,rownum是查询时数据库为查询结果集自动添加的行号。通过Oracle的explain可以看出,执行select的时候rownum取回的记录先后顺序添加行号的,那么通过rownum<=10得到是最先被取回的10条记录,而不是按照gate排序后得到的前10条记录。网上搜了一下rownum的说明,select语句的执行顺序和order by的字段有关系,如果order by 的字段是主键字段,则是先排序,再取10条,而order by字段不是主键字段时,是先取10条再排序,此时结果就与要求不一样了。
    oracle_rownum_explain1.png

    为了获得正确结果,可以先将结果集排序,然后在子查询中使用rownum取回排名前10的记录。对子查询记录的取回先后顺序,是按照子查询记录的排序执行的,所以rownum顺序跟子查询的排序顺序一致。

    1. select gate from (
    2. select * from xmu_sbg_mj
    3. order by gate desc
    4. ) where rownum <= 10

    oracle_rownum_explain2.png

    这个问题,还可以通过分析函数row_number()完成。row_number()是oracle的分析函数,排名按照记录集中记录的顺序依次递增,遇到相同的数值排名依次递增。row_number()可以按照不同的分组进行组内排序。
    如按照学院分组,然后进行排序。

    1. select cert_id, gate, dept,
    2. row_number() over (partition by dept order by gate desc) dept_rank
    3. from reader

    再介绍两个不是连续排名的分析函数
    RANK()
    Rank()函数也是oracle的分析函数,遇到相同的数据,此时所有相同数据返回的排名值是一样的。同时会在最后一条相同记录和下一条不同记录的排名之间空出排名。rank()是跳跃排序,有两个第二名时接下来就是第四名。同样是可以实现在不同的分组内进行排序。rank over()的时候,空值是最大的,如果排序字段为null, 可能造成null字段排在最前面,影响排序结果。
    可以这样: rank over(partition by course order by score desc nulls last)

    1. select cert_id, gate, dept,
    2. rank() over (partition by dept order by gate desc nulls last) dept_rank
    3. from reader

    DENSE_RANK()
    Dense_rank函数也是oracle的分析函数,返回一个唯一的值,当碰到相同数据,此时所有相同数据的排名都是一样的。dense_rank()是连续排序,有两个第二名时仍然跟着第三名。它和row_number的区别在于row_number是没有重复值的。

    1. select cert_id, gate, dept,
    2. dense_rank() over (partition by dept order by gate desc) dept_rank
    3. from reader

    注意分析函数中partition by和group by的区别:
    1. partition by只是将原始数据进行名次排列(记录数不变)
    2. group by是对原始数据进行聚合统计(记录数可能变少, 每组返回一条)