SQL书写入门提高级之数据构造

[复制链接]
查看11 | 回复9 | 2008-10-22 23:25:51 | 显示全部楼层 |阅读模式
本帖仅讨论静态SQL. 阅读对象: Oracle 初中级用户.
SQL书写的一般技巧就在于SQL查询中的数据构造,首先你必须知道的静态SQL的一些特性:
a) 一旦书写完一个静态SQL, 那么输出的列数, 列类型就已经确定, 不会在SQL执行过程中动态增加列数或改变列类型.
b) SQL查询本身是对表中已有数据进行查询, 因此你不能通过SQL获取表中不存在的数据.
c) SQL查询不允许递归, 也就是不允许在SQL查询的结果上重复的执行SQL查询.
对于这些特性中的c),我们也容易理解, 毕竟SQL是结构化数据查询语言, 目的就是查询数据, 所以不具备 PLSQL代码的循环, 递归等功能是再正常不过的事情.比如我有数据a, 经
过一逻辑运算后得到b, 然后我对b再采用同样的计算逻辑得到c, 以此类推, 知道最后的结果满足某条件, 这样的递归计算逻辑SQL就是无法实现的. 而对于b)特性稍微想想,我们也
可以理解, 你不能指望SQL查询出不存在的数据出来, 比如盒子里有个苹果, 你总不能多拿出一个梨子来吧.
SQL查询的主要运用除了直接查询出表中的数据之外就是对于已知数据进行加工得到另一种形式的数据. 这里边包含两种情况, 第一, 查询列的构造. 比如仓库里谷物, 那么我进行
加工得到米和糠, 这个米和糠不是凭空产生, 它是从已有物品谷物加工而来. 这是SQL能做的.
比如 scott 下的emp 表为例, 其中有 雇用日期 hiredate, 则我们可以得到员工的生日, 年龄列表, 如下:
select empno,ename,trunc((sysdate - hiredate)/365) age, to_char(hiredate,'mm/dd') birthday from emp;
EMPNO ENAME
AGE BIRTHDAY
----- ---------- ---------- --------
7369 SMITH
27 12/17
7499 ALLEN
27 02/20
7521 WARD
27 02/22
........
第二, 查询行的构造, 这种情况的构造要稍微难一些, 比如我要对对公司员工人数按照部门做个统计, 那么各个部门的人数表就不是已知数据, 而是通过员工信息表进行汇总会得
到构造行.
比如我们要得到部门的人数统计还有总人数, 如下:
select deptno,count(*) from emp group by rollup(deptno);
DEPTNO COUNT(*)
------ ----------
10
3
20
5
30
6

14
最后一行为总人数.
有时,我们需要输出一个列表, 但是这个列表中的数据并不是从现成的数据表中获取的, 而可能是通过一定的计算逻辑得到的, 那么这个时候的数据构造和前面的数据构造又有些不
同.比如我要得到某个月的日期列表, 当我们已知月份的时候, 我们就可以推算出该月的日期列表, 根本不需要将日期列表存放于数据表中. 那么既然不存在于数据表中, 我们要如
何来查询出这个列表呢? 首先我们必须要得到一个具有该月份天数的数据行数, 当我们得到数据行过后, 在通过某种计算逻辑构造日期列, 最终就得到了该月份的日期列表. 要得
到数据行, 我们可以查询任意包含数据的表来获取数据行, 只是我们不需要该数据表中的任何列信息, 我们所要的就是重新构造一个日期列. 但这里有个限制就是, 如果表中只有
20条数据, 我将无法得到30个数据行(假定该月份有30天). 这时你有两种选择, 一是查询具有更多数据行的表, 并且限制返回行数为你需要的行数, 其二是进行表的自身连接, 形
成笛卡尔积, 这样 20*20 最多就可以返回 400 行, 你只需要限制返回30行即可. 当然高版本Oracle还可以用分层查询(select level from dual connect by level 2,同时假定 all_objects 表中的数据行数大于 N.
select mod(n,rownum + 1) mod_n from all_objects where rownum <= n - 2;
这一步构造了 N- 2 行数据, 对于每一行都用一个自然数去试除 N, 并得到一个余数.
接下来, 就是在这个已知的结果集上再次执行SQL查询, 判断其中是否存在有 能整除的 行, 余数为0就是能整除.
当然这最后一步有多种判断法, 我们这里使用上述结果集中能整除的行数超过1则是合数, 否则是素数的方法来判定.
select decode(count(*),0,'素数','合数') from
(select mod(n,rownum + 1) mod_n from all_objects where rownum <= n - 2)
where mod_n = 0;
补充:构造数据行, 除了从已知表中的数据得到, 分层查询得到, 还可以从有下面这种方式,不知道这个是不是标准SQL中有的, 只是这种方式的行中没有行号信息, 需要再套一层获取行号信息.
select 1 from dual group by cube(2,2,2);
select rownum from (select 1 from dual group by cube(2,2,2));
这里 group by cube(2,2,2) 可以得到 2*2*2 个数据行, group by cube(2,2,2,2) 就可以得到 16 个数据行.
原帖由 homeworld80 于 2008-10-27 18:03 发表
楼主的构造数据行的方式较不灵活,
这里 group by cube(2,2,2) 可以得到 2*2*2 个数据行, group by cube(2,2,2,2) 就可以得到 16 个数据行,这句话容易误导读者,
得到的数据行和 group by cube(2,2,2)里面的2没有关系,完全可以改成 group by cube(1,1,1),这其实是一个组合问题,计算如下:
C(0,n)+...+C(n,n)
这个式子不知道该怎么简化了,呵呵。
原帖由 newkid 于 2008-10-27 22:06 发表 简化就是2^n
这里原本我是想用 group by cube(null,null,null) 等形式的, 后来为了方便记忆最终得到的行数而使用了常数2, 的确会导致误解, 多谢提醒. 使用 cube 是对数据进行交叉统计, 这个知识点不在这个讨论范围.
[ 本帖最后由 N先生 于 2008-10-28 09:55 编辑 ]
回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
2002年的一个精华帖:
http://www.itpub.net/viewthread. ... p%3Bfilter%3Ddigest
SQL 大挑战-- 可以放到精华区的几个命题

这里有三道题,对于第三题,考虑了两天,还没有最让我满意的解法--我想能够自动补充日期间隔,但是没有办法只用select语句实现,要借助于PL/SQL才能动态生成,大家都来动动脑吧。
TABLE如下
日期收入支出
2000/3/1 5030
2000/3/2 4560
2000/3/5 6010
能否用SELECT語句得出以下結果
1.
日期 收入支出 余額
2000/3/150 30 20
2000/3/245 60 5
2000/3/560 10 55
2.
TABLE加入一行
2000/3/5 6010
結果
日期 收入支出 余額
2000/3/150 30 20
2000/3/245 60 5
2000/3/512020 105
3.
日期 收入支出 余額
2000/3/150 30 20
2000/3/245 60 5
2000/3/30 0 5
2000/3/40 0 5
2000/3/512020 105
为了做这个题目, 我们首先把建表脚本写出:
alter session set nls_date_format='yyyy/mm/dd';
create table test(dt date,income number, expense number);
insert into test values('2000/3/1',50,30);
insert into test values('2000/3/2',45,60);
insert into test values('2000/3/5',60,10);
commit;
对于这个题目, 我们来看第1题, 仅仅是需要构造出一个列"余额",而余额的值来自于前面(按照日期排序)所有行的收入和支持之和.由于无法从SQL查询的当前行得知余额,而必须要从多行中得到余额, 那么这里我们这个构造列可以使用一个子查询, 用于汇总相应行的值. 如下:
select dt,income,expense,(select sum(t2.income - t2.expense) from test t2 where t2.dt <= t1.dt) balance
from test t1;
DT
INCOME EXPENSE BALANCE
---------- --------- --------- ---------
2000/03/01503020
2000/03/024560 5
2000/03/05601055
对于第二题,
insert into test values('2000/3/5',60,10);
commit;
与第一题相比并没有新增数据构造, 仅仅是将相同日期的数据进行了合并而已.这不属于本帖讨论范围.
select dt,sum(income) income,sum(expense) expense,
(select sum(t2.income - t2.expense) from test t2 where t2.dt <= t1.dt) balance
from test t1
group by dt
order by dt;
DT
INCOME EXPENSE BALANCE
---------- --------- --------- ---------
2000/03/01503020
2000/03/024560 5
2000/03/05 12020 105
最后对于第三题, 我们看到原始表中并不是每一个日期都有数据, 最终结果需要出现没有收支记录的日期记录. 这就是行的构造了.我们使用前面介绍的方法, 我从具有数据的 all_objects 表中获取行来构造. 另外我们注意到结果中只补足了原始数据表中最小日期和最大日期之间的日期数据, 因此我们要找出最大最小日期之间的所有日期.
这个我们前面有介绍, 比较容易:
select (select min(dt)from test)+ rownum - 1 dt from all_objects
where rownum <= (select max(dt) - min(dt) + 1 from test);
DT
----------
2000/03/01
2000/03/02
2000/03/03
2000/03/04
2000/03/05
得到了所有日期的列表, 那么我们使用一个外连接就可以把各个日期的收支数据包括进来了:
select a.dt, nvl(b.income,0) income, nvl(b.expense,0) expense
from (select (select min(dt) from test) + rownum - 1 dt

from all_objects
where rownum <= (select max(dt) - min(dt) + 1 from test)) a,
test b
where b.dt(+) = a.dt;
DT
INCOMEEXPENSE
---------- ---------- ----------
2000/03/01 50 30
2000/03/02 45 60
2000/03/03
0
0
2000/03/04
0
0
2000/03/05 60 10
2000/03/05 60 10
至此, 我们又回到了第二题的情况, 很容易的写出最终的SQL,
with tmp as(
select a.dt, nvl(b.income,0) income, nvl(b.expense,0) expense
from (select (select min(dt) from test) + rownum - 1 dt

from all_objects

where rownum <= (select max(dt) - min(dt) + 1 from test)) a,
test b
where b.dt(+) = a.dt)
select dt,sum(income) income,sum(expense) expense,
(select sum(t2.income - t2.expense) from tmp t2 where t2.dt <= t1.dt) balance
from tmp t1
group by dt
order by dt;
DT
INCOMEEXPENSEBALANCE
---------- ---------- ---------- ----------
2000/03/01 50 30 20
2000/03/02 45 60
5
2000/03/03
0
0
5
2000/03/04
0
0
5
2000/03/05120 20105
[ 本帖最后由 N先生 于 2008-10-28 09:44 编辑 ]
回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
精华区的SQL难度适中的就找到上面一个, 其它的SQL大多也用到了数据构造, 但是更多的是列的复杂构造, 不适合放在这里.
本主题就到这里.
[ 本帖最后由 N先生 于 2008-10-28 09:55 编辑 ]
回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
guanshui太狠了把
回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
楼主的构造数据行的方式较不灵活,
这里 group by cube(2,2,2) 可以得到 2*2*2 个数据行, group by cube(2,2,2,2) 就可以得到 16 个数据行,这句话容易误导读者,
得到的数据行和 group by cube(2,2,2)里面的2没有关系,完全可以改成 group by cube(1,1,1),这其实是一个组合问题,计算如下:
C(0,n)+...+C(n,n)
这个式子不知道该怎么简化了,呵呵。
[ 本帖最后由 homeworld80 于 2008-10-27 18:21 编辑 ]
回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
原帖由 homeworld80 于 2008-10-27 18:03 发表
楼主的构造数据行的方式较不灵活,
这里 group by cube(2,2,2) 可以得到 2*2*2 个数据行, group by cube(2,2,2,2) 就可以得到 16 个数据行,这句话容易误导读者,
得到的数据行和 group by cube(2,2,2)里面的2没有关系,完全可以改成 group by cube(1,1,1),这其实是一个组合问题,计算如下:
C(0,n)+...+C(n,n)
这个式子不知道该怎么简化了,呵呵。

简化就是2^n
楼主验证素数的方法可以改进,求到N的开方为上限就行了。
回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
mark
支持
BTW:N先生 在干什么啊
回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
原帖由 newkid 于 2008-10-27 22:06 发表
楼主验证素数的方法可以改进,求到N的开方为上限就行了。

这里只是列举来说明数据行,列的构造而已, 并不过多的去关注某个问题的最优解


回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
原帖由 jvkojvko 于 2008-10-27 22:10 发表
mark
支持
BTW:N先生 在干什么啊

做 Oracle 开发呀


回复

使用道具 举报

千问 | 2008-10-22 23:25:51 | 显示全部楼层
原帖由 N先生 于 2008-10-27 17:16 发表
2002年的一个精华帖:
http://www.itpub.net/viewthread. ... p%3Bfilter%3Ddigest
SQL 大挑战-- 可以放到精华区的几个命题

这里有三道题,对于第三题,考虑了两天,还没有最让我满意的解法--我想能够自动补充日期间隔,但是没有办法只用select语句实现,要借助于PL/SQL才能动态生成,大家都来动动脑吧。

就用SUM(income - expense) OVER (........)好了,哪里用那么麻烦?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题

0

回帖

4882万

积分

论坛元老

Rank: 8Rank: 8

积分
48824836
热门排行