??xml version="1.0" encoding="utf-8" standalone="yes"?> create view V (id,amt,trx) as select 1, 100, 'PR' from t1 union all select 2, 100, 'PR' from t1 union all select 3, 50, 'PY' from t1 union all select 4, 100, 'PR' from t1 union all select 5, 200, 'PY' from t1 union all select 6, 50, 'PY' from t1 select * from V ID AMT TR -- ---------- -- 1 100 PR 2 100 PR 3 50 PY 4 100 PR 5 200 PY 6 50 PY ID列唯一标识每次事务处理。AMT列表C每ơ事务处理(取款或存ƾ)涉及的金额。TRX列定义了事务处理的类型;取款?#8220;PY”Q存ƾ是“PR”。如果TRX值是PYQ则惌从篏计和中减去AMTg表的金额Q如果TRX值是PRQ则惌l篏计和加上AMTg表的金额。最后应该返回如下结果集Q?/p>
TRX_TYPE AMT BALANCE -------- ---------- ---------- PURCHASE 100 100 PURCHASE 100 200 PAYMENT 50 150 PURCHASE 100 250 PAYMENT 200 50 PAYMENT 50 0 解决Ҏ DB2和Oracle 使用H口函数SUM OVER创徏累计和,q用CASE表达式判断事务处理的cdQ?/p>
1 select case when trx = 'PY' 2 then 'PAYMENT' 3 else 'PURCHASE' 4 end trx_type, 5 amt, 6 sum( 7 case when trx = 'PY' 8 then -amt else amt 9 end 10 ) over (order by id,amt) as balance 11 from V MySQL、PostgreSQL和SQL Server 使用标量子查询创建篏计和Qƈ使用CASE表达式判断事务处理的cdQ?/p>
1 select case when v1.trx = 'PY' 2 then 'PAYMENT' 3 else 'PURCHASE' 4 end as trx_type, 5 v1.amt, 6 (select sum( 7 case when v2.trx = 'PY' 8 then -v2.amt else v2.amt 9 end 10 ) 11 from V v2 12 where v2.id <= v1.id) as balance 13 from V v1 讨论 CASE表达式判断是该给累计和加上当前的AMTD是从中减d前的AMT?。如果事务处理是取款Q则把AMT更改|q样减了累计和。CASE表达式的l果如下所C: select case when trx = 'PY' then 'PAYMENT' else 'PURCHASE' end trx_type, case when trx = 'PY' then -amt else amt end as amt from V TRX_TYPE AMT -------- --------- PURCHASE 100 PURCHASE 100 PAYMENT -50 PURCHASE 100 PAYMENT -200 PAYMENT -50 在确定了事务处理cd之后Q就可以从篏计和中加上或者减去AMT倹{有关窗口函数SUM OVER或标量子查询如何创徏累计和的说明Q请参阅“计算累计?#8221;?/p>
解决Ҏ DB2 使用函数TRANSLATE和REPLACEQ从字母数字串中提取数字字符Q?/p>
1 select cast( 2 replace( 3 translate( 'paul123f321', 4 repeat('#',26), 5 'abcdefghijklmnopqrstuvwxyz'),'#','') 6 as integer ) as num 7 from t1 Oracle和PostgreSQL 使用函数TRANSLATE和REPLACEQ可以从包含字母数字的字W串中提取数字字W: 1 select cast( 2 replace( 3 translate( 'paul123f321', 4 'abcdefghijklmnopqrstuvwxyz', 5 rpad('#',26,'#')),'#','') 6 as integer ) as num 7 from t1 MySQL和SQL Server 到本书编写时为止Q这两个供应商都不支持TRANSLATE函数Q因此这里不能给x案了?/p>
讨论 两种解决Ҏ的唯一差别是语法,DB2使用函数REPEAT代替RPADQ而且TRANSLATE参数列表的顺序也不同。以下的解释采用了Oracle/PostgreSQL解决ҎQ?DB2也类伹{如果从里向外运行该查询Q仅仅从TRANSLATE开始)Q就会发现这非常单。首先,TRANSLATE把非数字字符转换?#8220;#”Q?/p>
select translate( 'paul123f321', 'abcdefghijklmnopqrstuvwxyz', rpad('#',26,'#')) as num from t1 NUM ----------- ####123#321 ׃现在所有非数字字符都用“#”表示了,因此只需使用REPLACEL它们Q然后把l果转换为数倹{这个特D的例子其单,因ؓ字符串中只有字母和数字。如果还有其他字W,那么用另一U方法会更容易:不是扑և非数字字WƈL它们Q而是扑և所有数字字W,q去掉不属于q些字符范围的其他字W。下面的例子会有助于理解q种技巧: select replace( translate('paul123f321', replace(translate( 'paul123f321', '0123456789', rpad('#',10,'#')),'#',''), rpad('#',length('paul123f321'),'#')),'#','') as num from t1 NUM ------ 123321 较之原始ҎQ该解决Ҏ看v来有点儿费解Q但如果把它分解开来就Ҏ理解了。观察一下最内层的TRANSLATE调用Q? select translate( 'paul123f321', '0123456789', rpad('#',10,'#')) from t1 TRANSLATE(' ----------- paul###f### 与原来方案不同的是,它没有用“#”字符替换每个非数字字W,而是?#8220;#”字符替换所有数字字W。接下来Q去掉所?#8220;#”Q这P只剩下非数字字符Q?/p>
select replace(translate( 'paul123f321', '0123456789', rpad('#',10,'#')),'#','') from t1 REPLA ----- paulf 下一步,再次调用TRANSLATEQ这ơ用“#”字符替换原始字符串中的所有非数字字符Q前面查询的l果Q: select translate('paul123f321', replace(translate( 'paul123f321', '0123456789', rpad('#',10,'#')),'#',''), rpad('#',length('paul123f321'),'#')) from t1 TRANSLATE(' ----------- ####123#321 到这里停一停,验一下最外层的TRANSLATE调用。RPAD的第二个参数QDB2中REPEAT的第二个参数Q是原始字符串的长度。这样做很方便,因ؓ是没有Q何字W出现的ơ数会比它所在的整个字符串长。现在,?#8220;#”字符替换所有非数字字符Q最后一步,使用REPLACEL所?#8220;#”。至此,仅剩下数字?/p>
解决Ҏ MySQL和PostgreSQL 使用子查询排除最高和最低| 1 select avg(sal) 2 from emp 3 where sal not in ( 4 (select min(sal) from emp), 5 (select max(sal) from emp) 6 ) DB2、Oracle和SQL Server 使用内联视图及窗口函数MAX OVER和MIN OVERQ生成一个结果集Q可以很ҎC中剔除最大和最| 1 select avg(sal) 2 from ( 3 select sal, min(sal)over() min_sal, max(sal)over() max_sal 4 from emp 5 ) x 6 where sal not in (min_sal,max_sal) 讨论 MySQL和PostgreSQL 子查询返回表中的最高工资和最低工资。针对返回的g用NOT INQ就可以从^均g排除最高工资和最低工资。记住,如果存在重复Q多个职员都是最高或最低工资)Q那么他们都会被排除在^均g外。如果只x除一个最高和最低|只需从SUM中减d们,再做除法Q?/p>
select (sum(sal)-min(sal)-max(sal))/(count(*)-2) from emp DB2、Oracle和SQL Server 内联视图X返回所有工资,其中包括最高工资和最低工资: select sal, min(sal)over() min_sal, max(sal)over() max_sal from emp SAL MIN_SAL MAX_SAL --------- --------- --------- 800 800 5000 1600 800 5000 1250 800 5000 2975 800 5000 1250 800 5000 2850 800 5000 2450 800 5000 3000 800 5000 5000 800 5000 1500 800 5000 1100 800 5000 950 800 5000 3000 800 5000 1300 800 5000 从每一行都可以讉K最高工资和最低工资,因此Q要扑և哪些工资是最高工资的?或最低工资的非常单。外层查询会对内联视图Xq回的行作筛选,q样Q所有与MIN_SAL和MAX_SALAN相匹配的行都会从q_g排除掉?/p>
解决Ҏ 使用COALESCE函数把NULL转换?Q这样在q行聚集时可以把它们包括q来Q?/p>
1 select avg(coalesce(comm,0)) as avg_comm 2 from emp 3 where deptno=30 讨论 请务必记住,在用聚集函数时会忽略NULL。不使用COALESCE函数时该解决Ҏ的输出如下: select avg(comm) from emp where deptno=30 AVG(COMM) --------- 550 该查询表明,DEPTNO 30的^均䄦金是550Q快速检查这些行如下Q?/p>
select ename, comm from emp where deptno=30 order by comm desc ENAME COMM ---------- --------- BLAKE JAMES MARTIN 1400 WARD 500 ALLEN 300 TURNER 0 q表明六个职员中只有四个职员挣得佣金。DEPTNO 30中所有䄦金的d?200Q其q_值应该是2200/6Q而不?200/4。如果不用COALESCE函数Q回{的是问?#8220;DEPTNO 30中挣得䄦金的职员的^均䄦金是多少Q?#8221;Q而不?#8220;DEPTNO 30中所有职员的q_佣金是多?”。用聚集时C要相应处理NULL倹{?/p>
解决Ҏ ȝ来说Q在SQL中计占L的百分比跟书面计一P先除后乘。这个例子要计算表EMP中DEPTNO 10工资所占的癑ֈ比。首先,出DEPTNO 10的工资,然后除以表中的工资dQ最后一步,乘以100Q则q回一个表C百分比的倹{?/p>
MySQL和PostgreSQL DEPTNO 10的工资d除以所有工资dQ?/p>
1 select (sum( 2 case when deptno = 10 then sal end)/sum(sal) 3 )*100 as pct 4 from emp DB2、Oracle和SQL Server 使用内联视图及窗口函数SUM OVERQ计出所有工资d以及DEPTNO 10的工资和。然后,在外层查询中q行除法和乘法操作: 1 select distinct (d10/total)*100 as pct 2 from ( 3 select deptno, 4 sum(sal)over() total, 5 sum(sal)over(partition by deptno) d10 6 from emp 7 ) x 8 where deptno=10 讨论 MySQL和PostgreSQL 用CASE语句能够L地得到DEPTNO 10的工资。然后将它们加v来,q以所有工资d。由于聚集时会忽略NULL|所以CASE语句中不必加入ELSE子句。如果想看到切的被除数和除敎ͼ则可以执行不做除法的查询Q?/p>
select sum(case when deptno = 10 then sal end) as d10, sum(sal) from emp D10 SUM(SAL) ---- --------- 8750 29025 依定义SAL的方式不同,在进行除法操作时可能需要做昑ּcd转换。例如,在DB2、SQL Server和PostgreSQL中,如果SAL定义为整敎ͼ则可以把它{换ؓ数Q以便得到正答案,如下所C: select (cast( sum(case when deptno = 10 then sal end) as decimal)/sum(sal) )*100 as pct from emp DB2、Oracle和SQL Server 除传l解x案外Q该Ҏ使用H口函数计算相对于L的百分数。对于DB2和SQL ServerQ如果SAL定义为整数类型,则在除法操作之前Q需要进行类型{换: select distinct cast(d10 as decimal)/total*100 as pct from ( select deptno, sum(sal)over() total, sum(sal)over(partition by deptno) d10 from emp ) x where deptno=10 必须CQ窗口函数在WHERE子句后执行。因此不能把针对DEPTNO的筛选放在内联视图X中。分别考虑一下内联视图X中包含及不包含DEPTNO{选的l果。首先,看一下不包含DEPTNO{选的l果Q?/p>
select deptno, sum(sal)over() total, sum(sal)over(partition by deptno) d10 from emp DEP ------- --------- --------- 10 29025 8750 10 29025 8750 10 29025 8750 20 29025 10875 20 29025 10875 20 29025 10875 20 29025 10875 20 29025 10875 30 29025 9400 30 29025 9400 30 29025 9400 30 29025 9400 30 29025 9400 30 29025 9400 包含DEPTNO{选的l果Q?/p>
select deptno, sum(sal)over() total, sum(sal)over(partition by deptno) d10 from emp where deptno=10 DEPTNO TOTAL D10 ------ --------- --------- 10 8750 8750 10 8750 8750 10 8750 8750 ׃H口函数在WHERE子句后执行,因此TOTAL的g表示DEPTNO 10的工资之和,而实际上需要用TOTAL表示所有工资的d。这是必须把针对DEPTNO的筛选放在内联视图X外面的原因?/p>
select sal from emp where deptno = 20 order by sal SAL ---------- 800 1100 2975 3000 3000 中间Cؓ2975?/p>
解决Ҏ 除了Oracle解决ҎQ用函数计算中间敎ͼ之外Q其他所有解x案都是以Rozenshtein、Abramovich和Birger在Optimizing Transact-SQL: Advanced Programming Techniques (SQL Forum Press, 1997)中描q的Ҏ为基的。与传统的自联接相比Q窗口函数的引入Q解决Ҏ更ؓ有效?/p>
DB2 使用H口函数COUNT(*) OVER和ROW_NUMBERQ查找中间数Q?/p>
1 select avg(sal) 2 from ( 3 select sal, 4 count(*) over() total, 5 cast(count(*) over() as decimal)/2 mid, 6 ceil(cast(count(*) over() as decimal)/2) next, 7 row_number() over (order by sal) rn 8 from emp 9 where deptno = 20 10 ) x 11 where ( mod(total,2) = 0 12 and rn in ( mid, mid+1 ) 13 ) 14 or ( mod(total,2) = 1 15 and rn = next 16 ) MySQL和PostgreSQL 使用自联接查找中间数Q?/p>
1 select avg(sal) 2 from ( 3 select e.sal 4 from emp e, emp d 5 where e.deptno = d.deptno 6 and e.deptno = 20 7 group by e.sal 8 having sum(case when e.sal = d.sal then 1 else 0 end) 9 >= abs(sum(sign(e.sal - d.sal))) 10 ) Oracle 使用函数MEDIANQOracle Database 10gQ或PERCENTILE_CONTQOracle9i DatabaseQ: 1 select median (sal) 2 from emp 3 where deptno=20 1 select percentile_cont(0.5) 2 within group(order by sal) 3 from emp 4 where deptno=20 对于Oracle8i DatabaseQ用DB2解决Ҏ。对于Oracle8i Database之前的版本,可以采用PostgreSQL/MySQL解决Ҏ?/p>
SQL Server 使用H口函数COUNT(*) OVER和ROW_NUMBERQ可得到中间敎ͼ 1 select avg(sal) 2 from ( 3 select sal, 4 count(*)over() total, 5 cast(count(*)over() as decimal)/2 mid, 6 ceiling(cast(count(*)over() as decimal)/2) next, 7 row_number()over(order by sal) rn 8 from emp 9 where deptno = 20 10 ) x 11 where ( total%2 = 0 12 and rn in ( mid, mid+1 ) 13 ) 14 or ( total%2 = 1 15 and rn = next 16 ) 讨论 DB2和SQL Server DB2和SQL Server 解决Ҏ的唯一差别是语法的E许不同QSQL Server?#8220;%”求模Q而DB2使用MOD函数Q其余的都相同。内联视图Xq回三个不同的计数|TOTAL、MID和NEXTQ还用到由ROW_NUMBER生成的RN。这些附加列有助于求解中间数。检验内联视图X的结果集Q就会看到这些列表示的意义: select sal, count(*)over() total, cast(count(*)over() as decimal)/2 mid, ceil(cast(count(*)over() as decimal)/2) next, row_number()over(order by sal) rn from emp where deptno = 20 SAL TOTAL MID NEXT RN ---- ----- ---- ---- ---- 800 5 2.5 3 1 1100 5 2.5 3 2 2975 5 2.5 3 3 3000 5 2.5 3 4 3000 5 2.5 3 5 要得C间数Q一定要把SAL值由低到高排序。由于DEPTNO 20中的职员数是奇数Q因此它的中间数是其RN与NEXT相等的SALQ即大于职员L除以2的最整敎ͼ?/p>
如果l果集返回奇数行QWHERE子句的第一部分Q第11?3行)条g不满뀂如果能够确定结果集是奇数行Q则可以化ؓQ?/p>
select avg(sal) from ( select sal, count(*)over() total, ceil(cast(count(*)over() as decimal)/2) next, row_number()over(order by sal) rn from emp where deptno = 20 ) x where rn = next 令h遗憾的是Q如果结果集包含偶数行,上述化的解决Ҏp不通。在最初的解决Ҏ中,采用MID列中的值处理偶数行。想想DEPTNO 30的内联视图X的结果会怎样Q该部门?名职员: select sal, count(*)over() total, cast(count(*)over() as decimal)/2 mid, ceil(cast(count(*)over() as decimal)/2) next, row_number()over(order by sal) rn from emp where deptno = 30 SAL TOTAL MID NEXT RN ---- ----- ---- ---- ---- 950 6 3 3 1 1250 6 3 3 2 1250 6 3 3 3 1500 6 3 3 4 1600 6 3 3 5 2850 6 3 3 6 ׃q回了偶数行Q则采用下述方式计算中间敎ͼ计算RN分别{于MID和MID + 1两行的^均数?/p>
MySQL和PostgreSQL ҎW一个自联接表EMP计算中间敎ͼ而该表返回了所有工资的W卡儿积QGROUP BY E.SAL会去掉重复|。HAVING子句使用函数SUM计算E.SAL{于D.SAL的次敎ͼ如果q个值大于等于E.SAL且大于D.SALơ数Q那么该行就是中间数。在SELECT列表中加入SUM可以观察到q种情况Q?/p>
select e.sal, sum(case when e.sal=d.sal then 1 else 0 end) as cnt1, abs(sum(sign(e.sal - d.sal))) as cnt2 from emp e, emp d where e.deptno = d.deptno and e.deptno = 20 group by e.sal SAL CNT1 CNT2 ---- ---- ---- 800 1 4 1100 1 2 2975 1 0 3000 4 6 Oracle 在Oracle Database 10g或Oracle9i Database中,可以使用Oracle提供的函数计中间数Q对于Oracle8i DatabaseQ可以采用DB2解决ҎQ其他版本必采用PostgreSQL解决Ҏ。显然可以用MEDIAN函数计算中间|用PERCENTILE_CONT函数也可以计中间值就不那么显而易见了。传递给PERCENTILE_CONT的?.5是一个百分比倹{子句WITHIN GROUP (ORDER BY SAL)定PERCENTILE_CONT要搜索哪些有序行Q记住,中间值就是一l已排序值的中间|。返回的值就是搜索的有序行中W合l定癑ֈ比(在这个例子中?.5Q因为其两个边界值分别ؓ0?Q的倹{?/p>
select sal
from emp
where deptno = 20
order by sal
SAL
----------
800
1100
2975
3000
3000
the mode is 3000.
解决Ҏ
DB2和SQL Server
使用H口函数DENSE_RANKQ把工资重复出现ơ数分等U,以便提取模式Q?/p>
1 select sal
2 from (
3 select sal,
4 dense_rank()over(order by cnt desc) as rnk
5 from (
6 select sal, count(*) as cnt
8 from emp
9 where deptno = 20
10 group by sal
11 ) x
12 ) y
13 where rnk = 1
Oracle
在Oracle8i Database中,可以使用DB2l出的解x案。对于Oracle9i及更高版本,可以用聚集函数MAX的KEEP扩展Q以得到SAL模式。特别要注意的是Q如果存在绑带,也即多个行都是模式,则采用KEEPҎ仅能得到一个,卛_中工资最高的那个。如果想要看所有模式(如果存在多个模式Q,则必M改该ҎQ或者简单地使用前面介绍的DB2解决Ҏ。在q个例子中,׃3000是DEPTNO 20中SAL的模式,而且它也是最高的SALQ因此以下方案就可以了:
1 select max(sal)
2 keep(dense_rank first order by cnt desc) sal
3 from (
4 select sal, count(*) cnt
5 from emp
6 where deptno=20
7 group by sal
8 )
MySQL和PostgreSQL
使用子查询查找模式:
1 select sal
2 from emp
3 where deptno = 20
4 group by sal
5 having count(*) >= all ( select count(*)
6 from emp
7 where deptno = 20
8 group by sal )
讨论
DB2和SQL Server
内联视图X返回每个SAL及它出现的次数。内联视图Y使用H口函数DENSE_RANKQ它允许l带Q给l果排序。结果按每个SAL出现的次数分{Q如下所C:
1 select sal,
2 dense_rank()over(order by cnt desc) as rnk
3 from (
4 select sal,count(*) as cnt
5 from emp
6 where deptno = 20
7 group by sal
8 ) x
SAL RNK
----- ----------
3000 1
800 2
1100 2
2975 2
最外层的查询只单地保留RNK?的行?/p>
Oracle
内联视图返回所有SAL及其出现的次敎ͼ如下所C:
select sal, count(*) cnt
from emp
where deptno=20
group by sal
SAL CNT
----- ----------
800 1
1100 1
2975 1
3000 2
下一步,使用聚集函数MAX的KEEP扩展查找模式。如果仔l分析下面给出的KEEP子句Q会发现它又有三个子句,即DENSE_RANK、FIRST和ORDER BY CNT DESCQ?/p>
keep(dense_rank first order by cnt desc)
q种做法Ҏ模式极其方便。KEEP子句Ҏ内联视图q回的CNT值来定MAXq回SAL的哪个倹{按从右向左的方向将CNT递减排序Q然后保留下按DENSE_RANKơ序q回的所有CNT值的W一个倹{查看一下内联视囄l果集,׃看到3000h最高的CNT?—?2。MAX(SAL) q回的是拥有最高CNT值的最大SALQ在本例中是3000?/p>
有关Oracle中集合函数的KEEP扩展的深入讨论,请参阅第11章第11.11节。有关Oracle中集合函数的KEEP扩展的深入讨论,请参阅第11章第11.11节?/p>
MySQL和PostgreSQL
子查询将q回每个SAL出现的次数。外层查询将q回其的出现ơ数大于{于子查询所q回所有计数值的SALQ换句话_外层查询会返回DEPTNO 20中出现最多的工资Q?/p>
ENAME SAL RUNNING_DIFF
---------- ---------- ------------
MILLER 1300 1300
CLARK 2450 -1150
KING 5000 -6150
解决Ҏ
DB2和Oracle
使用H口函数SUM OVER创徏累计差:
1 select ename,sal,
2 sum(case when rn = 1 then sal else -sal end)
3 over(order by sal,empno) as running_diff<>5 select empno,ename,sal,
6 row_number()over(order by sal,empno) as rn
7 from emp
8 where deptno = 10
9 ) x
MySQL、PostgreSQL和SQL Server
使用标量子查询计篏计差Q?/p>
1 select a.empno, a.ename, a.sal,
2 (select case when a.empno = min(b.empno) then sum(b.sal)
3 else sum(-b.sal)
4 end
5 from emp b
6 where b.empno <= a.empno
7 and b.deptno = a.deptno ) as rnk
8 from emp a
9 where a.deptno = 10
讨论
该解x案与“生成累计?#8221;一节介l的解决Ҏ大致相同。唯一的差别是QSAL除了W一个|因ؓ要从DEPTNO 10的SAL开始)之外Q其余所有值都q回负倹{?/p>
解决Ҏ
作ؓ例子Q本解决Ҏ中都计算职员工资的篏乘积。虽然工资的累乘U没有多大用处,然而可以很Ҏ地把该技巧用于其他更有用的领域?/p>
DB2和Oracle
使用H口函数SUM OVERQ用Ҏ相加来模拟乘法操作:
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中,对小于等?的值取Ҏ是无效的。如果表中包含这L|一定要避免把这些无效的g递给SQL的LN函数。ؓ了增加可L,该解x案ƈ没有Ҏ效值和NULL值采取防范措施,但自q写代码时Q一定要考虑是否需要这U预阌Ӏ如果一定要用到负值和0|那么q种解决Ҏ不合适?/p>
Oracle独有的另一U解x案是使用Oracle Database 10g新引入的MODEL子句。在下面的例子中Q每个SAL都是负数Q这表明累乘U允许出现负|
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
q可以用对数相加的ҎQ但q些q_q不支持H口函数Q因此用标量子查询取而代之:
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?/p>
讨论
除了MODEL子句ҎQ仅对Oracle Database 10g或更高版本可用)之外Q所有解x案都利用了乘法运的Ҏ,按下列步骤用加法q行计算Q?/p>
1. 计算各自的自然对?/p>
2. 计算q些Ҏ的和
3. 对结果进行数学常量e的幂q算Q用EXP函数Q?/p>
当采用这U方法时Q需要注意,对于0值和负|q种Ҏ不可行,因ؓM于{于0的值都出了SQLҎ的定义域?/p>
DB2和Oracle
有关H口函数SUM OVER的功能,请参?#8220;生成累计?#8221;一节?/p>
对于Oracle Database 10g或更高版本,可以使用MODEL子句生成累乘U。同时用MODEL子句及窗口函数ROW_NUMBERQ很Ҏp讉K前面的行。可以像讉K数组一栯问MEASURES列表中的每一V然后,可以使用DIMENSIONS列表中的(由ROW_NUMBERq回的|别名RNQ搜索该数组Q?/p>
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]的gؓQ?300。由于数字逐一q箋递增、没有间隙,所以可以通过?来引用前一行。RULES子句如下Q?/p>
rules (
tmp[any] = case when sal[cv()-1] is null then sal[cv()]
else tmp[cv()-1]*sal[cv()]
end
)
它用内|操作符ANY处理每一行,而ƈ未进行硬~码。这个例子中ANY的值分别ؓ1??。把TMP[n]初始化ؓ0。通过计算相应SAL行的当前|函数CVq回当前|Q可以给TMP[n]指定一个倹{把TMP[1]初始化ؓ0Q把SAL[1]初始化ؓQ?300。SAL[0]没有|所以把TMP[1]讄为SAL[1]。在讄了TMP[1]之后Q下一行就是TMP[2]。计第一个SAL[1]Q由于ANY的当前值是2Q因此SAL[CV()Q?]的值是SAL[1]Q。SAL[1]不ؓI,而且{于Q?300Q因此把TMP[2]讄为TMP[1]和SAL[2]的乘U。所有行都进行上q操作?/p>
MySQL、PostgreSQL和SQL Server
有关MySQL、PostgreSQL和SQL Server解决Ҏ所采用的子查询Ҏ的说明,请参阅本章第7.6节?/p>
要注意,Z子查询解x案的输出与Oracle和DB2解决Ҏ的输出有许差别Q其原因来自EMPNO比较Q它们按不同的顺序计篏乘积Q。与累计和一P其L也是由标量子查询的谓词驱动的Q在该解x案中Q行是按EMPNO排序的,而对于Oracle/DB2 解决ҎQ行是按SAL排序的?/p>
解决Ҏ
下面l出了一U解x案,它展CZ如何计算所有职员工资的累计和。ؓ增加可读性,其结果是按SAL排序的,q样p够很Ҏ地观察到累计和变化的q程?/p>
DB2和Oracle
使用H口版本的SUM函数计算累计和:
1 select ename, sal,
2 sum(sal) over (order by sal,empno) as running_total
3 from emp
4 order by 2
ENAME SAL RUNNING_TOTAL
---------- ---------- -------------
SMITH 800 800
JAMES 950 1750
ADAMS 1100 2850
WARD 1250 4100
MARTIN 1250 5350
MILLER 1300 6650
TURNER 1500 8150
ALLEN 1600 9750
CLARK 2450 12200
BLAKE 2850 15050
JONES 2975 18025
SCOTT 3000 21025
FORD 3000 24025
KING 5000 29025
MySQL、PostgreSQL和SQL Server
使用标量子查询计篏计和Q由于不使用SUM OVERq类H口函数Q因此就不能像在DB2和Oracle解决Ҏ中那样容易地按SALl结果排序)。不怎么_累计和是正确的(最l结果与上一节相同)Q但׃没有q行排序Q其中间值有所不同Q?/p>
1 select e.ename, e.sal,
2 (select sum(d.sal) from emp d
3 where d.empno <= e.empno) as running_total
4 from emp e
5 order by 3
ENAME SAL RUNNING_TOTAL
---------- ---------- -------------
SMITH 800 800
ALLEN 1600 2400
WARD 1250 3650
JONES 2975 6625
MARTIN 1250 7875
BLAKE 2850 10725
CLARK 2450 13175
SCOTT 3000 16175
KING 5000 21175
TURNER 1500 22675
ADAMS 1100 23775
JAMES 950 24725
FORD 3000 27725
MILLER 1300 29025
讨论
生成累计和是因用新的ANSIH口函数而得以简化的d之一。对于不支持q些H口函数的DBMSQ需要用标量子查询Q按取值唯一的字D联接)?/p>
DB2和Oracle
H口函数SUM OVER能够非常Ҏ地生成篏计和。该解决Ҏ中的ORDER BY子句不仅包含SAL列,而且q包含EMPNO列(主键Q,以避免篏计和中出现重复倹{下面例子中的RUNNING_TOTAL2列示意了存在重复值时可能带来的问题:
select empno, sal,
sum(sal)over(order by sal,empno) as running_total1,
sum(sal)over(order by sal) as running_total2
from emp
order by 2
ENAME SAL RUNNING_TOTAL1 RUNNING_TOTAL2
---------- ---------- -------------- --------------
SMITH 800 800 800
JAMES 950 1750 1750
ADAMS 1100 2850 2850
WARD 1250 4100 5350
MARTIN 1250 5350 5350
MILLER 1300 6650 6650
TURNER 1500 8150 8150
ALLEN 1600 9750 9750
CLARK 2450 12200 12200
BLAKE 2850 15050 15050
JONES 2975 18025 18025
SCOTT 3000 21025 24025
FORD 3000 24025 24025
KING 5000 29025 29025
对于WARD、MARTIN、SCOTT和FORDQRUNNING_TOTAL2中的值都不正。他们的工资分别出现了多ơ,q些重复值都被加在一赯入篏计和。这是需要用EMPNOQ它是唯一的)才能生成与RUNNING_TOTAL1一LQ正)l果的原因。大家想一惻I对于ADAMSQRUNNING_TOTAL1的gؓ2850QRUNNING_TOTAL2把WARD的工?250?850相加Q应该得?100Q然而,RUNNING_TOTAL2却返回了5350Q这是ؓ什么呢Q因为WARD和MARTIN的SAL相同Q他们两个的工资Q?250Q加在一起就{于2500Q然后再?850Q就得到5350。如果指定按不会有重复值的列组合(例如QSAL和EMPNO的取值组合都是唯一的)排序Q就能确保生成正的累计和?/p>
MySQL、PostgreSQL和SQL Server
在这些DBMS完全支持H口函数之前Q可以用标量子查询计算累计和。一定要按取值唯一的列联接Q否则一旦存在像工资重复q样的情况,׃产生不正的累计和。本节解x案的关键是把D.EMPNO与E.EMPNO联接hQ它会返回(求和Q每个满D.EMPNO于或等于E.EMPNO D.SAL。ؓ了更Ҏ理解q些内容Q可以重新编写标量子查询Q把它写成职员之间的联接Q?/p>
select e.ename as ename1, e.empno as empno1, e.sal as sal1,
d.ename as ename2, d.empno as empno2, d.sal as sal2
from emp e, emp d
where d.empno <= e.empno
and e.empno = 7566
ENAME EMPNO1 SAL1 ENAME EMPNO2 SAL2
---------- ---------- ---------- ---------- ---------- ----------
JONES 7566 2975 SMITH 7369 800
JONES 7566 2975 ALLEN 7499 1600
JONES 7566 2975 WARD 7521 1250
JONES 7566 2975 JONES 7566 2975
EMPNO2中的每个gEMPNO1中的每个值相比较。对于EMPNO2值小于等于EMPNO1值的所有行Q都会把SAL2值加入d。在q个例子中,职员Smith、Allen、Ward和Jones的EMPNO值都与Jones的EMPNO值相比较。由于这四个职员的EMPNO都满_于等于Jones的EMPNO的条Ӟ所以会把这些工资加hQ而那些大于Jones的EMPNO的职员都不会计入SUM中。完整的查询的计方法是Q将所有EMPNO于{于7934QMiller的EMPNOQ这个表中的最大|的所有职员的工资加v来?/p>