分析函数是Oracle从8.1.6开始引入的一个新的概念,为我们分析数据提供了一种简单高效的处理方式。在分析函数出现以前,我们必须使用自联查询,子查询或者内联视图,甚至复杂的存储过程实现的语句,现在只要一条简单的SQL语句就可以实现了,而且在执行效率方面也有相当大的提高。下面我将针对分析函数做一些具体的说明。
分析函数的一般格式是函数名(参数列表) over ([partition by 字段名或表达式] [order by 字段名或表达式]),其中over()部分称为开窗函数,它是可以选填的。
    开窗函数指定了分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变化而变化,举例如下:
over(order by salary) 按照salary排序进行累计,order by是个默认的开窗函数
over(partition by deptno)按照部门分区
over(order by salary range between 50 preceding and 150 following)
每行对应的数据窗口是之前行幅度值不超过50,之后行幅度值不超过150
over(order by salary rows between 50 preceding and 150 following)
每行对应的数据窗口是之前50行,之后150行
over(order by salary rows between unbounded preceding and unbounded following)
每行对应的数据窗口是从第一行到最后一行,等效:
over(order by salary range between unbounded preceding and unbounded following)
分析函数用于计算基于组的某种聚合值,它和聚合函数的不同之处是对于每个组返回多行,而聚合函数对于每个组只返回一行。
许多分析函数同时也是聚合函数,比如sum()函数,这样使用就是聚合函数。
SQL> select department_id,sum(salary) sum_salary from employees group by department_id;
而这样使用就是分析函数。
SQL> select distinct department_id,sum(salary) over(partition by department_id) sum_salary from employees ;
它们得出的结果是相同的,都是:
DEPARTMENT_ID SUM_SALARY
------------- ----------
           10       4400
           20      19000
           30      24900
           40       6500
           50     156400
           60      28800
           70      10000
           80     304500
           90      58000
          100      51600
          110      20300
                    7000
已选择12行。
请注意,这里我们用到了distinct 关键字,如果不用distinct,第2个查询将返回107行数据,即employees表的每行记录都将返回一行sum_salary,因为不用distinct的含义是:针对每个雇员计算他/她所在的部门的薪金总数。
在这个例子中,聚合函数是更好的选择,但在另外一些情形下,我们更应该使用分析函数。
下面通过几个实例来介绍部分分析函数的用途。
问题1:求出每个省工业企业利润总额最多的前10名。
利用我们传统的聚合函数max可以方便地取出利润总额最多的一家,但是取出多家就无能为力了,同样,如果不分组我们可以通过排序取出任何一个省利润总额最多的前10名,但无法实现对多个省的分组。而采用rank聚合函数,可以方便地实现我们的要求。
完整的语句如下:
select * from
(select  substr(z01_04,1,2) 地区码,
DENSE_RANK() OVER (PARTITION BY substr(z01_04,1,2) order by b04_50 desc) 名次, b04_50 "利润总额"
from cj604,cj601 where b04_50>0 and cj601.uuid=cj604.uuid  ) where 名次<=10;
我们在开窗函数中使用地区码作为分组标志,并按照利润总额倒序排列。
结果如下(数据为模拟数据,以下同)
地区       名次   利润总额
---- ---------- ----------
31            1     963799
31            2     229643
...
31            9     135917
31           10     125245
32            1     349940
32            2     300587
...
注意:RANK()函数有3组,分别是rank, dense_rank, row_number,它们的区别是:
rank如果出现两个相同的数据,那么后面的数据就会直接跳过这个排名,比如:当第2名和第3名的利润相同时,rank的结果是1,2,2,4;而dense_rank则不会跳过这个排名,结果是1,2,2,3;而row_number哪怕是两个数据完全相同,排名也会不一样,结果是1,2,3,4
问题2:求出按登记注册类型分组的职工人数和销售额占总体的比重
分析函数ratio_to_report专门用来解决个体占总体的比重一类的问题。
语句
select d.*,round((ratio_to_report(职工人数) over())*100,1) as 人数百分比,
round((ratio_to_report(销售额) over())*100,1) as 销售额百分比
from
(select c.code 代码 , substr(b.reg_type,1,10) 登记注册类型, 职工人数, 销售额 from
(select substr(z01_08,1,1)||'00' code, sum(z01_171_01) 职工人数,sum(b03_01) 销售额
from cj603 c,cj601
j where c.uuid=j.uuid group by  substr(z01_08,1,1)
)c, djzclx b where c.code=b.reg_code
)d;
可以得出下面的结果:
代码 登记注册类型           职工人数     销售额 人数百分比 销售额百分比
---- -------------------- ---------- ---------- ---------- ------------
100  内资企业                8510509 3002627283         63      56.2
200  港、澳、台商投资企业    2066175  746728306       15.3        14
300  外商投资企业            2936984 1597046896       21.7      29.9
其中内层的子查询语句
select substr(z01_08,1,1)||'00' code, sum(z01_171_01) 职工人数,sum(b03_01) 销售额
from cj603 c,cj601 j where c.uuid=j.uuid group by  substr(z01_08,1,1)
获得如下的结果
CODE   职工人数     销售额
---- ---------- ----------
100     8510509 3002627283
200     2066175  746728306
300     2936984 1597046896
外层查询中ratio_to_report函数自动对结果集中的职工人数和销售额计算比重。
问题3 求按行业中类划分的大中小型企业个数
case语句不是分析函数,但它在统计汇总中的作用非常重要,可以用来设定复杂的分组条件。
以下是统计上工业大中小型企业划分标准。
指标名称
计量单位
大型
中型
小型
从业人员数

2000及以上且
300-2000以下
300以下或
销售收入
万元
30000及以上且
3000-30000以下
3000以下或
资产合计
万元
40000及以上
4000-40000以下
4000以下

请注意下面这个说明:大型和中型企业须同时满足所列各项条件的下限指标,否则下划一档。
比如某企业虽然从业人员数和销售收入符合大型企业的要求,但资产合计30000万元,不满足大型企业的要求,只能划归中型企业。实际上,中型企业单位数=企业单位总数-大型企业单位数-小型企业单位数。
因此,用b04_71<2000 and b04_71>=300 and b04_29>=30000 and b04_29<300000 and b04_16>=40000 and b04_16<400000的写法来表述中型标准是错误的。
正确写法应该是:
not(b04_71>=2000 and b04_29>=300000 and b04_16>=400000) and not(b04_71<300 or b04_29<30000 or b04_16<40000),当然前提是这3个字段没有空值null。
完整的SQL语句如下:
select code 代码 , substr(INDUSTRY_NAME,1,10) 行业名称, c.*  from
(
select substr(z01_064,1,2) as code ,count(*) as TOL,
count(case when b04_71>=2000 and b04_29>=300000 and b04_16>=400000 then 1 else null end) as big,
count(case when not(b04_71>=2000 and b04_29>=300000 and b04_16>=400000)
and not(b04_71<300 or b04_29<30000 or b04_16<40000) then 1 else null end) as mid,
count(case when b04_71<300 or b04_29<30000 or b04_16<40000 then 1 else null end) as small
from cj604 a,cj601 b where a.uuid=b.uuid group by substr(z01_064,1,2)
)c, industry b where c.code=b.INDUSTRY_CODE
输出结果如下:
代码 行业名称           CODE        TOL        BIG        MID      SMALL                                                                                               
---- ------------------ ---- ---------- ---------- ---------- ----------                                                                                               
06   煤炭开采和洗选业   06            9          1          2          6                                                                                               
07   石油和天然气开采业 07            3          1          0          2                                                                                               
08   黑色金属矿采选业   08           13          1          3          8                                                                                               
13   农副食品加工业     13         1342          2         48       1269                                                                                               
14   食品制造业         14          784          3         66        691                                                                                               
15   饮料制造业         15          385          0         31        331                                                                                               
...
问题4 求按地区划分的3种登记注册类型的营业利润率
decode函数不是分析函数,但它在统计汇总中的作用非常重要,它的格式是:
decode(字段名或表达式,比较值1,返回值1, [比较值2,返回值2,...] 默认返回值),它的作用是当字段或表达式的值等于比较值1时,就得出返回值1,当字段或表达式的值等于比较值2时,就得出返回值2,以此类推,如果都不符合,就返回默认返回值。其中从比较值2开始的参数对可以不提供。
语句
select substr(name,1,4) 地区名称, c.*
from(select 地区代码,
decode(注册类型码,'1',营业利润率,null) A1,
decode(注册类型码,'2',营业利润率,null) A2,
decode(注册类型码,'3',营业利润率,null) A3
from(
select
substr(z01_04,1,2) 地区代码,substr(z01_08,1,1) 注册类型码,
round(sum(b04_45)/sum(b04_29)*100,2) 营业利润率
from cj601 a,cj604 b where a.uuid=b.uuid
group by substr(z01_04,1,2),substr(z01_08,1,1)
)
)c,dq
where 地区代码=dq.code;
得出如下结果。
地区名称 地区     A1     A2     A3
-------- ---- ------ ------ ------
上海     31     6.74
上海     31            5.30
上海     31                   6.37
江苏     32     3.94
江苏     32            4.85
江苏     32                   4.32
浙江     33     4.55
浙江     33            5.25
浙江     33                   5.76
因为decode函数只针对一行内的数据进行处理,这样的结果并不符合要求,我们需要在第二层查询语句的外面再加一层按地区代码的分组汇总,完整写法如下:
select substr(name,1,4) 地区名称, c.*
from(
select 地区代码,SUM(A1) A1,SUM(A2) A2,SUM(A3) A3 from(
select 地区代码,
decode(注册类型码,'1',营业利润率,null) A1,
decode(注册类型码,'2',营业利润率,null) A2,
decode(注册类型码,'3',营业利润率,null) A3
from(
select
substr(z01_04,1,2) 地区代码,substr(z01_08,1,1) 注册类型码,
round(sum(b04_45)/sum(b04_29)*100,2) 营业利润率
from cj601 a,cj604 b where a.uuid=b.uuid
group by substr(z01_04,1,2),substr(z01_08,1,1)
)
)group by 地区代码 )c,dq
where 地区代码=dq.code;
这样就得到了正确的结果:
地区名称 地区     A1     A2     A3
------- ---- ------ ------ ------
上海     31     6.74   5.30   6.37
江苏     32     3.94   4.85   4.32
浙江     33     4.55   5.25   5.76
同样的问题我们也可以通过lead分析函数来完成。
select substr(name,1,4) 地区名称, 地区代码, A1,A2,A3
from(
select * from(
select 地区代码,
lead(营业利润率,    0) over(partition by 地区代码 order by 注册类型码) A1,
lead(营业利润率,    1) over(partition by 地区代码 order by 注册类型码) A2,
lead(营业利润率,    2) over(partition by 地区代码 order by 注册类型码) A3,
row_number( ) over(partition by 地区代码 order by 注册类型码) rn
from(
select
substr(z01_04,1,2) 地区代码,substr(z01_08,1,1) 注册类型码,
round(sum(b04_45)/sum(b04_29)*100,2) 营业利润率
from cj601 a,cj604 b where a.uuid=b.uuid
group by substr(z01_04,1,2),substr(z01_08,1,1)
))where rn=1
)c,dq
where 地区代码=dq.code;
lead函数的第一个参数是我们关心的值,第2个参数是偏移量n,对本例就是下n种注册类型码。
之所以要限定rn=1,还是因为分析函数对每一行都返回分组值,而我们关心的是注册类型为1的那一行。
利用lag和lead函数,我们可以在同一行中显示前n行的数据,也可以显示后n行的数据。
如果本例改用lag函数实现,代码如下:
注意过滤条件rn=3以及lag函数第2个参数的变化,我们把第3行作为当前行,取出它前面的2行。
select substr(name,1,4) 地区名称, 地区代码, A1,A2,A3
from(
select * from(
select 地区代码,
lag(营业利润率,    2) over(partition by 地区代码 order by 注册类型码) A1,
lag(营业利润率,    1) over(partition by 地区代码 order by 注册类型码) A2,
lag(营业利润率,    0) over(partition by 地区代码 order by 注册类型码) A3,
row_number( ) over(partition by 地区代码 order by 注册类型码) rn
from(
select
substr(z01_04,1,2) 地区代码,substr(z01_08,1,1) 注册类型码,
round(sum(b04_45)/sum(b04_29)*100,2) 营业利润率
from cj601 a,cj604 b where a.uuid=b.uuid
group by substr(z01_04,1,2),substr(z01_08,1,1)
))where rn=3
)c,dq
where 地区代码=dq.code;
这种方法比前一种方法利用sum分组汇总的好处是对字符类型和其他非数值类型字段都可以采用。
问题5 求按登记注册类型多个层次划分的单位个数小计和总计
例如要得出如下的结果:
代码   登记注册类型                            家数         
------ --------------------------------------- ---------
100    内资企业                                    61920
110      国有企业                                   1365
140      联营企业                                    476
141        国有联营企业                               52
...
200    港、澳、台商投资企业                         9004
210      合资经营企业(港或澳、台资)                 4454
220      合作经营企业(港或澳、台资)                  556
300    外商投资企业                                11396
310      中外合资经营企业                           5070
320      中外合作经营企业                            663
我们有3种方法,都可以完成任务。
方法1
select code 代码 , substrb('    ',1,item_level*2-2)||b.reg_type 登记注册类型, cnt 家数 from
(
(select substr(z01_08,1,1)||'00' code ,count(*) cnt
from cj601
group by substr(z01_08,1,1))
union
(select substr(z01_08,1,2)||'0' code ,count(*) cnt
from cj601
group by substr(z01_08,1,2))
union
(select substr(z01_08,1,3) code ,count(*) cnt
from cj601
group by substr(z01_08,1,3))
)
c, djzclx b where c.code=b.reg_code;
方法2
select code 代码 , substrb('    ',1,item_level*2-2)||b.reg_type 登记注册类型, cnt 家数 from
(
select
case when code3 is not null then code3
     when code2<>'0' then code2
else code1
end code,cnt from (
select substr(z01_08,1,1)||'00' code1 , substr(z01_08,1,2)||'0' code2 , substr(z01_08,1,3) code3 ,count(*) cnt
    from cj601
    group by rollup(substr(z01_08,1,1),substr(z01_08,1,2),substr(z01_08,1,3))
) where code2<>code3 or code3 is null and code1<>'00'
)
c, djzclx b where c.code=b.reg_code
order by 1
;
方法3
select code 代码 , substrb('    ',1,item_level*2-2)||b.reg_type 登记注册类型, cnt 家数 from
(
select
case when code3 is not null then code3
     when code2<>'0' then code2
else code1
end code,cnt from (
select substr(z01_08,1,1)||'00' code1 , substr(z01_08,1,2)||'0' code2 , substr(z01_08,1,3) code3 ,sum(cnt) cnt
    from (select substr(z01_08,1,3) z01_08,count(*) cnt from cj601 group by substr(z01_08,1,3))
    group by rollup(substr(z01_08,1,1),substr(z01_08,1,2),substr(z01_08,1,3))
) where code2<>code3 or code3 is null and code1<>'00'
)
c, djzclx b where c.code=b.reg_code
order by 1
;
上述3种写法都能得出正确的结果,但执行效率有巨大差别,第一种写法最简单,但是使用union要对cj601作了3遍全表扫描,执行效率最低,第2种写法对cj601做rollup分组,让数据库自动求小计和总计,第3种写法先对cj601做分组汇总,对结果集再做rollup分组,让数据库求小计和总计,在数据量中等的时候效率差不多,数据量大的时候,方法3效率更好些,因为rollup分组要处理的记录数更少,而rollup分组比普通分组开销大一些。
Oracle提供的分析函数一共有10多个,但有些专门的统计函数比如求标准差,相关系数,协方差等我们一般用不到,主要用到的是本文提到的RANK, lead, ratio_to_report等,我们如果能够将它们和decode函数,case语句配合,善加利用,就能编写出执行效率高的汇总语句,高效完成统计数据处理任务。更加详细的关于分析函数的信息,请参考资料Oracle9i Data Warehousing Guide 第19章SQL for Analysis in Data Warehouses

转载自CSDN博客:http://blog.csdn.net/l1t/archive/2008/12/22/3579760.aspx

利用Oracle内置分析函数进行高效统计汇总的更多相关文章

  1. SQL Server利用RowNumber()内置函数与Over关键字实现通用分页存储过程(支持单表或多表结查集分页)

    SQL Server利用RowNumber()内置函数与Over关键字实现通用分页存储过程,支持单表或多表结查集分页,存储过程如下: /******************/ --Author:梦在旅 ...

  2. oracle 内置函数 least decode

    在博客园的第一个博客,为什么叫第一个.... oracle 内置函数 east(1,2,3,4.....) 可以有多个值,最多几个?不知道欢迎补充 ,,,) from dual 这个函数返回是1,就是 ...

  3. 如何利用.Net内置类,解析未知复杂Json对象

    如何利用.Net内置类,解析未知复杂Json对象 如果你乐意,当然可以使用强大的第三方类库Json.Net中的JObject类解析复杂Json字串 . 我不太希望引入第三方类库,所以在.Net内置类J ...

  4. 【Android】18.1 利用安卓内置的定位服务实现位置跟踪

    分类:C#.Android.VS2015: 创建日期:2016-03-04 一.安卓内置的定位服务简介 通常将各种不同的定位技术称为位置服务或定位服务.这种服务是通过电信运营商的无线电通信网络(如GS ...

  5. python 练习题:请利用Python内置的hex()函数把一个整数转换成十六进制表示的字符串

    # -*- coding: utf-8 -*- # 请利用Python内置的hex()函数把一个整数转换成十六进制表示的字符串 n1 = 255 n2 = 1000 print(hex(n1)) pr ...

  6. 利用Windows内置工具winsat测试硬盘速度(SSD&机械盘对比)

    利用Windows内置工具winsat测试硬盘速度(SSD&机械盘对比) 以下是红色内容是在命令行运行: C:\Users\Administrator>winsat diskWindow ...

  7. SQL入门(2): Oracle内置函数-字符/数值/日期/转换/NVL/分析函数与窗口函数/case_decode

    本文介绍Oracle 的内置函数. 常用!  一. 字符函数 ASCII 码与字符的转化函数 chr(n)   例如 select chr(65) || chr(66) || chr(67) , ch ...

  8. oracle——学习之路(oracle内置函数)

    oracle与很多内置函数,主要分为单行函数与集合函数. 首先要提一下dual表,它oracle的一个表,没有什么实质的东西,不能删除它,否则会造成Oracle无法启动等问题,他有很大用处,可以利用它 ...

  9. 利用Java内置的API开发JMX功能

    一.什么是JMX JMS是一种Java规范,定义了如何管理一个软件系统(或应用程序)的规范. 对于一个简单的应用程序,该程序本身不需要被管理.但如果是开发的一个复杂系统(如一个电商平台.一个企业内部管 ...

随机推荐

  1. UE4 插件扩展引擎工具栏

    UE4 作为游戏引擎,已经提供了非常强大的游戏开发的API.作为游戏制作者来讲,我们需要一些专用的功能辅助我们更好的开发游戏,而不是仅仅从构建游戏逻辑出发.因此也就有了扩展编辑器功能的这个想法,还好 ...

  2. 【07】Ajax status和statusText状态对照表

    Ajax status和statusText状态对照表   XMLHttpRequest 对象的 status 和 statusText 属性保存有服务器返回的 http 状态码,不同的是,statu ...

  3. 九度教程第22题——今年暑假不AC(看尽量多的电视节目)

    #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <algorithm> using namespace ...

  4. SPOJ - QTREE Query on a tree题解

    题目大意: 一棵树,有边权,有两个操作:1.修改一条边的权值:2.询问两点间路径上的边的权值的最大值. 思路: 十分裸的树链剖分+线段树,无非是边权要放到深度大的一端的点上,但是有两个坑爹的地方,改了 ...

  5. 洛谷P2888 [USACO07NOV]牛栏Cow Hurdles

    题目描述 Farmer John wants the cows to prepare for the county jumping competition, so Bessie and the gan ...

  6. Pagodas 等差数列

    nn pagodas were standing erect in Hong Jue Si between the Niushou Mountain and the Yuntai Mountain, ...

  7. 选择器的使用(empty选择器)

    <!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head><meta ...

  8. 条款十: 如果写了operator new就要同时写operator delete

    为什么有必要写自己的operator new和operator delete? 答案通常是:为了效率.缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也 ...

  9. 制作svg动画

    要实现一步一步画出来一个图片,css3做不到吧.除非一张张的图片定时显示.想不到别的招了.如今用的是一个插件,做了一个svg动画. 插件地址:http://lazylinepainter.info/ ...

  10. 【Akka】Actor模型探索

    Akka是什么 Akka就是为了改变编写高容错性和强可扩展性的并发程序而生的.通过使用Actor模型我们提升了抽象级别,为构建正确的可扩展并发应用提供了一个更好的平台.在容错性方面我们採取了" ...