1 With子句
1.1 学习目标
掌握with子句用法,并且了解with子句能够提高查询效率的原因。
1.2 With子句要点
- with子句的返回结果存到用户的临时表空间中,只做一次查询,反复使用,提高效率。
- 如果定义了with子句,而在查询中不使用,那么会报ora-32035 错误:未引用在with子句中定义的查询名。
- 前面的with子句定义的查询在后面的with子句中可以使用。但是一个with子句内部不能
嵌套with子句。
- 当一个查询块名字和一个表名或其他的对象相同时,解析器从内向外搜索,优先使用子查询块名字。
- with查询的结果列有别名,引用的时候必须使用别名或*。
- with有可能影响执行计划。
1.3 with子句语法
With alias_name as (select1), --as和select中的括号都不能省略
alias_name2 as (select2),--后面的没有with,逗号分割,同一个主查询同级别地方,with子
查询只能定义一次
…
alias_namen as (select n) –与下面的实际查询之间没有逗号
Select ….
1.4 with使用例子:
- 最简单的使用方法:
如查询部门名称包含“A”的所有员工信息
--with clause
with a as
(select deptno from dept where dname like '%A%')
select * from emp where deptno in (select * from a);
with a as
(select deptno from dept where dname like '%A%'),--a结果集
a2 as(select * from a where deptno>20)--a1结果集直接从a中筛选
select * from emp where deptno in (select * from a2);
- 多层同级只能用一个with,并且后面的结果集可以使用前面的结果集:
查询部门名称包含“A”并且部门编号大于20的所有员工信息
with a as
(select deptno from dept where dname like '%A%'),--a结果集
a2 as(select * from a where deptno>20)--a1结果集直接从a中筛选
select * from emp where deptno in (select * from a2);
- 不同级查询可以使用多个with:
查询部门名称包含“A”并且部门编号大于20的所有员工信息的另外一种实现方式如下
with a as
(select deptno from dept where dname like '%A%')--a结果集
select * from emp where deptno in (--括号内层作为子查询,为第二级
with a2 as(select * from a where deptno>20)--a1结果集直接从a中筛选
select * from a2
);
1.5 使用场景
那什么情况下能使用到with子句呢?以下我就举几个简单的例子,简单的说明以下:
- 我想测试一句sql,而我不想专门建立一个测试表:
我想测试成绩大于90的学生,我不想建立学生表,可以用到with子句
with stu as(
select '张娜' sname,99 score from dual union
select '王杰' ,35 from dual union
select '宋丽' ,85 from dual union
select '陈晓' ,73 from dual union
select '李元' ,100 from dual
)--with 组成一个临时的结果集,存放在用户的临时表空间
select * from stu where score>90
- 当一个sql重复用到某个相同的结果集作为子查询:
--查询销售部工资>1500或者销售部工资小于1000的员工
select * from emp where deptno=(select deptno from dept where dname ='SALES') and sal >1500
union all
select * from emp where deptno=(select deptno from dept where dname ='SALES') and sal <1000
--以上sql select deptno from dept where dname ='SALES'需要执行两次,影响效率
--可以使用with优化一下
with salno as(select deptno from dept where dname ='SALES')
select * from emp where deptno=(select * from salno) and sal >1500
union all
select * from emp where deptno=(select * from salno) and sal <1000
2 集合操作
2.1 学习目标
掌握union,union all,minus,intersect的使用,能够描述集合运算,了解内部运行原理。
2.2 要点
Union all 效率一般比union高。Union all内部不做排序工作,也不做剔除
重复行工作,而union则做这个工作。所以当数据量比较大的时候,能用union all的时候尽量用union all。除了union all 默认不做排序和剔除重复行的操作外,
union,minus,intersect都默认按第1个查询结果的第1列进行升序排列,并且
不包含重复行。
2.3 语法
(select resource 1)
Union/union all/minus/intersect
(select resource 2)
Union/union all/minus/intersect
(select resource 3)
……….
其中查询结果集的各个字段的类型能够互相兼容,并且总的结果集字段名与第一个结果集相同。
2.4 使用案例
数据准备:
create table t1 as select rownum rn from dual connect by rownum<7;
create table t2 as select rownum+3 rn from dual connect by rownum<7;
- 查询t1和t2表的所有记录,不去除重复。
- 查询t1和t2表的所有记录,去除重复。
- 查询t1和t2表都存在的记录
- 查询t1表存在,t2表不存在的记录
- 排序操作:
- 除了union all其他的全部会在总的结果集中剔除重复,例如:
insert into t1 values(1);
commit;
现在t1表中有两条相同的记录,其rn的值为1。
在进行集合运算时重复的记录被剔除:
2.5 使用场景
当要对多个结果集进行集合操作时,可是使用集合操作。
3 case与decode
3.1 学习目标
会使用case表达式和decode函数,理解各个参数和返回值的含义。
3.2 要点
Case表达式:
- When后面的表达式类型应该全部保持一致,返回值类型也必须保持一致,或者能够进行隐式转换。
- case 表达式 when 值,如果值是null,就算表达式也是null,结果也是返回false。也就是case 后面的表达式如果值为null,不会与when null 匹配,只会与else 匹配。
Decode函数的使用方法与case when相似,但是decode只能用等号匹配。
3.3 语法
Case表达式第一种:
case exp when comexp then returnvalue
..when comexp then returnvalue
Else
Returnvalue
End
Case表达式第二种:
case when Boolean then returnvalue
..when Boolean then return value
Else
Returnvalue
End
Decode函数:
decode(exp,
value1,res1,
value2,res2,….,
valuen resn,
elsevalue)。
3.4 使用案例
Case 第一种用法:
Case 第二种用法:
Decode用法:
上文提到过null,碰到null的时候要注意,比如:
这种情况可以这样处理:
如果用decode函数:
3.5 使用场景
当我们的sql要求根据不同的条件返回不同的值时,可以使用。
4 exists与in、not exists与not in
4.1 学习目标
掌握exists与in的、not exists与not in的用法,了解其内部的执行顺序 与执行原理,知道什么情况下用exists,什么情况下用in。
4.2 要点
- Exists 用于只能用于子查询,可以替代in,若匹配到结果,则退出内部
查询,并将条件标志为true,传回全部结果资料。
- 若子查询结果集比较小,优先使用in,若外层查询比子查询小,优先使用exists。因为若用in,则oracle 会优先查询子查询,然后匹配外层查询,若使用exists,则oracle 会优先查询外层表,然后再与内层表匹配。最优化匹配原则,拿最小记录匹配大记录。
4.3 语法
In:Select select_fields from table_name where field_name in(select clause);
Exists:Select select_fields from table_name exists (select clause)
4.4 使用案例
查询员工部门编号在部门表中存在的员工记录:
以上语句可以用Exist替换:
另外not in和not exists在某些情况下也可以相互转换,但是要注意一点,not in中的子查询返回的结果集包含null值的时候,查询会失效。例如我想查询对应员工记录数为0的部门。如下:
用not exists:
以上语句不能用not in替换:
查询失效无记录返回。注意这并不是oracle的bug,因为在oracle中null不表示空,而是表示未知,当使用not in的时候,如果子查询返回的结果集中包含null值,我们并不知道外层查询的记录在不在子查询返回的结果集之内,所以无记录返回。虽然这样,但是并不表示not in和not exists是完全不可以转换的,比如子查询所选的字段在对应的表中没有null值,这时not in和not exists是可以相互转换的。或者在某些情况下内层子查询加上field_name is not null限制条件也是可以的。
4.5 使用场景
当内层查询返回的结果集较小时,用in 或者not in效率较高。当内层子查询返回的结果集比较大时,用exists或者not exists执行的效率较高。
5 行列互换
5.1 学习目标
掌握列转行技术和常用的行专列技术。
5.2 要点
行专列的情况有多种,不同的情况侧重点也不一样。
5.3 语法
5.4 使用案例
- 列转行
第一种方法:需要用到union或者union all:
第二种方法:用到model
- 行专列,如我有escore表用来记录每个学生每个科目的成绩,如下:
如果我想将每个学生的成绩统计在一行上,如:
3 语文 11 数学 55 英语 66
则我可以使用如下sql:
这个sql表面上看没什么问题,但是仔细看一下三个结果集es、ys和ss,他们来源于同一个表,而且查询方法也类似,都是根据type的值去筛选的,这样就会对escore表查询三遍,严重影响查询速率,那这个sql我们如何去优化呢!
首先在你的脑海里面要有一种思路,根据需求,原先每个学生成绩有多行记录,现在要显示到一行上,那一般情况下我们是需要根据学生分组的。所以group by sid 这个是一定要有的,既然分组那我们可是使用oracle的聚合函数去求其他行的数据。至于科目字段目前都是已知的,也就是第2,4,6列显示的分别是英语、语文、数学这几个字,是常量,我们不用去考虑,那剩下的也就是最关键的,我们去求三科的成绩就可以了。
让我们再看一下escore表,当指针移到某一行数据时,当type=e时,我们就取到score,加到第三列上,那第五列和第七列就加0,也就是sum(decode(type,’e’,score,0)),其他列类似,这样group
by时用到的聚合函数还有decode结合在一起使用,就可以完成我们的要求了,sql写出来时这样的:
- 字符串组合的多行转一列,例如我有一张测试表如下:
我想根据id分组,将每一行的name连接起来,如下图是我想要的结果:
这种行转列不是真正意义的行转列,是多行数据的值拼接后显示到一列上,那这种情况怎么处理呢,首先分析一下:多行id相同的值转换成一行,一般情况下需要用到group by,但是对于字符串,oracle中没有一个聚合函数适合用到此处的字符串连接,那该怎么办呢?
在oracle中,有sys_connect_by_path(field_name,concat_value)函数,可以通过connect by来依次连接每一行的数据,connect by 的语法是这样的:
start with field1=1--以当前表达式返回true的行开始
connect by prior field2=field3--通过当前行查找下一行,也就是说某一行数据的field3字段等于当前行的field2,那就把这行数据作为下一行
有了这个思路,我们就可以用connect by 通过使用sys_connect_by_path(field_name,concat_value)这个函数,并且根据id分组,将字符串连接在一起,然后通过max聚合函数,选出每组最长的字符转就可以了,那剩下的也就是最关键的问题就是我怎样去使用connect by,通过当前行找到下一行呢?充分发散一下你的思维,看一下如下结果集:
那我下一步用如下思路使用connect by将所要的结果查询上来:
start with lg is null--以lg为null的行作为起始行
connect by prior rn=lg and prior id=id --当前行与其他行比较,满足这个条件的就作为下一行数据
总的查询结果如下:
其实怎么使用connect by 方法很多,例如如下sql也能完成: