纸飞机

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  22 随笔 :: 28 文章 :: 30 评论 :: 0 Trackbacks

问题:计算某个数字列的累乘积。其操作方式与“计算累计和”相似,只是使用乘法而不是加法。

解决方案

作为例子,本解决方案中都计算职员工资的累乘积。虽然工资的累乘积没有多大用处,然而可以很容易地把该技巧用于其他更有用的领域。

DB2和Oracle

使用窗口函数SUM OVER,用对数相加来模拟乘法操作:

1 select empno,ename,sal,

2         exp(sum(ln(sal))over(order by sal,empno)) as running_prod

3    from emp

4   where deptno = 10

EMPNO ENAME        SAL          RUNNING_PROD

----- ---------- ---- --------------------

7934 MILLER      1300                  1300

7782 CLARK       2450               3185000

7839 KING        5000           15925000000

在SQL中,对小于等于0的值取对数是无效的。如果表中包含这样的值,一定要避免把这些无效的值传递给SQL的LN函数。为了增加可读性,该解决方案并没有对无效值和NULL值采取防范措施,但自己编写代码时,一定要考虑是否需要这种预防。如果一定要用到负值和0值,那么这种解决方案不合适。

Oracle独有的另一种解决方案是使用Oracle Database 10g新引入的MODEL子句。在下面的例子中,每个SAL都是负数,这表明累乘积允许出现负值:

1 select empno, ename, sal, tmp as running_prod

2    from (

3 select empno,ename,-sal as sal

4    from emp

5   where deptno=10

6         )

7   model

8     dimension by(row_number()over(order by sal desc) rn )

9     measures(sal, 0 tmp, empno, ename)

10    rules (

11      tmp[any] = case when sal[cv()-1] is null then sal[cv()]

12                      else tmp[cv()-1]*sal[cv()]

13                 end

14    )

EMPNO ENAME        SAL          RUNNING_PROD

----- ---------- ---- --------------------

7934 MILLER      -1300                -1300

7782 CLARK       -2450              3185000

7839 KING        -5000         -15925000000

MySQL、PostgreSQL和SQL Server

还可以使用对数相加的方法,但这些平台并不支持窗口函数,因此用标量子查询取而代之:

1 select e.empno,e.ename,e.sal,

2         (select exp(sum(ln(d.sal)))

3            from emp d

4           where d.empno <= e.empno

5             and e.deptno=d.deptno) as running_prod

6   from emp e

7   where e.deptno=10

EMPNO ENAME        SAL          RUNNING_PROD

----- ---------- ---- --------------------

7782 CLARK       2450                  2450

7839 KING        5000              12250000

7934 MILLER      1300           15925000000

SQL Server用户使用LOG代替LN。

讨论

除了MODEL子句方案(仅对Oracle Database 10g或更高版本可用)之外,所有解决方案都利用了乘法运算的特性,按下列步骤用加法进行计算:

1.     计算各自的自然对数

2.     计算这些对数的和

3.     对结果进行数学常量e的幂运算(使用EXP函数)

当采用这种方法时,需要注意,对于0值和负值,这种方法不可行,因为任何小于等于0的值都超出了SQL对数的定义域。

DB2和Oracle

有关窗口函数SUM OVER的功能,请参阅“生成累计和”一节。

对于Oracle Database 10g或更高版本,可以使用MODEL子句生成累乘积。同时使用MODEL子句及窗口函数ROW_NUMBER,很容易就能访问前面的行。可以像访问数组一样访问MEASURES列表中的每一项。然后,可以使用DIMENSIONS列表中的项(由ROW_NUMBER返回的值,别名RN)搜索该数组:

select empno, ename, sal, tmp as running_prod,rn

   from (

select empno,ename,-sal as sal

   from emp

where deptno=10

        )

model

    dimension by(row_number()over(order by sal desc) rn )

    measures(sal, 0 tmp, empno, ename)

   rules ()

EMPNO ENAME              SAL RUNNING_PROD          RN

----- ---------- ---------- ------------ ----------

7934 MILLER           -1300             0           1

7782 CLARK            -2450             0           2

7839 KING             -5000             0           3

观察一下,会发现SAL[1]的值为-1300。由于数字逐一连续递增、没有间隙,所以可以通过减1来引用前一行。RULES子句如下:

rules (

    tmp[any] = case when   sal[cv()-1] is null then sal[cv()]

                    else tmp[cv()-1]*sal[cv()]

               end

)

它使用内置操作符ANY处理每一行,而并未进行硬编码。这个例子中ANY的值分别为1、2和3。把TMP[n]初始化为0。通过计算相应SAL行的当前值(函数CV返回当前值),可以给TMP[n]指定一个值。把TMP[1]初始化为0,把SAL[1]初始化为-1300。SAL[0]没有值,所以把TMP[1]设置为SAL[1]。在设置了TMP[1]之后,下一行就是TMP[2]。计算第一个SAL[1](由于ANY的当前值是2,因此SAL[CV()-1]的值是SAL[1])。SAL[1]不为空,而且等于-1300,因此把TMP[2]设置为TMP[1]和SAL[2]的乘积。所有行都进行上述操作。

MySQL、PostgreSQL和SQL Server

有关MySQL、PostgreSQL和SQL Server解决方案所采用的子查询方法的说明,请参阅本章第7.6节。

要注意,基于子查询解决方案的输出与Oracle和DB2解决方案的输出有少许差别,其原因来自EMPNO比较(它们按不同的顺序计算累乘积)。与累计和一样,其总数也是由标量子查询的谓词驱动的;在该解决方案中,行是按EMPNO排序的,而对于Oracle/DB2 解决方案,行是按SAL排序的。

posted on 2008-05-14 21:40 纸飞机 阅读(1409) 评论(0)  编辑  收藏 所属分类: Database

只有注册用户登录后才能发表评论。


网站导航: