转自:http://www.itpub.net/thread-719692-1-1.html

摘要
一组连续的数,去掉中间一些数,如何求出剩下的数的区间(即号段)?知道号段的起止,如何求出该号段内所有的数?知道一个大的号段范围和已经取过的号段,如何求出可用的号段?利用Oracle提供的强大的查询功能以及分析函数,我们可以很轻松的解决上述问题。

关键词:
号段选取、连续数、断点、层次查询、分析函数、connect by、rownum、level、lead、lag

1.        问题的提出
在实际工作中,我们常常会碰到号段选取的问题,例如:
        一组连续的数,去掉中间一些数,要求出剩下的数的区间(即号段)
例如:一串数字为1,2,3,4,7,9,10,则号段为1-4,7-7,9-10
        知道号段的起止,要求出该号段内所有的数
例如:号段为1-3,15-15,则号段内所有的数为1,2,3,15
        一组数,中间可能有断点,要求出缺失的数
例如:一串数字为1,2,3,4,7,9,10,则缺失的数为5,6,8
        已知大号段范围及已用号段范围,求可用号段范围
例如:大号段范围0-999,已用号段范围0-200,399-599,则可用号段范围为201-398,600-999
2.        基础知识
先做下热身运动,回顾一下层次查询和lead/lag函数的运用。
2.1        伪列rownum和level
伪列就是并非在表中真正存在的列。已有很多资料介绍rownum和level这两个伪列。这里只想强调一点,伪列是只针对结果集的。
2.2        利用层次查询构造连续的数
        产生5~8这4个连续的数
[php]
select * from (select rownum+4 from dual connect by rownum<5);
select * from (select level+4 from dual connect by level<5);
........
[/php]
        以8月为界,例如2005年8月1日,之前的在校学生入学年份为2001~2004,之后的为2002~2005。求当前日期下的在校学生入学年份:
[php]
select * from (select to_char(add_months(sysdate, 4), 'yyyy') - rownum from dual connect by rownum<5);
........
[/php]
2.3        用分析函数Lead和Lag获得相邻行的字段值
[php]
select rn, lag(rn)over(order by rn) previos, lead(rn)over(order by rn) next
from (select rownum+4 rn from dual connect by rownum<5);

RN    PREVIOS       NEXT
---------- ---------- ----------
         5                     6
         6          5          7
         7          6          8
         8          7
........
[/php]
简单的说,在这里,Lag是获得前一行的内容,而Lead是获得后一行的内容。
[php]
select rn, lag(rn,2,-1)over(order by rn) previos, lead(rn,2,-1) over(order by rn) next
from (select rownum+4 rn from dual connect by rownum<5);

RN    PREVIOS       NEXT
---------- ---------- ----------
         5         -1          7
         6         -1          8
         7          5         -1
         8          6         -1
........
[/php]
这里,通过指定offset参数来获得两行前的内容和两行后的内容,如果offset超出范围并且未设定默认值-1,那么系统会自动将其值设为NULL。
3.        问题的解决
有了基础知识的积累,我们就可以解决前面提到的问题。
3.1        已知号码求号段
3.1.1        题例
我有一个表结构,
fphm,kshm
2014,00000001
2014,00000002
2014,00000003
2014,00000004
2014,00000005
2014,00000007
2014,00000008
2014,00000009
2013,00000120
2013,00000121
2013,00000122
2013,00000124
2013,00000125

(第二个字段内可能是连续的数据,可能存在断点。)

怎样能查询出来这样的结果,查询出连续的记录来。
就像下面的这样?
2014,00000001,00000005
2014,00000009,00000007
2013,00000120,00000122
2013,00000124,00000125

3.1.2        解答
思路:利用lag取得前一行的kshm,然后和本行的kshm想比,如果差值为1,说明这一行和上一行是连续的。由于首尾的特殊性,故而需要先用max和min来获得首尾点。
[php]
select fphm, nvl(lag(e)over(partition by fphm order by s),minn) ST, nvl(S,maxn) EN from
(select fphm, lag(kshm,1) over(partition by fphm order by kshm) S, kshm E,
min(kshm)over(partition by fphm) minn, max(kshm) over(partition by fphm) maxn from t)
where nvl(E-S-1,1)<>0;

FPHM       ST         EN
---------- ---------- ----------
2013       00000120   00000122
2013       00000124   00000125
2014       00000001   00000005
2014       00000007   00000009

........
[/php]
3.2        根据号段求出包含的数
3.2.1        题例
有表及测试数据如下:
CREATE TABLE T20
(
ID NUMBER(2),
S NUMBER(5),
E NUMBER(5)
);

INSERT INTO T20 ( ID, S, E ) VALUES ( 1, 10, 11);
INSERT INTO T20 ( ID, S, E ) VALUES ( 2, 1, 5);
INSERT INTO T20 ( ID, S, E ) VALUES ( 3, 88, 92);
COMMIT;

S为号段起点,E为号段终点,求出起点和终点之间的数(包括起点和终点)
3.2.2        解答
很明显,这需要构造序列来解决问题
[php]
select a.id, a.s, a.e,b.dis, a.S+b.dis-1 h from
t20 a,
(select rownum dis from
    (select max(e-s)+1 gap from t20)
connect by rownum<=gap) b
where a.e>=a.s+b.dis-1
order by a.id, 4

运行结果:
        ID          S          E        DIS          H
---------- ---------- ---------- ---------- ----------
         1         10         11          1         10
         1         10         11          2         11
         2          1          5          1          1
         2          1          5          2          2
         2          1          5          3          3
         2          1          5          4          4
         2          1          5          5          5
         3         88         92          1         88
         3         88         92          2         89
         3         88         92          3         90
         3         88         92          4         91
         3         88         92          5         92

........
[/php]
我们再看下面这种做法:

select a.id, a.s, a.e,rownum, a.S+rownum-1 h from
t20 a ,
(select id, e-s+1 gap from t20 where id=2) b
where a.id=b.id
connect by rownum<=gap

[php]
        ID          S          E     ROWNUM          H
---------- ---------- ---------- ---------- ----------
         2          1          5          1          1
         2          1          5          2          2
         2          1          5          3          3
         2          1          5          4          4
         2          1          5          5          5

........
[/php]
嗯,得到的结果也是正确的,若我们把粗斜体字部分去掉后,看看结果是什么样:
[php]
        ID          S          E     ROWNUM          H
---------- ---------- ---------- ---------- ----------
         1         10         11          1         10
         1         10         11          2         11
         2          1          5          3          3
         2          1          5          4          4
         2          1          5          5          5
         2          1          5          6          6
         3         88         92          7         94
........
[/php]
这样的结果,显然不是我们需要的,更何况,这是错误的。由此更能深入理解,伪列是只针对结果集的。
3.3        求缺失的号
3.3.1        题例
table T,列:serial_no
我想能够查询一下serial_no这个字段的不连续的值。
例如:
serial_no
1
2
3
4
6
8
9
10
我想一个sql语句查出来缺失的号码,
显示结果为:
5
7
3.3.2        解答
思路:找出数B和它前面的数A进行比较(数按从大到小进行排序),如果B-A=1,则说明是连续的,中间没有断点。
[php]
select distinct s+level-1 rlt from (select lag(serial_no,1) over(order by serial_no)+1 S,
serial_no-1 E from t) where E-S<>0 connect by level<=e-s
........
[/php]
3.4        求尚未使用的号段
3.4.1        题例
表A结构:
bill_type_id varchar2(1),
bill_start number,
bill_end number,
office_level varchar2(4)
数据如下:
A 0 999 1
A 0 199 2
A 300 499 2
A 700 799 2
sql目的是取出包含在level1级别里的,还没有录入level2级别的号段。
3.4.2        解答
这个好像是3.1和3.3这两个问题的逆问题
创建表及测试数据:
CREATE TABLE T8
(
A NUMBER(4),
B NUMBER(4),
C NUMBER(4),
Q VARCHAR2(1 BYTE)
);

Insert into T8(A, B, C, Q)Values(555, 666, 2, 'A');
Insert into T8(A, B, C, Q)Values(100, 199, 2, 'A');
Insert into T8(A, B, C, Q)Values(0, 999, 1, 'A');
Insert into T8(A, B, C, Q)Values(300, 499, 2, 'A');
COMMIT;

思路:将大号段的边界与小号段的边界相比,从大号段中将小号段“挖”掉,这样剩下的就是可用号段了。
[php]
select S,E from
(
SELECT NVL2(LAG(A)OVER(PARTITION BY Q ORDER BY A), B+1, MIN(A)OVER(PARTITION BY Q)) S,
NVL(LEAD(A)OVER(PARTITION BY Q ORDER BY A)-1, MAX(B)OVER(PARTITION BY Q)) E
from t8 START WITH C=1 CONNECT BY C-1 = PRIOR C AND Q= PRIOR Q
)
where s<=e
运行结果:
         S          E
---------- ----------
         0         99
       200        299
       500        554
       667        999        
........

Oracle层次查询和分析函数在号段选取中的应用的更多相关文章

  1. 【转载】Oracle层次查询和分析函数

    摘要 一组连续的数,去掉中间一些数,如何求出剩下的数的区间(即号段)?知道号段的起止,如何求出该号段内所有的数?知道一个大的号段范围和已经取过的号段,如何求出可用的号段?利用Oracle提供的强大的查 ...

  2. Oracle层次查询

    Oracle层次查询的语法如下: 下面根据两道“烧脑”的题具体来体现: 1. 根据时间先后顺序,十二星座的英文名称用逗号串起来为'Aries,Taurus,Gemini,Cancer,Leo,Virg ...

  3. 带您了解Oracle层次查询

    http://database.51cto.com/art/201010/231539.htm Oracle层次查询(connect by )是结构化查询中用到的,下面就为您介绍Oracle层次查询的 ...

  4. Oracle 层次查询 connect by

      oracle 层次查询 语法:       SELECT ... FROM            [WHERE condition]                             --过 ...

  5. Oracle - 层次查询

    如果表中含有层次数据,可以通过使用层次查询有序地查看层次数据. 语法: condition:指一个或多个表达式和逻辑(布尔)运算符的组合,并返回TRUE.FALSE或UNKNOWNstart with ...

  6. Oracle层次查询start with connect by

    博客参考:https://www.cnblogs.com/jerryxing/articles/2339352.html start with connect by 层次查询(Hierarchical ...

  7. oracle层次查询的陷阱

    今天开发组同事找到我,说一个简单的层次查询非常慢,业务就是有一个存设备表连接关系的表,从node1连入,从node2连出,现在要找出node2的连出顺序,sql类似于: SELECT LEVEL ID ...

  8. 【转】 oracle 层次查询判断叶子和根节点

    Oracle 9i判断是叶子或根节点,是比较麻烦的一件事情,SQL演示脚本如下: DROP TABLE idb_hierarchical; create TABLE idb_hierarchical ...

  9. Oracle层次查询和with函数的使用

    开发中大家应该都做过什么类似部门管理这样的功能,一般情况下一个部门下面还有下一级部门(子部门),这个层级就类似一棵树.这种情况下一般会把父级部门和子级部门分成2个或者多个表,这种算是比较常规的做法:有 ...

随机推荐

  1. 使用rpm包安装lamp环境

    前提: 是你的centos能联网,或者有本地的yum仓库 或者配置通过代理上网 vim /etc/yum.conf 加入如下内容 proxy=http://192.168.11.82:808 1.通过 ...

  2. Ex 6_18 硬币有限的兑换问题_第七次作业

    子问题定义: 定义一个二维数组b,其中b[i][j]表示前i个币种是否能兑换价格j,表示第i个币种的面值,第i个币种的使用有两种情况,若使用,则b[i][j]=b[i-1][j-],若不使用,则b[i ...

  3. Oracle 数据库、实例、用户、表空间、表之间的关系

    数据库: Oracle数据库是数据的物理存储.这就包括(数据文件ORA或者DBF.控制文件.联机日志.参数文件).其实oracle数据库的概念和其它数据库不一样,这里的数据库是一个操作系统只有一个库. ...

  4. 学习笔记(一)--->《Java 8编程官方参考教程(第9版).pdf》:第一章到六章学习笔记

    注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.违者本人不负法律责任.违法者自负一切法律责任. ...

  5. hdu3642扫描线 长方体

    立方体交,自己写的莫名其妙MLE了,不知道为什么 #include<iostream> #include<cstring> #include<cstdio> #in ...

  6. (二)使用CXF开发WebService服务器端接口

    CXF作为java领域主流的WebService实现框架,Java程序员有必要掌握它. CXF主页:http://cxf.apache.org/ 简介:百度百科 今天的话,主要是用CXF来开发下Web ...

  7. 2018-2019 2 20165203 《网络对抗技术》 Exp3 免杀原理与实践

    2018-2019 2 20165203 <网络对抗技术> Exp3 免杀原理与实践 免杀原理与实践说明及基础问答部分 实验任务 正确使用msf编码器(0.5分),msfvenom生成如j ...

  8. RabbitMQ(二):Java 操作队列

    1. 简单模式 模型: P:消息的生产者 队列:rabbitmq C:消息的消费者 获取 MQ 连接 public static Connection getConnection() throws I ...

  9. 《SQL基础教程》

    Product表 CREATE TABLE Product (product_id CHAR(4) NOT NULL, product_name VARCHAR(100) NOT NULL, prod ...

  10. 里氏代换原则(Liskov Substitution Principle,LSP)

    第一种定义: 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换为o2,程序P的行为没有发生变化,那么类型S是类型T的子类型. 第二种定义: 所有引 ...