刚开始接触分析函数,总有些头绪梳理不清楚,下面是引用了itpub上nyfor的一些。
问题的提出:在使用first_value()或last_value()的时候,同时使用partition和order by会得不到想要的效果。
SQL> select deptno,empno,ename,sal,
2 last_value(sal) over( partition by deptno )
3 from emp;
DEPTNO EMPNO ENAME SAL LAST_VALUE(SAL)OVER(PARTITIONB
------ ----- ---------- --------- ------------------------------
10 7782 CLARK 2450.00 1300
10 7839 KING 5000.00 1300
10 7934 MILLER 1300.00 1300
20 7369 SMITH 800.00 2975
20 7876 ADAMS 1100.00 2975
20 7902 FORD 3000.00 2975
20 7788 SCOTT 3000.00 2975
20 7566 JONES 2975.00 2975
30 7499 ALLEN 1600.00 1250
30 7698 BLAKE 2850.00 1250
30 7654 MARTIN 1250.00 1250
30 7900 JAMES 950.00 1250
30 7844 TURNER 1500.00 1250
30 7521 WARD 1250.00 1250
这样没有问题,可是如果将sal列按desc排序,结果如下
SQL> select deptno,empno,ename,sal,
2 last_value(sal) over ( partition by deptno order by sal desc )
3 from emp;
DEPTNO EMPNO ENAME SAL LAST_VALUE(SAL)OVER(PARTITIONB
------ ----- ---------- --------- ------------------------------
10 7839 KING 5000.00 5000
10 7782 CLARK 2450.00 2450
10 7934 MILLER 1300.00 1300
20 7788 SCOTT 3000.00 3000
20 7902 FORD 3000.00 3000
20 7566 JONES 2975.00 2975
20 7876 ADAMS 1100.00 1100
20 7369 SMITH 800.00 800
30 7698 BLAKE 2850.00 2850
30 7499 ALLEN 1600.00 1600
30 7844 TURNER 1500.00 1500
30 7521 WARD 1250.00 1250
30 7654 MARTIN 1250.00 1250
30 7900 JAMES 950.00 950
为什么会出现这样的效果呢?
分析函数分析的时候是在一个分析窗口中进行的,默认的窗口是当前组的第一行到当前行,
如果你要让窗口是整个组,那么需要指定窗口子句,以下代码指定了整个分组作为分析窗口。
这样就清楚了,因为我们没有指定分析窗口,那么就采用了默认的窗口(第一行到当前行),
通过上面的数据,大家可以看出第一条的5000,那么当前行是5000,则取5000,到第二条的时候,
上面的是5000,当前行是2450,那么就取2450(因为是 last_value())。这样一直分析到最后。
下面是指定了分析窗口大小的例子
SQL> select deptno,empno,ename,
2 sal,
3 last_value(sal) over( partition by deptno
4 order by sal
5 rows between unbounded preceding and unbounded following
6 ) max_sal
7 from emp;
DEPTNO EMPNO ENAME SAL MAX_SAL
------ ----- ---------- --------- ----------
10 7934 MILLER 1300.00 5000
10 7782 CLARK 2450.00 5000
10 7839 KING 5000.00 5000
20 7369 SMITH 800.00 3000
20 7876 ADAMS 1100.00 3000
20 7566 JONES 2975.00 3000
20 7788 SCOTT 3000.00 3000
20 7902 FORD 3000.00 3000
30 7900 JAMES 950.00 2850
30 7521 WARD 1250.00 2850
30 7654 MARTIN 1250.00 2850
30 7844 TURNER 1500.00 2850
30 7499 ALLEN 1600.00 2850
30 7698 BLAKE 2850.00 2850
14 rows selected
这样就正确了,下面是nyfor的总结,我在这里引用了,必备我以后查找!
在这里先谢过了。呵呵
对于分析函数,大家要注意:
1. 分析函数是在整个SQL查询结束后(SQL语句中的ORDER BY的执行比较特殊)再进行的操作, 也就是说
SQL语句中的ORDER BY也会影响分析函数的执行结果,请看:
SQL> select deptno,
2 empno,
3 ename,
4 sal,
5 hiredate,
6 last_value(sal) over(partition by deptno) last_value
7 from emp
8 where deptno = 30;
DEPTNO EMPNO ENAME SAL HIREDATE last_value
------ ----- ---------- --------- ----------- -----------
30 7499 ALLEN 1600.00 1981-02-20 950
30 7521 WARD 1250.00 1981-02-22 950
30 7654 MARTIN 1250.00 1981-09-28 950
30 7698 BLAKE 2850.00 1981-05-01 950
30 7844 TURNER 1500.00 1981-09-08 950
30 7900 JAMES 950.00 1981-12-03 950
6 rows selected
SQL> select deptno,
2 empno,
3 ename,
4 sal,
5 hiredate,
6 last_value(sal) over(partition by deptno) last_value
7 from emp
8 where deptno = 30
9 order by deptno,mgr;
DEPTNO EMPNO ENAME SAL HIREDATE last_value
------ ----- ---------- --------- ----------- -----------
30 7499 ALLEN 1600.00 1981-02-20 2850
30 7521 WARD 1250.00 1981-02-22 2850
30 7654 MARTIN 1250.00 1981-09-28 2850
30 7900 JAMES 950.00 1981-12-03 2850
30 7844 TURNER 1500.00 1981-09-08 2850
30 7698 BLAKE 2850.00 1981-05-01 2850
6 rows selected
SQL> select deptno,
2 empno,
3 ename,
4 sal,
5 hiredate,
6 last_value(sal) over(partition by deptno) last_value
7 from emp
8 where deptno = 30
9 order by deptno,mgr desc;
DEPTNO EMPNO ENAME SAL HIREDATE last_value
------ ----- ---------- --------- ----------- -----------
30 7698 BLAKE 2850.00 1981-05-01 1250
30 7499 ALLEN 1600.00 1981-02-20 1250
30 7521 WARD 1250.00 1981-02-22 1250
30 7900 JAMES 950.00 1981-12-03 1250
30 7844 TURNER 1500.00 1981-09-08 1250
30 7654 MARTIN 1250.00 1981-09-28 1250
6 rows selected
从上面的结果我们分析得出:
a) 如果SQL语句中的Order By满足分析函数分析时要求的排序,那么SQL语句中的排序将先执行,分析
函数分析时就不必再排序
b) 如果SQL语句中的Order By不满足分析函数分析时要求的排序,那么SQL语句中的排序将最后在分
析函数分析结束后执行排序
2. 分析函数中包含三个分析子句:分组(Partition By), 排序(Order By), 窗口(Window)
窗口就是分析函数分析时要处理的数据范围,就拿SUM来说, 它是SUM窗口中的记录而不是整个分组中
的记录,因此我们在想得到某个栏位的累计值时, 我们需要把窗口指定到该分组中的第一行数据到当
前行, 如果你指定该窗口从该分组中的第一行到最后一行,那么该组中的每一个SUM值都会一样,即整
个组的总和.
窗口子句在这里我只说rows方式的窗口,range方式的这里不提, 此外滑动窗口也不提. 窗口子句中
我们经常用到指定第一行,当前行,最后一行这样的三个属性. 第一行是 unbounded preceding, 当
前行是 current row, 最后一行是 unbounded following.
出现窗口子句,必须指定Order By子句, 如:
last_value(sal) over
(partition by deptno order by sal rows between unbounded preceding and unbounded following)
以上示例指定窗口为整个分组.
当省略窗口子句时:
a) 如果存在Order By则默认的窗口是unbounded preceding and current row
b) 如果同时省略Order By则默认的窗口是unbounded preceding and unbounded following
如果省略分组,则把全部记录当成一个组:
a) 如果存在Order By则默认窗口是unbounded preceding and current row
b) 如果这时省略Order By则窗口默认为unbounded preceding and unbounded following
一篇经典的帖子就是nyfor利用first_value来实现的,原文出处(http://www.itpub.net/703576,1.html)
/*
请教如何由父子关系得到祖先的关系?
一个很棘手的问题,
有一张表是描述父子关系的,例如:
id, parentid 数据有
--------------------------------
a, b
b, c
c, d
现在需求是由这个父子关系得到祖先的层次关系,即把这个内容的父亲,爷爷,…… 都要找出来.
也就是上面的数据必须得到如下的数据:
id, ancestorid
-----------------------
a, b
a, c
b, c
a, d
b, d
c, d
请大家帮帮忙,解决一下这个有挑战的问题!*/
SQL> create table t(id varchar2(1),parentid varchar2(1));
Table created.
SQL> insert into t values('a','b');
1 row created.
SQL> insert into t values('b','c');
1 row created.
SQL> insert into t values('c','d');
1 row created.
SQL> select first_value(id) over (partition by rownum - level order by level) id, parentid
2 from t
3 connect by prior parentid = id;
ID PA
-- --
a b
a c
a d
b c
b d
c d
6 rows selected.
SQL> drop table t;
Table dropped.
下面来看看nyfor的思路
SQL> create table t(id varchar2(1),parentid varchar2(1));
Table created
SQL> insert into t values('a','b');
1 row inserted
SQL> insert into t values('b','c');
1 row inserted
SQL> insert into t values('c','d');
1 row inserted
SQL> select rownum,level,id,parentid from t
2 connect by prior parentid = id;
ROWNUM LEVEL ID PARENTID
---------- ---------- -- --------
1 1 a b
2 2 b c
3 3 c d
4 1 b c
5 2 c d
6 1 c d
6 rows selected
SQL> select rownum - level,level, id, parentid from t
2 connect by prior parentid = id;
ROWNUM-LEVEL LEVEL ID PARENTID
------------ ---------- -- --------
0 1 a b
0 2 b c
0 3 c d
3 1 b c
3 2 c d
5 1 c d
6 rows selected
SQL> select first_value(id) over(partition by rownum - level order by level) id, parentid
2 from t
3 connect by prior parentid = id;
ID PARENTID
-- --------
a b
a c
a d
b c
b d
c d
6 rows selected
SQL> drop table t;
Table dropped