16.3 复杂的中国式报表统计
在现实生活中我们会遇到很多复杂的中国式报表,例如在一个报表中,根据某一列进行统计的前十名后十名,还有根据另外一列统计的前十名后十名,比较离奇的是,不同列的前十名后十名需要放在同一行,整合在同一个表中。又比如存在这样一种动态的情况,比如汇率牌价,利率牌价,股价等,需要统计某一币种某一日的初始价,结束价,最高价,最低价,而且这些数值也是需要放在同一行。这种情况下,虽然BIRT报表本身可以通过网格,内嵌表来实现,或者传递参数实现,但效率会非常差。
这个时候,我们需要使用数据库厂商本身提供的分析统计函数,效率会高许多倍。以Oracle为例,oracle在9i之后提供了分析函数,这些函数的功能强大,完成了包括统计,排序,分组,计算等等众多函数在内。下面是Oracle的分析函数列表:
Oracle分析函数——函数列表
SUM :该函数计算组中表达式的累积和
MIN :在一个组中的数据窗口中查找表达式的最小值
MAX :在一个组中的数据窗口中查找表达式的最大值
AVG :用于计算一个组和数据窗口内表达式的平均值。
COUNT :对一组内发生的事情进行累积计数
-------------------------------------------------------------------------------------------------
RANK :根据ORDER BY子句中表达式的值,从查询返回的每一行,计算它们与其它行的相对位置
DENSE_RANK :根据ORDER BY子句中表达式的值,从查询返回的每一行,计算它们与其它行的相对位置
FIRST :从DENSE_RANK返回的集合中取出排在最前面的一个值的行
LAST :从DENSE_RANK返回的集合中取出排在最后面的一个值的行
FIRST_VALUE :返回组中数据窗口的第一个值
LAST_VALUE :返回组中数据窗口的最后一个值。
LAG :可以访问结果集中的其它行而不用进行自连接
LEAD :LEAD与LAG相反,LEAD可以访问组中当前行之后的行
ROW_NUMBER:返回有序组中一行的偏移量,从而可用于按特定标准排序的行号
-------------------------------------------------------------------------------------------------
STDDEV :计算当前行关于组的标准偏离
STDDEV_POP:该函数计算总体标准偏离,并返回总体变量的平方根
STDDEV_SAMP:该函数计算累积样本标准偏离,并返回总体变量的平方根
VAR_POP :该函数返回非空集合的总体变量(忽略null)
VAR_SAMP :该函数返回非空集合的样本变量(忽略null)
VARIANCE :如果表达式中行数为1,则返回0,如果表达式中行数大于1,则返回VAR_SAMP
COVAR_POP :返回一对表达式的总体协方差
COVAR_SAMP:返回一对表达式的样本协方差
CORR :返回一对表达式的相关系数
-------------------------------------------------------------------------------------------------
CUME_DIST :计算一行在组中的相对位置
NTILE :将一个组分为"表达式"的散列表示
PERCENT_RANK:和CUME_DIST(累积分配)函数类似
PERCENTILE_DISC:返回一个与输入的分布百分比值相对应的数据值
PERCENTILE_CONT:返回一个与输入的分布百分比值相对应的数据值
RATIO_TO_REPORT:该函数计算expression/(sum(expression))的值,它给出相对于总数的百分比
REGR_ (Linear Regression) Functions:这些线性回归函数适合最小二乘法回归线,有9个不同的回归函数可使用
-------------------------------------------------------------------------------------------------
CUBE :按照OLAP的CUBE方式进行数据统计,即各个维度均需统计
oracle分析函数的语法:
function_name(arg1,arg2,...)
over
(<partition-clause> <order-by-clause ><windowing clause>)
说明:
1. partition-clause 数据记录集分组
2. order-by-clause 数据记录集排序
3. windowing clause 功能非常强大、比较复杂,定义分析函数在操作行的集合。有三种开窗方式: range、row、specifying。
--Partition by,按相应的值(manager_id)进行分组统计
SELECT
manager_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
AVG(salary) OVER (PARTITION BY manager_id) avg_salary
FROM employees;
--等同于上面
SELECT
a.manager_id,
a.employee_name,
a.hire_date,
a.salary,
b.avg_salary
FROM
(
SELECT
manager_id,
first_name||' '||last_name employee_name,
hire_date,
salary
FROM employees
) a,
(
SELECT
manager_id,
AVG(salary) avg_salary
FROM employees
GROUP BY manager_id
) b
WHERE a.manager_id=b.manager_id
ORDER BY a.manager_id
--Order by按相应的值(hire_date)进行排序并累计统计
SELECT
manager_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
AVG(salary) OVER (ORDER BY hire_date)
FROM employees;
--Partition by Order by首先按相应的值(manager_id,hire_date)排序,并按order by的值(hire_date)进行累计统计
SELECT
manager_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
AVG(salary) OVER (PARTITION BY manager_id ORDER BY hire_date)
FROM employees;
--Partition by Order by首先按相应的值(manager_id,hire_date)排序,并按order by的值(hire_date)进行累计统计
--该平均值由当前员工和与之具有相同经理的前一个和后两个三者的平均数得来
SELECT
manager_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
AVG(salary) OVER (PARTITION BY manager_id ORDER BY hire_date ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING)
FROM employees;
--Partition by Order by首先按相应的值(manager_id,hire_date)排序,并按order by的值(hire_date)进行累计统计
--该平均值由当前员工和与之具有相同经理,并且雇用时间在该员工时间之前的50天以内和在该员工之后的150天之内员工的薪水的平均值
--range为取值范围,估计只有数字和日期能够进行取值了
SELECT
manager_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
AVG(salary) OVER (PARTITION BY manager_id ORDER BY hire_date RANGE BETWEEN 50 PRECEDING AND 150 FOLLOWING)
FROM employees;
--Partition by Order by首先按相应的值(manager_id,hire_date)排序,并按order by的值(hire_date)进行累计统计
--该平均值由当前员工和与之具有相同经理的平均值
--每行对应的数据窗口是从第一行到最后一行
SELECT
manager_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
AVG(salary) OVER (PARTITION BY manager_id ORDER BY hire_date) avg_salary_part_order,
AVG(salary) OVER (PARTITION BY manager_id ) avg_salary_order,
AVG(salary) OVER (PARTITION BY manager_id ORDER BY hire_date RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) avg_salary_unbound1, --等同于仅partition时候的值
AVG(salary) OVER (PARTITION BY manager_id ORDER BY hire_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) avg_salary_unbound2--等同于上面
FROM employees;
SUM
功能描述:该函数计算组中表达式的累积和。
SAMPLE:下例计算同一经理下员工的薪水累积值
MIN
功能描述:在一个组中的数据窗口中查找表达式的最小值。
SAMPLE:下面例子中dept_min返回当前行所在部门的最小薪水值
MAX
功能描述:在一个组中的数据窗口中查找表达式的最大值。
SAMPLE:下面例子中dept_max返回当前行所在部门的最大薪水值
AVG
功能描述:用于计算一个组和数据窗口内表达式的平均值。
SAMPLE:下面的例子中列c_mavg计算员工表中每个员工的平均薪水报告
SELECT
department_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
MIN(salary) OVER (PARTITION BY department_id order by hire_date) AS dept_min,
MAX(salary) OVER (PARTITION BY department_id order by hire_date) AS dept_max,
AVG(salary) OVER (PARTITION BY department_id order by hire_date) AS dept_avg,
SUM(salary) OVER (PARTITION BY department_id order by hire_date) AS dept_sum/*,
COUNT(*) OVER (ORDER BY salary) AS count_by_salary,
COUNT(*) OVER (ORDER BY salary RANGE BETWEEN 50 PRECEDING AND 150 FOLLOWING) AS count_by_salary_range*/
FROM employees
COUNT
功能描述:对一组内发生的事情进行累积计数,如果指定*或一些非空常数,count将对所有行计数,如果指定一个表达式,count返回表达式非空赋值的计数,当有相同值出现时,这些相等的值都会被纳入被计算的值;可以使用DISTINCT来记录去掉一组中完全相同的数据后出现的行数。
SAMPLE:下面例子中计算每个员工在按薪水排序中当前行附近薪水在[n-50,n+150]之间的行数,n表示当前行的薪水
例如,Philtanker的薪水2200,排在他之前的行中薪水大于等于2200-50的有1行,排在他之后的行中薪水小于等于2200+150的行没有,所以count计数值cnt3为2(包括自己当前行);cnt2值相当于小于等于当前行的SALARY值的所有行数
SELECT
department_id,
first_name||' '||last_name employee_name,
salary,
COUNT(*) OVER (ORDER BY salary) AS count_by_salary,
COUNT(*) OVER (ORDER BY salary RANGE BETWEEN 50 PRECEDING AND 150 FOLLOWING) AS count_by_salary_range
FROM employees
WHERE department_id in (10,20,30);
Oracle分析函数——函数RANK,DENSE_RANK,FIRST,LAST…
RANK
功能描述:根据ORDER BY子句中表达式的值,从查询返回的每一行,计算它们与其它行的相对位置。组内的数据按ORDER BY子句排序,然后给每一行赋一个号,从而形成一个序列,该序列从1开始,往后累加。每次ORDER BY表达式的值发生变化时,该序列也随之增加。有同样值的行得到同样的数字序号(认为null时相等的)。然而,如果两行的确得到同样的排序,则序数将随后跳跃。若两行序数为1,则没有序数2,序列将给组中的下一行分配值3,DENSE_RANK则没有任何跳跃。
SAMPLE:下例中计算每个员工按部门分区再按薪水排序,依次出现的序列号(注意与DENSE_RANK函数的区别)
DENSE_RANK
功能描述:根据ORDER BY子句中表达式的值,从查询返回的每一行,计算它们与其它行的相对位置。组内的数据按ORDER BY子句排序,然后给每一行赋一个号,从而形成一个序列,该序列从1开始,往后累加。每次ORDER BY表达式的值发生变化时,该序列也随之增加。有同样值的行得到同样的数字序号(认为null时相等的)。密集的序列返回的时没有间隔的数
SAMPLE:下例中计算每个员工按部门分区再按薪水排序,依次出现的序列号(注意与RANK函数的区别)
SELECT
department_id,
first_name||' '||last_name employee_name,
salary,
RANK() OVER (ORDER BY salary) AS RANK_ORDER,
DENSE_RANK() OVER (ORDER BY salary) AS DENSE_RANK_ORDER
FROM employees
SELECT
department_id,
first_name||' '||last_name employee_name,
salary,
RANK() OVER (PARTITION BY department_id ORDER BY salary) AS RANK_PART_ORDER,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary) AS DENSE_RANK_PART_ORDER
FROM employees
FIRST
功能描述:从DENSE_RANK返回的集合中取出排在最前面的一个值的行(可能多行,因为值可能相等),因此完整的语法需要在开始处加上一个集合函数以从中取出记录
SAMPLE:下面例子中DENSE_RANK按部门分区,再按佣金commission_pct排序,FIRST取出佣金最低的对应的所有行,然后前面的MAX函数从这个集合中取出薪水最低的值;LAST取出佣金最高的对应的所有行,然后前面的MIN函数从这个集合中取出薪水最高的值
LAST
功能描述:从DENSE_RANK返回的集合中取出排在最后面的一个值的行(可能多行,因为值可能相等),因此完整的语法需要在开始处加上一个集合函数以从中取出记录
SAMPLE:下面例子中DENSE_RANK按雇用日期排序,FIRST取出salary最低的对应的所有行,然后前面的MAX函数从这个集合中取出薪水最低的值;LAST取出雇用日期最高的对应的所有行,然后前面的MIN函数从这个集合中取出薪水最高的值
SELECT
department_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY hire_date) OVER (PARTITION BY department_id) "Worst",
MAX(salary) KEEP (DENSE_RANK LAST ORDER BY hire_date) OVER (PARTITION BY department_id) "Best"
FROM employees
看起来last_value和first_value的标准似乎有些不一样,不过单独执行就很清楚了
SELECT
department_id,
first_name||' '||last_name employee_name,
hire_date,
salary,
FIRST_VALUE(first_name||' '||last_name) OVER(PARTITION BY department_id ORDER BY salary ) AS lowest_sal,
FIRST_VALUE(first_name||' '||last_name) OVER(PARTITION BY department_id ORDER BY salary DESC) AS highest_sal,
LAST_VALUE(first_name||' '||last_name) OVER(PARTITION BY department_id ORDER BY salary ) AS last_sal,
LAST_VALUE(first_name||' '||last_name) OVER(PARTITION BY department_id ORDER BY salary DESC) AS last_sal_desc
FROM employees
LAG
功能描述:可以访问结果集中的其它行而不用进行自连接。它允许去处理游标,就好像游标是一个数组一样。在给定组中可参考当前行之前的行,这样就可以从组中与当前行一起选择以前的行。Offset是一个正整数,其默认值为1,若索引超出窗口的范围,就返回默认值(默认返回的是组中第一行),其相反的函数是LEAD
SAMPLE:下面的例子中列prev_sal返回按hire_date排序的前1行的salary值
LEAD
功能描述:LEAD与LAG相反,LEAD可以访问组中当前行之后的行。Offset是一个正整数,其默认值为1,若索引超出窗口的范围,就返回默认值(默认返回的是组中第一行)
SAMPLE:下面的例子中列prev_sal返回按hire_date排序的后1行的salary值
SELECT
first_name||' '||last_name employee_name,
hire_date,
salary,
LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_sal,
LEAD(salary, 1,0) OVER (ORDER BY hire_date) AS "next_sal"
FROM employees
ROW_NUMBER
功能描述:返回有序组中一行的偏移量,从而可用于按特定标准排序的行号。
SAMPLE:下例返回每个员工再在每个部门中按员工号排序后的顺序号
SELECT
department_id,
first_name||' '||last_name employee_name,
employee_id,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY employee_id) AS emp_id
FROM employees
至于后面的数学统计函数,本节不举例子详解了,用法请查看官方文档。
回到文章开头提到的问题,我们需要查询汇率表中某一天的某个币种的初始买入价,结束买入价,最高买入价,最低买入价,初始卖出价,结束卖出价,最高卖出价,最低卖出价,方法如下: