OCA/OCP认证考试指南全册(第3版) Oracle Database 12c(1Z0-061,1Z0-062,1Z0-063) (计算机与信息)
上QQ阅读APP看书,第一时间看更新

5.4 创建简单的表

可用多种方法将表保存在数据库中,最简单的是堆表。堆表包含了随机排列的可变长度的行。输入行的顺序和排列行的顺序之间可能有某种关联,但这只是巧合而已。更高级的表结构,如下所示,可能对行强制顺序和分组,或者要求随机分布:

索引组织的表 按索引键的顺序存储行。

索引群集 可以反规范化父子关系表,这样来自不同表的相关行就可以存储在一起。

哈希群集 要求随机分布行,这样可以打破依据条目序列的排序。

分区表 以单独的物理结构(分区)存储行,依据列的值分配行。

使用更高级的表结构对SQL没有影响。对用这些选项定义的表执行的每个SQL语句都会返回相同的结果,就像表是标准的堆表一样,因此使用这些特性不会影响代码。但当它们的使用对于编程人员而言是透明时,它们的确大大提高了性能。

5.4.1 使用列规范创建表

要创建标准的堆表,可以使用下面的语法:

        CREATE TABLE [schema.]table [ORGANIZATION HEAP]
        (column datatype [DEFAULT expression]
        [, column datatype [DEFAULT expression]...);

至少指定表名(如果没有指定其他人的,就会在自己的模式中创建它)和具有某个数据类型的一列。很少有开发人员指定ORGANIZATION HEAP,因为这是默认的,是行业标准的SQL。列定义中的DEFAULT关键字要求提供一个表达式,如果INSERT语句没有提供值,那么在插入行时,这个表达式会生成列的值。

分析下面的语句:

        create table scott.emp
        (empno number(4),
        ename varchar2(10),
        hiredate date default trunc(sysdate),
        sal number(7,2),
        comm number(7,2) default 0.03);

这会在SCOTT模式中创建一个名为EMP的表。用户SCOTT本身必须发出语句(这种情况下,就没必要命名模式),或者另一个用户可以发出它(如果允许他们在SCOTT模式中创建表)。下面逐列进行考察:

● EMPNO可以是4位数长,没有小数部分。如果在INSERT语句中包含小数,就会将它们舍入(向上舍入或者向下舍入)为最接近的整数。

● ENAME可以最多存储10个字符。

● HIREDATE可以接受任何日期,可能包含时间,但如果没有提供值,就会输入今天午夜的日期。

● SAL表示员工的薪水,可以接受数值,最多可达到7位。小数点左边最多可以有5位数字。如果超过两位的数字出现在小数点右边,就会舍入它们。

● COMM(表示佣金率)的默认值是0.03,如果INSERT语句没有包含这一列的值,就会输入它。

创建表之后,下面这些语句插入行和选择结果:

        insert into scott.emp (empno, ename, sal)
        values (1000, 'John', 1000.789);
        1 row created.

       select *
        from scott.emp;

           EMPNO    ENAME       HIREDATE         SAL        COMM
        ----------   ----------  --------- ---------- ----------
              1000   John        19-NOV-13    1000.79         .03

注意,INSERT语句中没有提到的列值由DEFAULT子句生成。如果表定义中没有定义这些子句,列就为NULL。还要注意对SAL值的舍入。

提示:

DEFAULT子句可能很有用,但它功能有限。不能使用子查询来生成默认值:只能指定字面值或者函数或者序列。

5.4.2 使用子查询创建表

除了创建一个空表,然后插入行之外(如前所述),还可以使用子查询通过其他表来创建表。这种方法可以创建表定义,并使用一条语句给表填充行。所有查询都可用作表结构和行的来源。其语法如下所示:

CREATE TABLE [schema.]table AS subquery;

所有查询都返回行的二维集合;这个结果存储为新的表。使用子查询创建表的简单示例是:

        create table employees_copy as
        select *
        from employees;

这条语句会创建表EMPLOYEES_COPY,它完全是EMPLOYEES表的副本,它们的定义和包含的行完全相同。列上的所有NOT NULL(非空)和CHECK(检查)约束也将应用于新的表,但所有PRIMARY KEY(主键)、UNIQUE(唯一值)或者FOREIGN KEY(外键)约束则不适用(稍后将讨论约束的概念)。这是因为这三类约束需要索引,这些索引可能不是必需的。

下面这个示例更复杂:

        create table emp_dept as
        select last_name ename, department_name dname, round(sysdate -hire_date)
            service
        from employees
        natural join departments
        order by dname, ename;

新表中的行是联接这两个源表的结果,两个选中的列已经改变名称。计算员工被雇佣天数的算术运算的结果将填充新的SERVICE列,并将会按指定的顺序插入这些行。后面的DML不会保持这个顺序,但假设是标准的HR模式数据,新表会如下所示:

        select *
        from emp_dept
        where rownum < 10;

       ENAME            DNAME               SERVICE
        --------------- --------------- ----------
        Gietz            Accounting             4203
        De Haan          Executive              4713
        Kochhar          Executive              3001
        Chen             Finance                2994
        Faviet           Finance                4133
        Popp             Finance                2194
        Sciarra          Finance                2992
        Urman            Finance                2834
        Austin           IT                     3089
        9 rows selected.

子查询当然可以包含WHERE子句来限制插入新表的行。要创建一个没有行的表,可以使用排除所有行的WHERE子句:

        create table no_emps as
        select *
        from employees
        where 1=2;

WHERE子句1=2绝不会返回TRUE,因此会创建表结构以备用,但在创建时没有插入行。

5.4.3 在创建之后更改表定义

创建表后,可以对表进行许多更改。影响物理存储的那些更改属于数据库管理员领域,但许多更改只是逻辑的,由SQL开发人员实施。下面就是示例(它们大部分都很简单):

● 添加列:

        alter table emp
        add (job_id number);

● 修改列:

        alter table emp
        modify (comm number(4,2) default 0.05);

● 删除列:

        alter table emp
        drop column comm;

● 将列标记为未使用:

        alter table emp
        set unused column job_id;

● 重命名列:

        alter table emp
        rename column hiredate to recruited;

● 将表标记为只读:

        alter table emp
        read only;

所有这些变更都是包含内置COMMIT的DDL命令。因此它们是不可逆的,如果表上有活动事务,它们就会失败。其实它们也是瞬间完成的(删除列除外)。删除列可能是一个耗时的操作,因为删除各列之后,必须重新构造各行来删除列的数据。SET UNUSED命令——对于SQL而言,它让这些列不存在——通常是更好的选择,如果方便的话,后面采用下列命令,一次删除表中所有未使用的列:

        ALTER TABLE tablename
        DROP UNUSED COLUMNS;

对于DML命令而言,将表标记为只读会产生错误。但还是能够删除表。这可能令人困惑,但理解之后,就会发现它完全符合逻辑。DROP命令实际上不影响表:它影响数据字典中的表,这些表定义表,它们不是只读的。

5.4.4 删除和截断表

TRUNCATE TABLE命令能够删除表的所有行,同时保持表定义不变。DROP TABLE更极端,因为同时会删除表定义。其语法如下所示:

DROP TABLE [schema.]tablename ;

如果没有指定模式,那么当前登录的模式中名为tablename的表就会被删除。

和TRUNCATE一样,SQL在删除表之前不会生成警告,而且,与任何DDL命令一样,它包含COMMIT。因此DROP一般不可逆。但也有一些限制:如果会话(甚至是你自己的)有正在处理的事务,该事务包含表中的行,那么DROP就会失败;另外,也不可以删除在为其他表而定义的外键约束中引用的表。首先必须删除这个表(或者这个约束)。

提示:

Oracle 12c包含一个默认为启用的垃圾箱选项。它允许还原任何已删除的表,除非表是用PURGE选项删除的,或者禁用了垃圾箱选项。

练习5-3 创建表

在本练习中,使用SQL Developer创建一个堆表,使用子查询来插入一些行,并修改表。使用SQL*Plus进行更多修改,然后删除表。

(1) 使用SQL Developer,以用户HR身份连接数据库。

(2) 右击导航树中的Tables分支,单击New Table选项。

(3) 将新表命名为EMPS,使用Add Column按钮,将它设置为如图5-6所示。

图5-6 设置新表

(4) 单击DDL标签,看看是不是已经构造了语句。它应该如下所示:

        CREATE TABLE EMPS
        (
        EMPNO NUMBER
        ,ENAME VARCHAR2(25)
        ,SALARY NUMBER
        ,DEPTNO NUMBER(4, 0)
        );

返回Table选项卡(如图5-6所示),单击OK按钮创建表。

(5) 运行下列语句:

        insert into emps
        select employee_id, last_name, salary, department_id
        from employees;

并提交插入:

        commit;

(6) 右击SQL Developer导航器中的EMPS表,单击Column和Add按钮。

(7) 定义一个新的列EMAIL,数据类型为DATE,如图5-7所示;单击Apply按钮创建列。

图5-7 定义新列

(8) 使用SQL*Plus作为HR用户连接到数据库。

(9) 为EMPS表中的HIRED列定义一个默认值:

        alter table emps
        modify (hired default sysdate);

(10) 在没有指定HIRED值的情况下插入一行,检查新的行有HIRED日期,而其他行没有:

        insert into emps (empno, ename)
        values (99, 'Newman');
        select hired, count(1)
        from emps
        group by hired;

(11) 删除新表,进行整理。

        drop table emps;

5.5 创建和使用临时表

临时表包含所有会话都可以访问的定义,但其中的行是插入行的会话专用的。编程人员可将它们作为私有存储区,用于操纵大量数据。语法如下:

        CREATE GLOBAL TEMPORARY TABLE temp_tab_name
        (column datatype [, column datatype] )
        [ON COMMIT {DELETE | PRESERVE} ROWS] ;

列定义与普通表没有差别,但确实可以通过子查询来提供。末尾的可选子句确定插入的任何行的生命周期。默认方式是:在插入行的事务完成之时,删除相应的行,但也可以更改此行为,以便将它们保留到插入行的会话结束为止。无论选择哪个选项,数据都专门用于每个会话:不同用户可将自己的行插入自己的表的副本中,谁都看不到其他人的行。

在很多方面,临时表都与永久表类似。可以针对其执行任何DML或SELECT命令。可以为临时表定义索引、约束以及触发器。可以在视图和同义词中引用它,或将它与其他表联接在一起。二者的区别在于:临时表的数据是临时的,专用于相应的会话,针对其执行的所有SQL命令的速度远比针对永久表的命令快。

速度快的第一个原因是,临时表不是永久表空间中的段。表将写出到用户临时表空间的临时段中。临时表空间上的I/O远比永久表空间上的I/O速度快,原因是前者不使用数据库缓冲区缓存,而全部由会话的服务器进程直接在磁盘上执行。

速度快的第二个原因在于:针对临时表的DML不生成重做数据。由于数据仅在会话生存期间(可能仅是事务期间)保留,就没必要生成重做数据。这带来了双重好处:针对表的会话的DML更快,且取消了重做生成系统的负担(在忙碌的多用户数据库中,这是一个糟糕的争用点)。

图5-8显示了使用SQL*Plus来创建和使用临时表的过程。Database Control的Table Creation Wizard也可以创建临时表。

图5-8 创建和使用临时表

练习5-4 创建和使用临时表

本练习创建用于报告当前员工的临时表。使用两个SQL*Plus 会话演示数据是每个会话专用的。

(1) 使用SQL*Plus,以用户HR的身份连接到数据库。

(2) 按如下方式创建临时表:

        create global temporary table tmp_emps on commit preserve rows as
        select * from employees where 1=2

(3) 插入一些行,并将其提交:

        insert into tmp_emps select * from employees where department_id=30;
        commit;

(4) 以HR的身份启动第二个SQL*Plus会话。

(5) 在第二个会话中,确认第一个会话中的第一个插入虽已提交但不可见,并插入一些不同的行:

        select count(*) from tmp_emps;
        insert into tmp_emps select * from employees where department_id=50;
        commit;

(6) 在第一个会话中,将表截断:

        truncate table tmp_emps;

(7) 在第二个会话中,确认表的会话副本仍然包含行:

        select count(*) from tmp_emps;

(8) 在第二个会话中,演示会话的终止并不会清除行。这需要断开连接并再次连接:

        disconnect;
        connect hr/hr
        select count(*) from tmp_emps;

(9) 在两个会话中通过删除表来清理环境。