ORACLE Advanced SQL
这篇文档对深入研究Oracle有很大的用处。下面分别从以下几个方面介绍。
Transaction Management,joins,Subquerys,Optimizer,Indexs, Enhancement to other SQL operations
如果您身边就有Oracle系统,那么您也可以通过运行本篇文章的例子来做一些试验。
l 事务处理(Transaction Management):
事务的四大特性原子性、隔离性、独立性,持续性。仅仅通过字面理解理解事务处理可能不是太直观。那么,我们可以借用Jim Gray的描述来理解——“ 事务概念是分布式计算的一个重要抽象。在某一个层次实现了事务,那么所有跟高层次上会有一个简化的失败语义(要么全做,要么全不做),并且错误处理也会简单得多。 ”
在Oracle中通过设置ISOLATION LEVEL来避免几乎所有数据库都可能遇到的问题——脏读(一个事务读取了另一个事务写的但是尚未提交的数据)、不一致分析(一个事务前后读取了不同的数据)和幻象数据读取(例如,我要执行一个事务查询,该事务查询的任务是查询13:00这个时间点的数据,但是这个事务执行时间可能需要一个小时,但是在这期间由于还有正常的数据操作。所以,当我在执行完事务查询时可能得到的数据就是14:00的数据了,我们称这种现象为幻象读取)。
我们知道标准的SQL99提出的事务隔离级别有四种,分别是READ UNCOMMITED、READ COMMITED、REPEATABLE READ、SERIALIZABLE。ORACLE分别实现了READ COMMITED和SERIALIZABLE。从这一点当中我们可以看出,ORACLE默认是避免脏读的(当然这也不一定如果在执行完DML语句完以后如果没有做COMMIT操作可能也会出现脏读的情况。但是,我还没有遇到这种情况,读者可以自己试验。)例句 SET TRANSACTION ISOLATION LEVEL [READ COMMITED| SERIALIZABLE]。READ COMMITED可以避免脏读,这也是ORACLE的默认选项。但是,它不可避免我们提出的不一致分析和幻象读取的问题。那么我们可以将其隔离级别设置为SERIALIZABLE这样我们就可以避免不一致分析和幻象读取的问题。那么,既然SERIALIZABLE级别这么好,那么我们是不是任何事务都要设置呢?答案是否定的,我们知道世界万物都是有其有利的一面就一定有其不利的一面。那么它的不利一面我们可以通过分析SERIALIZABLE的实现机制来看看。
从图中可以看出我们要实现SERIALIZABLE需要在系统中建立一个UNDO表空间来存放已经过时的数据。我们可以根据我们最大执行事务的执行时间来估算一个UNDO段的大小。显然,我们要是实现SERIALIZABLE就必须要另外的空间来建立UNDO表空间。也就是说我们要实现SERIALIZABLE是通过空间来换取避免不一致分析和幻象读取的。
l Joins操作
在叙述下面的内容之前先介绍一下我的机器环境:
硬件环境:P4 2.8G,MEMORY 526M,80G硬盘。
软件环境:Windows 2000,ORACLE 9.2.0.1
首先您要建立一个有足够量数据的表,我建立一个有100万行以上的数据的表
建表sql语句如下:
DROP SEQUENCE SEQ_EMP
/
CREATE SEQUENCE SEQ_EMP START WITH 1000000
/
DROP TABLE EMPLOYEE
/
CREATE TABLE EMPLOYEE
(EMPNO NUMBER(8),
ENAME VARCHAR2(10),
SAL NUMBER(5),
COMM NUMBER(5),
DEGREE NUMBER(1),
HIREDATE DATE,
DEPTNO NUMBER(2),
ID_NO VARCHAR2(18),
BIRTHDAY DATE,
CONSTRAINT PK_EMPLOYEE PRIMARY KEY(EMPNO)
)
/
CREATE OR REPLACE TRIGGER TRI_INS_EMPLOYEE
BEFORE INSERT ON EMPLOYEE
FOR EACH ROW
DECLARE
V_EN EMPLOYEE.EMPNO%TYPE;
BEGIN
V_EN:=:NEW.EMPNO;
:NEW.COMM:=CEIL(:NEW.EMPNO/500000)*1000;
:NEW.SAL:=(4-MOD(:NEW.EMPNO,4))*1000;
:NEW.DEGREE:=CASE WHEN MOD(:NEW.EMPNO,4)=0 THEN NULL
WHEN MOD(:NEW.EMPNO,10)<7 THEN 1
WHEN MOD(:NEW.EMPNO,10)<9 THEN 2
ELSE 3
END;
:NEW.DEPTNO:=CASE WHEN :NEW.EMPNO<1000000 THEN 10
WHEN :NEW.EMPNO<1500000 THEN 20
WHEN :NEW.EMPNO<2000000 THEN 30
ELSE 40
END;
:NEW.ID_NO:=REPLACE('21010119'||(7+MOD(V_EN,3))||MOD(V_EN,10)||TO_CHAR(1+MOD(V_EN,12),'00')||TO_CHAR(MOD(V_EN,20)+1,'00')||0||TO_CHAR(MOD(V_EN,100),'00')||MOD(V_EN,2),' ',NULL);
:NEW.ID_NO:=REPLACE(:NEW.ID_NO,' ',NULL);
:NEW.BIRTHDAY:=TO_DATE(SUBSTR(:NEW.ID_NO,7,8),'YYYYMMDD');
END;
/
INSERT INTO EMPLOYEE (EMPNO)
VALUES(SEQ_EMP.NEXTVAL)
/
/
INSERT INTO EMPLOYEE (EMPNO) (SELECT SEQ_EMP.NEXTVAL FROM EMPLOYEE)
/
在做好以上准备以后我们就可以进行我们以后的实验了。
在ORACLE中包括很多中连接方式EQUIJOINS、 SELF JOINS、 CARTESIAN PRODUCTS、 INNER JOINS、 OUTER JOINS、 ANTIJOINS, SEMIJOINS。
由于我的个人水平有限我不能全部介绍,我只能将自己理解的内容介绍个大家。
下面就仅仅介绍有关反连接的问题。
反连接(ANTIJOINS)通过 SET AUTOTRACE TRACE打开跟踪执行策略我们分别比较以下的语句。
============================================================
SELECT ENAME FROM EMP WHERE DEPTNO NOT IN(
SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');
SELECT ENAME FROM EMP WHERE NOT EXISTS(
SELECT 1 FROM DEPT WHERE DEPT.DEPTNO=EMP.DEPTNO AND LOC='NEW YORK');
我们可以发现,运用NOT IN 要比 NOT EXISTS要快一些但是有个比较大的问题,就是在上面的实验中我们假设EMP表和DEPT表中没有字段内容为NULL的数据。如果我们要查询的表中有为NULL的数据的时候,那么我们在运用NOT IN操作会发现查出的数据是不准确的情况。结论:在您为选择NOT IN 和 NOT EXISTS时而苦恼时请优先选择运用NOT EXISTS语句。
接下来是有关JOIN的一些实验。
下面我们做如下试验
比较JOIN与IN & 子查询以及EXISTS & 子查询 它们三者之间的执行效率
--12.04秒
SELECT * FROM EMPLOYEE A JOIN DEPT B ON A.DEPTNO=B.DEPTNO WHERE B.LOC='NEW YORK';
--4.02秒
SELECT * FROM EMPLOYEE WHERE DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');
--4.02秒
SELECT * FROM EMPLOYEE WHERE EXISTS (SELECT 1 FROM DEPT WHERE EMPLOYEE.DEPTNO=
DEPT.DEPTNO AND LOC='NEW YORK');
通过上面的实验我们可以得出运用 IN或者 EXISTS操作的效率要高于运用JOIN操作的语句。我们知道ORACLE在执行用户的查询请求是ORACLE会选择一些查询策略来完成操作,但是有时候ORACLE的选择是很愚蠢的。比如,我要做一个简单的连接查询的操作SELECT * FROM EMPLOYEE A JOIN EMP B ON A.EMPNO=B.EMPNO;通过跟踪执行策略我们会发现ORACLE可能选择一个很慢的策略。那么,遇到这种情况该如何处理呢?我们可以通过两种方式来处理。第一,我们可以为该查询添加注释让它执行我们要求的策略。比如我们可以这么写SELECT /*+ use_rule…(EMPLOYEE EMP) */* FROM EMPLOYEE A JOIN EMP B ON A.EMPNO=B.EMPNO;(use_rule您可以选择您需要的策略。例如HASH JOIN等其他的执行策略)。例如,我们为上面这个查询语句选择下面两种策略:
1. SELECT /*+USE_HASH(EMPLOYEE,DEPT)*/* FROM EMPLOYEE,DEPT WHERE EMPLOYEE.DEPTNO=DEPT.DEPTNO;
2. SELECT /*+USE_MERGE(EMP,DEPT)*/* FROM EMP JOIN DEPT ON EMP.DEPTNO=DEPT.DEPTNO;
第二,就是,为该表添加统计资料分析信息ANALYZE TABLE EMPLOYEE COMPUTE STATISTICS;这样我们在对该表查询时ORACLE就可以选择一个较优的执行策略了。
通过以上的实验我们可以得出以下结论:ORACLE系统在执行DML语句的时候可能不会选择其最优的执行策略,这需要我们通过为DML语句添加注释强迫ORACLE选择一个我们人为指定的执行策略或者通过为表、试图等添加统计资料分析信息来使ORACLE在执行DML时选择较优的执行策略。
l 子查询(Subquery)
这部分内容我们将会做一系列的实验,最后我们从实验的结果当中得出使用子查询的情况。我并不是要介绍如何使用子查询,而是通过使用子查询来分析ORACLE的执行策略等信息。
实验1:比较JOIN、IN以及EXISTS三种执行方式的执行时间
--12.04秒
SELECT * FROM EMPLOYEE A JOIN DEPT B ON A.DEPTNO=B.DEPTNO WHERE B.LOC='NEW YORK';
--4.02秒
SELECT * FROM EMPLOYEE WHERE DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE LOC='NEW YORK');
--4.02秒
SELECT * FROM EMPLOYEE WHERE EXISTS (SELECT 1 FROM DEPT WHERE EMPLOYEE.DEPTNO=
DEPT.DEPTNO AND LOC='NEW YORK');
通过实验1我们得出结论:使用EXISTS、IN语句要比JOIN语句执行效率高。
实验2:表较几个语句的查询效率
--27.07秒
SELECT * FROM EMPLOYEE
WHERE SAL>(SELECT AVG(SAL) FROM EMPLOYEE);
--27.03秒
SELECT * FROM EMPLOYEE A
WHERE SAL>
(SELECT AVG(SAL) FROM EMPLOYEE B WHERE A.DEPTNO=B.DEPTNO);
--39秒
SELECT * FROM EMPLOYEE A,
(SELECT DEPTNO,AVG(SAL) AS SAL1
FROM EMPLOYEE GROUP BY DEPTNO) B
WHERE A.DEPTNO=B.DEPTNO AND
A.SAL>B.SAL1 WHERE SAL>(SELECT AVG(SAL)
FROM EMPLOYEE B WHERE A.DEPTNO=B.DEPTNO);
--22.05秒
SELECT YY.EMPNO,YY.ENAME,YY.DEPTNO,YY.SAL,
YY.SAL-XX.SAL1 AS NEWSAL FROM EMPLOYEE YY,
(SELECT DEPTNO,AVG(SAL) AS SAL1 FROM EMPLOYEE
GROUP BY DEPTNO) XX
WHERE YY.DEPTNO=XX.DEPTNO AND YY.SAL>XX.SAL1;
--26.06秒
SELECT YY.EMPNO,YY.ENAME,YY.DEPTNO,YY.SAL,
YY.SAL-XX.SAL1 AS NEWSAL,XX.SAL1,XX.SAL2
FROM EMPLOYEE YY,
(SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2 FROM EMPLOYEE GROUP BY DEPTNO) XX
WHERE YY.DEPTNO=XX.DEPTNO AND YY.SAL>XX.SAL1;
通过实验2我们可以得出结论:
1. 在执行子查询尽可能将语句向连接的方式上靠。
2. 在子查询中可以将相关子查询转为连接的形式。
3. 在做子查询时select * 要比select 列名 的语句慢得多。
另外,在ORACLE中我们也可以运用WITH语句代替子查询以提高语句的可读性。下面是运用with语句的例子。
实验3:运用WITH语句
WITH
SUB_TABLE AS (
SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2 FROM EMPLOYEE GROUP BY DEPTNO)
SELECT A.EMPNO,A.ENAME,E.DEPTNO,A.SAL,A.SAL-SUB_TABLE.SAL11,
SUB_TABLE.SAL1,SUB_TABLE.SAL2
FROM EMPLOYEE A,SUB_TABLE WHERE
A.DEPTNO=SUB_TABLE.DEPTNO AND A.SAL>SUB_TABLE.SAL1;
WITH
SUB_TABLE AS (
SELECT DEPTNO,AVG(SAL) AS SAL1,MAX(SAL) AS SAL2
FROM EMPLOYEE GROUP BY DEPTNO)
SELECT *
FROM EMPLOYEE,SUB_TABLE WHERE
EMPLOYEE.DEPTNO=SUB_TABLE.DEPTNO AND EMPLOYEE.SAL>SUB_TABLE.SAL1;
l 索引(Index)
我们知道索引的使用可以提高查询速度,这是我以前对索引的理解。但是使用索引也有副作用。我们知道在ORACEL中查询一条记录时如果没有索引的情况下,它的执行方式如下所示
如果我要做查询 select C2 from 表 where C2=6 那么在ORACLE中系统会从最后一条扫描一直将表整个扫描一遍以后,这个查询动作才算完成。从中我们可以看出没有索引的表的记录是无序存放的。相反如果我们对这个表在列C2建立一个索引以后它的查询执行如下所示:
我们可以看到在查询是系统如果选用该索引的话那么ORACLE将会查找一些有序的数据,那么我们的查询速度将会大大地提高。
上面我们描述的是查询一列数据时的情况,那么如果查询所有数据呢,请看下图所示
如果我们带上索引查询,ORACLE首先会找到索引然后找到在基表中记录的位置。显然这样比直接在表中查询要慢。但是这个结论对不对呢?下面将会做一些实验说明这个问题。
在进行以下的实验值前首先,您需要作如下准备:
在EMPLOYEE表的SAL列上建立一个索引
CREATE INDEX IND_EMPLOYEE_SAL ON EMPLOYEE(SAL);
在EMPLOYEE表的SAL,DEGREE两个列上建立一个联合索引
CREATE INDEX IND_EMPLOYEE_SALandDEGREE
ON EMPLOYEE(SAL,DEGREE);
实验1:使用索引与不使用索引
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM
EMPLOYEE WHERE SAL=1000;
--13.03秒
SELECT /*+NO_INDEX(EMPLOYEE)*/
* FROM
EMPLOYEE WHERE SAL=1000;
利用索引查询的时间的数据由于我的粗心大意弄丢了,但是我记得运用索引的查询要比没有运用索引的查询要慢一些。
实验2:单列索引与多列索引之间的区别
//////////////////////////////////////////////////
////单列索引的情况
--13.04秒 USE INDEX IND_EMPLOYEE_SAL
SELECT * FROM EMPLOYEE WHERE SAL=1000;
--13.04秒
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM
EMPLOYEE WHERE SAL=1000;
--19秒
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM
EMPLOYEE WHERE SAL=1000;
//////////////////////////////////////////////////
////单列索引的情况
--22秒 USE TABLE ACCESS
SELECT * FROM EMPLOYEE WHERE DEGREE=1;
--22秒 USE TABLE ACCESS
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM
EMPLOYEE WHERE DEGREE=1;
--29秒
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM
EMPLOYEE WHERE DEGREE=1;
//////////////////////////////////////////////////
////多列索引的情况
--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT * FROM EMPLOYEE WHERE SAL=1000 AND DEGREE=1;
--9秒 USE INDEX IND_EMPLOYEE_SAL
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM
EMPLOYEE WHERE SAL=1000 AND DEGREE=1;
--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM
EMPLOYEE WHERE SAL=1000 AND DEGREE=1;
//////////////////////////////////////////////////
////多列索引的情况
--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT * FROM EMPLOYEE WHERE DEGREE=1 AND SAL=1000;
--9秒 USE INDEX IND_EMPLOYEE_SAL
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM
EMPLOYEE WHERE DEGREE=1 AND SAL=1000;
--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM
EMPLOYEE WHERE DEGREE=1 AND SAL=1000;
//////////////////////////////////////////////////
////多列索引的情况
--3.4秒 USE INDEX IND_EMPLOYEE_SAL
SELECT * FROM EMPLOYEE WHERE SAL=1000 AND COMM=2000;
--3.4秒 USE INDEX IND_EMPLOYEE_SAL
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM
EMPLOYEE WHERE SAL=1000 AND COMM=2000;
--8秒 USE INDEX IND_EMPLOYEE_SALandDEGREE
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SALandDEGREE)*/
* FROM
EMPLOYEE WHERE SAL=1000 AND COMM=2000;
实验3:CLUSTER
CREATE CLUSTER DD_DDMX (ID NUMBER(5))
TABLESPACE users ;
CREATE TABLE DD
(ID NUMBER(5) PRIMARY KEY,
DDTIME DATE)
CLUSTER DD_DDMX(ID);
CREATE TABLE DDMX
(ID NUMBER(5),
PROID VARCHAR2(20),
PRO VARCHAR2(30))
CLUSTER DD_DDMX(ID);
CREATE INDEX DD_DDMX_index
ON CLUSTER DD_DDMX
TABLESPACE indx;
实验4:BITMAP INDEX
CREATE BITMAP INDEX INDBIT_EMPLOYEE_SAL ON EMPLOYEE(SAL)
TABLESPACE INDX;
--13.04秒
SELECT /*+INDEX (EMPLOYEE IND_EMPLOYEE_SAL)*/
* FROM
EMPLOYEE WHERE SAL=1000;
--12.07秒 USE INDBIT_EMPLOYEE_SAL
SELECT /*+INDEX (EMPLOYEE INDBIT_EMPLOYEE_SAL)*/
* FROM
EMPLOYEE WHERE SAL=1000;
实验5:分区索引
分区索引的原理如下所示:
我们知道在磁道在磁盘上寻址的I/O操作的开销是相当大的,如果我们建立了分区索引将其索引放在不同的磁盘上那么可以大大节省I/0开销提高我们的查询速度。
ALTER TABLE EMP
ADD CONSTRAINT PK_EMP
PRIMARY KEY(EMPNO) USEING INDEX TABLESPACE INDX;
ALTER TABLE EMPLOYEE
DROP CONSTRAINT PK_EMPLOYEE;
ALTER TABLE EMPLOYEE
ADD CONSTRAINT PK_EMPLOYEE
PRIMARY KEY(EMPNO) USING INDEX TABLESPACE INDX;
CREATE INDEX IND_EMPLOYEE_BIRTHDAY
ON EMPLOYEE(BIRTHDAY)
GLOBAL PARTITION BY RANGE(BIRTHDAY)
(PARTITION P1 VALUES LESS THAN (DATE '1980-01-01') TABLESPACE USERS,
PARTITION P2 VALUES LESS THAN (DATE '1990-01-01') TABLESPACE INDX,
PARTITION P3 VALUES LESS THAN (DATE '2000-01-01') TABLESPACE USERS,
PARTITION P4 VALUES LESS THAN(MAXVALUE) TABLESPACE INDX);
CREATE TABLE wage
( empno NUMBER,
year_month INT NOT NULL,
opdate DATE)
PARTITION BY RANGE (year_month)
( PARTITION wage_q1 VALUES LESS THAN (199701)
TABLESPACE users,
PARTITION wage_q2 VALUES LESS THAN (199702)
TABLESPACE users,
PARTITION wage_q3 VALUES LESS THAN (199703)
TABLESPACE users,
PARTITION sales_q4 VALUES LESS THAN (199704)
TABLESPACE users);
-- Local Partitioned Index
CREATE INDEX IND_WAGE(year_month)
LOCAL
(PARTITION P1,
PARTITION P2,
PARTITION P3,
PARTITION P4);
以上是我们关于索引的一些实验信息,通过该实验我们可以得出以下结论:
1. 索引是为了提高查询速度,排序数据。
2. 索引不见得总能提高速度。
3. 查询结果占总记录数越少越可以用索引。
4. 分区索引 将数据分散到多个物理位置上来提高其IO的功能。
5. 全表扫描有时性能也不错。
6. 在统计资料不完整时未必选择正确的索引。
7. 有时候添加索引可能会降低速度。
8. 索引适合于结果行占所有行的比很小的时候运用 经验值为比为5%左右为好,但是还可能还有许多其他情况影响查询速度。
9. 值是唯一的时候运用索引可以提高查询速度。
10. 当统计资料不完整的时候,查询速度会很慢。可以通过对索引进行统计分析来调整。
11. where条件中列的顺序不影响其查询速度。
12. 多列索引中的后面的列最好不要运用索引。
13. 多列索引中其列的顺序是不同的。不同的顺序可能造成查询速度不一样。
14. 多列索引中第一列是有顺序的,但是其后面的列是没有顺序。
15. where条件查询的列中只要有索引,那么oracle会选择运用索引。
16. 当查询的结果大于所有行的记录数的10%时,那么最好不要运用索引。
17. 小表用索引反而会降低速度。
18. 索引需要DBA经常删除重新建立,因为索引结构变化过多可能造成以后的查询变慢。
19. DML语句会影响索引结构大的变化,所以经常作DML语句的表可以考虑不用索引。
20. CLUSTER索引仅仅用于只读的的表多用于cluster表。
21. 维图索引多用于行多但是值的类型很少的情况。
22. 表和索引要放在两个不同的表空间上。
l Enhancement to other SQL operations
下面的内容是介绍一些有关ORACLE技术的一些比较杂的内容。
一、层次查询
在同一个表中的不同记录有着直接或者间接的关系。
例如,我们查询EMP表的信息如下:
从图中我们可以看到ENAME是’SMITH’的领导是编号为7902的人。而7902的领导是编号为7566的人。7566的领导是编号为7839的人。那么这样的关系我们就可以通过运用层次查询就可以查询出来。
层次查询实验
SELECT * FROM EMP
CONNECT BY PRIOR MGR=EMPNO
START WITH ENAME='SMITH';
SELECT * FROM EMP
CONNECT BY PRIOR EMPNO=MGR
START WITH ENAME='SMITH';
SELECT LEVEL,ENAME FROM EMP
CONNECT BY PRIOR EMPNO=MGR
START WITH ENAME='SMITH';
SELECT MID,PARENTID,AMOUNT,SYS_CONNECT_BY_PATH(MID,'/') PATH
FROM MATERIAL
WHERE STOCK=1
CONNECT BY PRIOR MID=PARENTID
START WITH MID='1001';
二、分组查询
就是我们平时运用的group语句.
分组查询实验
SELECT DEPTNO,JOB,COUNT(*)
FROM EMP
GROUP BY ROLLUP(DEPTNO,JOB);
SELECT DEPTNO,JOB,COUNT(*)
FROM EMP
GROUP BY cube(DEPTNO,JOB);
SELECT DEPTNO,JOB,TO_CHAR(HIREDATE,'YYYY'),COUNT(*)
FROM EMP
GROUP BY GROUPING SETS((DEPTNO,JOB),
(DEPTNO,TO_CHAR(HIREDATE,'YYYY')),());
三、并行执行的SQL语句
并行执行的SQL语句实验
--19.09秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 1);
SELECT * FROM EMPLOYEE WHERE SAL=1000;
--18秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 2);
SELECT * FROM EMPLOYEE WHERE SAL=1000;
--19秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 4);
SELECT * FROM EMPLOYEE WHERE SAL=1000;
--20.3秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 6);
SELECT * FROM EMPLOYEE WHERE SAL=1000;
--19秒
ALTER TABLE EMPLOYEE PARALLEL (DEGREE 10);
SELECT * FROM EMPLOYEE WHERE SAL=1000;
四、实体化视图
在进行以下的实验之前,我们需要作如下准备:
建立一个普通视图:
CREATE VIEW V_EMPLOYEE
AS SELECT SUM(SAL) AS C FROM EMPLOYEE;
建立实体化视图:
CREATE MATERIALIZED VIEW 名字 AS 查询;
CREATE MATERIALIZED VIEW V_M_EMPLOYEE
AS SELECT SUM(COMM) AS C FROM EMPLOYEE;
实体化视图与普通视图的比较
--20.04秒
SELECT * FROM V_EMP;
--0.01秒
SELECT * FROM V_M_EMPLOYEE;
通过以上的实验,我们可以得出结论:实体化视图不包含新的数据,查询速度很快。
如果需要实体化视图包含新的数据我们可以通过
手工刷新:
EXEC DBMS_MVIEW.REFRESH('V_M_EMPLOYEE','CF'); CF -- 完全快速刷新
自动刷新:
//每个表可以创建一个实体化视图日志。
CREATE MATERIALIZED VIEW LOG ON 表名
WITH(列名列表),
ROWID INCLUDING NEW VALUES;
//创建自动刷新的实体化视图
CREATE MATERIALIZED VIEW 名字
BUILD IMMEDIATE
〔REFRESH FAST||REFRESH COMPLETE〕 ON COMMIT
--REFRESH FAST 仅仅支持Insert语句的刷新
AS ...
运用REFRESH COMPLETE 适合于update,delete操作较少的表。并且试验发现运用
REFRESH COMPLETE时COMMIT操作会很慢!
五、查询重写技术(QUERY REWRITE)
表面上看是在查询表,但是oracle实际上是去查询实体化视图的技术。
在下面的试验中我们也可以看出实际上无论是查询表还是视图oracle都会转向查询实体化视图 MV_EMP。
对于反复执行的汇总查询存放起来、节省查询时间,多用于汇总的计算。
//创建查询重写技术
CREATE MATERIALIZED VIEW 名字
BUILD IMMEDIATE
〔REFRESH FAST||REFRESH COMPLETE〕 ON COMMIT
--REFRESH FAST 仅仅支持Insert语句的刷新
ENABLE QUERY REWRITE
AS ...
运用查询重写技术的例子
CREATE MATERIALIZED VIEW MV_EMP
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
ENABLE QUERY REWRITE
AS SELECT SUM(COMM) AS C FROM EMPLOYEE;
ALTER SESSION SET QUERY_REWRITE_ENABLED=TRUE;
--0.00秒
-- EXPLAN: TABLE ACCESS (FULL) OF 'MV_EMP'
SELECT SUM(COMM) FROM EMPLOYEE;
--0.00秒
--EXPLAN: TABLE ACCESS (FULL) OF 'MV_EMP'
SELECT * FROM V_MEP;
经过手工刷新以后查询速度会变慢!
六、分布式技术
分布式技术包括分布式数据库、DB LINK、分布式查询以及分布式事务管理。这里我们仅仅介绍有关DB LINK的内容。
DB LINK的实验
GRANT CREATE DATABALSE LINK TO SCOTT;
CONN SCOTT/TIGER
CREATE DATABASLE LINK OEM_LINK
CONNECT TO SCOTT IDENTIFIED BY TIGER
USING 'AAA';
SELECT ENAME FROM EMP@OEM_LINK
通过以上实验我们得出结论:如果在执行分布式事务的时候那么网络一定要保持正常,如果网络断开就会发生死锁的情况。这时要进行在服务端和客户端分别将锁删除即可解决。
七、DML语句
INSERT INTO ALL
INTO 表1 VALUES(列名列表)
...
INTO 表2 VALUES(列名列表)
子查询;
DML语句的实验
CREATE TABLE TEST_DWRS
(DEPTNO NUMBER(2),
RENSHU NUMBER(10)
);
CREATE TABLE TEST_DWGZ
(DEPTNO NUMBER(2),
AVGSAL NUMBER(7,2)
);
INSERT ALL
INTO TEST_DWRS VALUES (DEPTNO,RENSHU)
INTO TEST_DWGZ VALUES (DEPTNO,AVGSAL)
SELECT DEPTNO DEPT,COUNT(*) REN_SHU,AVG(SAL) AVG_SAL
FROM EMP GROUP BY DEPTNO;
INSERT ALL
WHEN 条件1 THEN
INTO 表名1 VALUES(列名列表)
...
WHEN 条件2 THEN
INTO 表名2 VALUES(列名列表)
子查询;
INSERT ALL
WHEN REN_SHU>1000 THEN
INTO TEST_DWRS VALUES(DEPTNO,RENSHU)
WHEN AVG_SAL>2000 THEN
INTO TEST_DWGZ VALUES(DEPTNO,AVGSAL)
SELECT DEPTNO,COUNT(*) REN_SHU,AVG(SAL) AVG_SAL
FROM EMP GROUP BY DEPTNO;
八、外部表(EXTERNAL TABLES)
外部表是我们应用中可能会常常碰到的问题之一。例如,我需要将文本文件的数据导入到ORACLE数据库中我们就可以利用此项技术。
假设外部文件有如下信息:
我们可以将此信息转为以逗号分割的一些信息存为test.txt文本文件。
然后我们在ORACLE中建立一个订单表BILL(BILL_ID 订单编号,BILL_D订单日期)
以及订单明细表BILL_MX(BILL_ID 订单编号,P 产品编号,PAMOUNT 产品数量)。
我们下面的实验就是通过将外部的文件信息导入到BILL表和BILL_MX表中去。
首先简单介绍一下创建外部表的方法:
第一步,创建一个DIRECTORY对象
CREATE DIRECTORY 名字 AS '路径'
CREATE DIRECTORY test_dir AS 'D:/1';
第二步,创建外部表
…
外部表的实验
//创建外部表
CREATE TABLE BILL_EXTERNAL
(BILL_ID VARCHAR2(8),
BILL_D DATE,
P1 VARCHAR2(10),
P1_AMOUNT VARCHAR2(10),
P2 VARCHAR2(10),
P2_AMOUNT VARCHAR2(10),
P3 VARCHAR2(10),
P3_AMOUNT VARCHAR2(10))
ORGANIZATION EXTERNAL
(TYPE ORACLE_LOADER
DEFAULT DIRECTORY test_dir
ACCESS PARAMETERS
(RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY ','
(BILL_ID CHAR,
BILL_D CHAR DATE_FORMAT
DATE MASK "YYYYMMDD",
P1 CHAR,
P1_AMOUNT CHAR,
P2 CHAR,
P2_AMOUNT CHAR,
P3 CHAR,
P3_AMOUNT CHAR
)
)
LOCATION ('TEST.TXT')
);
//导入数据
INSERT ALL
WHEN BILL_ID<>0 THEN
INTO BILL VALUES(BILL_ID,BILL_D)
WHEN P1<>0 THEN
INTO BILL_MX VALUES(BILL_ID,P1,P1_AMOUNT)
WHEN P2<>0 THEN
INTO BILL_MX VALUES(BILL_ID,P2,P2_AMOUNT)
WHEN P3<>0 THEN
INTO BILL_MX VALUES(BILL_ID,P3,P3_AMOUNT)
SELECT * FROM BILL_EXTERNAL;
九、日期类型
日期类型的实验
SELECT SYSDATE + TO_YMINTERVAL('01-02') FROM DUAL;
SELECT SYSTIMESTAMP + TO_DSINTERVAL('01 01:02:01') FROM DUAL;
SELECT SYSTIMESTAMP + TO_YMINTERVAL('10-3') +
TO_DSINTERVAL('05 07:00:00') FROM DUAL;
十、自定义数据类型
自定义数据类型我们将会简单地介绍有关记录和集合的内容。
记录类型的实验
ORACLE 对象可以理解为JAVA中的类的概念。
CREATE TYPE T_REC AS OBJECT(
A NUMBER,
B NUMBER
);
CREATE TABLE RTEST(A NUMBER,
B NUMBER,
C T_REC);
INSERT INTO RTEST VALUES(1,2,T_REC(10,20));
CREATE TYPE EMP_INFORMATION AS OBJECT(
ADDR VARCHAR2(50),
EMAIL VARCHAR2(50),
PHONE VARCHAR2(11));
CREATE TABLE EMP1(
EMPID VARCHAR2(10),
EMPNAME VARCHAR2(20),
INFO EMP_INFORMATION);
INSERT INTO EMP1(EMPID,EMPNAME,INFO)
SELECT '0001','SHI',
EMP_INFORMATION('DALIAN','[email protected]','240560560')
FROM DUAL;
集合类型的实验
CREATE TYPE 类型名 AS TABLE OF 元素类型名
CREATE TABLE...
(....
C1 (NESTED TABLE的类型名),
...
)
CREATE TYPE NTA AS TABLE OF NUMBER;
CREATE TABLE NTEST (A NUMBER,
B NUMBER,
C NTA)
NESTED TABLE C STORE AS NTT;
INSERT INTO NTEST VALUES(1,2,NTA(100,200,300));
实验:创建一个工资表 职工编号,工资,补贴其中补贴是嵌套表,每个职工的都可以有多种补贴数量不定
CREATE TYPE BT_VALUE AS OBJECT(BT_NAME VARCHAR2(20),
BT_JE NUMBER(4));
CREATE TYPE BT_LIST AS TABLE OF BT_VALUE;
CREATE TABLE GZB(EMPNO NUMBER(4),SAL NUMBER(4), BT BT_LIST)
NESTED TABLE BT STORE AS BT_TAB;
INSERT INTO GZB VALUES(1001,1000,BT_LIST(BT_VALUE('JTFEE',500),
BT_VALUE('TELFEE',200)
)
)
UPDATE TABLE(SELECT BT FROM GZB WHERE EMPNO=1001) SET BT_JE=150 WHERE BT_NAME='JTFEE';
SELECT EMPNO,SAL,(SELECT SUM(BT_JE) FROM TABLE(BT)) BT_JE
FROM GZB;
至此,有关的ORACLE Advanced SQL的内容就介绍完了。通过,这篇文档的总结我也体会到数据库的学习与应用我们还有很长的路要走。