准备测试数据
drop table job_history;
create table job_history(
employee_id number not null,
start_date date,
end_date date,
ename varchar2(30),
job varchar2(30),
job_id number
);
delete from job_history;
insert into job_history values(107,to_date('2014-07-07','yyyy-mm-dd'),to_date('2015-07-07','yyyy-mm-dd'),'polly','sales',1);
insert into job_history values(107,to_date('2015-07-07','yyyy-mm-dd'),to_date('2018-07-07','yyyy-mm-dd'),'polly','finance',2);
insert into job_history values(104,to_date('2014-06-07','yyyy-mm-dd'),to_date('2015-06-07','yyyy-mm-dd'),'hanmeimei','secrectory',3);
insert into job_history values(104,to_date('2015-06-07','yyyy-mm-dd'),to_date('2017-06-07','yyyy-mm-dd'),'hanmeimei','finance',2);
insert into job_history values(105,to_date('1985-06-07','yyyy-mm-dd'),to_date('1987-02-07','yyyy-mm-dd'),'litao','engineer',4);
insert into job_history values(105,to_date('1987-06-07','yyyy-mm-dd'),to_date('2000-02-07','yyyy-mm-dd'),'litao','operations person',5);
insert into job_history values(4,to_date('2003-06-07','yyyy-mm-dd'),to_date('2006-04-07','yyyy-mm-dd'),'lilei','manager',6);
insert into job_history values(11,to_date('2006-08-07','yyyy-mm-dd'),to_date('2009-04-07','yyyy-mm-dd'),'lilei','ceo',7);
insert into job_history values(5,to_date('1999-01-07','yyyy-mm-dd'),to_date('1999-08-07','yyyy-mm-dd'),'uncle wang','cto',8);
insert into job_history values(12,to_date('2009-05-07','yyyy-mm-dd'),to_date('2010-08-07','yyyy-mm-dd'),'uncle wang','sales',9);
insert into job_history values(6,to_date('2001-07-07','yyyy-mm-dd'),to_date('2005-06-07','yyyy-mm-dd'),'jim','police',10);
insert into job_history values(13,to_date('2005-07-07','yyyy-mm-dd'),to_date('2009-10-07','yyyy-mm-dd'),'jim','sales',1);
insert into job_history values(7,to_date('2004-09-07','yyyy-mm-dd'),to_date('2008-06-07','yyyy-mm-dd'),'green','waiter',11);
insert into job_history values(14,to_date('2008-09-07','yyyy-mm-dd'),to_date('2015-06-07','yyyy-mm-dd'),'green','waiter',11);
insert into job_history values(1, to_date('2016-02-03', 'yyyy-mm-dd'), to_date('2016-09-03', 'yyyy-mm-dd'), 'lilei','reporter',12);
insert into job_history values(105, to_date('2016-02-03', 'yyyy-mm-dd'), to_date('2016-09-03', 'yyyy-mm-dd'), 'litao', 'reporter',12);
insert into job_history values (104, to_date('2016-02-03', 'yyyy-mm-dd'), to_date('2016-09-03', 'yyyy-mm-dd'),'hanmeimei', 'reporter',12);
insert into job_history values (104, to_date('2016-11-03', 'yyyy-mm-dd'), to_date('2017-10-03', 'yyyy-mm-dd'), 'hanmeimei', 'model',13);
列出干过多种岗位的员工
--列出干过多种岗位的员工
--desc job_history;
--向表中添加主键约束
--alter table job_history add constraint SYS_C0010295 primary key(employee_id);
--查找表中主键名称得job_history表中的主键名为SYS_C0010295
--SELECT * from user_cons_columns;
--alter table job_history drop constraint SYS_C0010295;
--alter table job_history modify (employee_id null);
select employee_id,count(*) job_ct
from job_history
group by employee_id
having count(*) > 1;
上面的列子是错的,为了了解提问者的真正业务,应该问清楚业务的方式面面,而不是用一个历史记录表统计。
- 在查询中将员工将当前的工作岗位列入计数还是只才考虑当前工作岗位以外的岗位?
- 能满足需求的数据放在哪儿,即在一张表还是多张表数据中?
- 数据模型是什么的,如果有的话,我能否到到一份数据字典或实体关系图(entity relationship diagram ERD)的副本?
- 结果集有预期的结果尺寸吗?
- 数据是何存储的?
- 这个查询执行的频率如何?
其实上面是用工作历史表来查询的,并不准确。
重写的工作位查询
select employee_id, count(*) job_ct
from (select e.employee_id, e.job_id
from employees e
union all
select j.employee_id, j.job_id from job_history j)
group by employee_id
having count(*) > 1;
数据的问题
获得完成任务的业务,接下来考虑的是如何来发现数据的存储方式呢?首先试着试着像优化器一样思考问题。优化器需要统计信息和实例参数值来计算计划。
- 为了得到所需的全部数据都需要那些表?
- 其中有表是分区的吗?如果有,分区是如何定义的呢?
- 每张表都有那些列?
- 每张表的索引都有那些?
- 每张表及其中的列和索引的统计信息都是什么?
- 某些列上有直方图信息吗?
统计信息为优化器描绘出了各种访问和联结数据的方法是如何实现的蓝图。
得到统计信息后,你可以使用这些信息来提问,以及回答关于你期望优化器对你的SQL做什么的问题。如索引统计信息叫聚族因子,这个统计信息帮助优化器计算将会访问多少个数据块。基本上聚族因子与表中的数据块数目越接近,使用这个索引时所需要访问数据块数目的估计值就越少,聚族因子与表中的数据行数目越接近,所需要访问的数据块数目的估计值就越大。访问的数据块数目越少,使用这个索引的成本就越小,优化器也就越可能为执行计划选择这个索引。
--索引聚族因子
select t.TABLE_NAME || '.' || i.index_name idx_name,
i.clustering_factor,
t.BLOCKS,
t.NUM_ROWS
from user_indexes i, user_tables t
where i.table_name = t.TABLE_NAME
and t.TABLE_NAME = 'SALES'
order by t.TABLE_NAME, i.index_name;
除了统计信息以外,可以实际执行一次查询来得到数据的相关情况以及在这个表中需要访问和返回多少行数据。不管语句有多复杂,你可以做优化器会做的事情并将语句分解为单表访问。对于所包括的每张表,只需执行一个或多个查询来计算并查看使用SQL语句所要使用的筛选条件会返回多少行数据。
建立逻辑表达式
通常对于同一个谓语逻辑可能多种表述方法。学习一些好的布尔逻辑技术,从而可以不用只依赖于某一种方法来表达条件逻辑。布尔逻辑表达式总是可以做出最高效的执行计划运算选择,一定要对其他替代方法进行彻低的测试,但最好还是要知道如何构造不同的替代语句,这样可以不必局限于某一种方法。
--表达条件逻辑的不同方法
variable empno number;
variable getall number;
exec :empno := 2;
exec :getall :=1;
select /* opt1 */
empno, ename
from emp
where empno = case
when :getall <> 1 then
:empno
else
empno
end;
--使用绑定变量创建where子句
variable getall number;
variable empno number;
exec :empno := 1;
exec :getall := 0;
select /* opt2 */ empno,ename from emp
where empno = nvl(:empno,empno);
select /* opt3 */ empno,ename from emp
where (:empno is null) or (:empno = empno);
在上面的例子中,布尔逻辑不能为你提供最好的执行计划。因此知道几种谓语构造的可替代方式以获得可能的最佳执行计划是很好的。
--使用绑定变量创建where子句
variable getall number;
variable empno number;
exec :empno := 1;
exec :getall := 0;
--使用用union all处理条件逻辑
select /* opt5 */ empno,ename from emp
where :empno is null
union all
select empno,ename from emp
where :empno = empno;
与串联执行计划类似,在上面的例子中,一个由两部分单独的子计划合并到一起来得到结果的执行计划。如果变量为空值,就将进行全扫描,并且会返回所有行。当绑定变量不为空时,将会进行索引唯一扫描并仅返回所需要那一行。
通常优化器使用and条件时能够更好的选择执行计划,这是因为or条件的使用意味着基于表达式的求解方式可能会使用两种不同的运算。而在and条件中,更可能只有一个选择,或者至少只会考虑本质上不是相对立的选择。
如果在一个很大的代码体内部写SQL代码,如果存储过程,尽量在编码语言中使用条件结构而不是将逻辑放在SQL中。sql语句越简单,在语句中需要直接进行处理的条件越少,优化器确定最优执行计划时的复杂性也就是越小。
根据业务,写出功能上正确并且性能最优的sql,我们要对自己的代码多了解业务的背景,涉及的表,字段,索引,加深对数据的理解,有助于帮助我们写出高质量高性能的SQL。问好问题的能力是一个非常具有智慧的习惯,必须长时间才能养成。