2. Oracle关系数据结构
2.1. 表和表簇
2.1.1. 模式对象简介
数据库模式是数据结构的逻辑容器,这些数据结构称为模式对象。模式对象的例子有表和索引。模式对象是通过 SQL 创建和操作的。
一个数据库用户拥有密码和各种数据库权限。每个用户拥有一个与其同名的模式。模式包含了属于该用户的数据。例如,hr用户拥有hr模式,该模式包含如employees表这样的模式对象。在生产数据库中,模式所有者通常代表一个数据库应用程序而不是一个人。
在一个模式内,每种特定类型的模式对象都有一个唯一的名称。例如,hr.employees 指的是 hr 模式中的employees表。图 2-1 描绘了名为hr的模式所有者和hr模式内的模式对象。
2.1.1.1. 模式对象类型
关系数据库中最重要的模式对象是表。表以行的形式存储数据。
Oracle SQL 允许您创建和操作许多其他类型的模式对象,包括以下内容:
- 索引: 索引是包含表或表簇中每个索引行条目的模式对象,并提供直接、快速访问行的功能。Oracle 数据库支持多种类型的索引。索引组织表是一种数据存储在索引结构中的表。
- 分区: 分区是大型表和索引的一部分。每个分区都有自己的名称,并且可以选择具有自己的存储特性。
- 视图: 视图是数据在一个或多个表或其他视图中的自定义表示。您可以将其视为存储的查询。视图实际上不包含数据。
- 序列: 序列是用户创建的对象,可以由多个用户共享以生成整数。通常,序列用于生成主键值。
- 维度: 维度定义了列集对之间的父子关系,其中列集中的所有列必须来自同一张表。维度通常用于分类客户、产品和时间等数据。
- 同义词: 同义词是其他模式对象的别名。由于同义词只是别名,所以除了其在数据字典中的定义外,不需要任何存储。
- PL/SQL子程序和包: PL/SQL是Oracle对SQL的过程扩展。PL/SQL子程序是一个命名的PL/SQL块,可以通过一组参数调用。PL/SQL包将逻辑相关的 PL/SQL类型、变量和子程序分组。
数据库中还存储了其他类型的对象,可以通过 SQL 语句创建和操作,但不包含在模式中。这些对象包括数据库用户、角色、上下文和目录对象。
2.1.1.2. 模式对象存储
一些模式对象将数据存储在称为段的逻辑存储结构中。例如,非分区的堆组织表或索引会创建一个段。其他模式对象,例如视图和序列,仅包含元数据。本节仅描述具有段的模式对象。
Oracle数据库在逻辑上将模式对象存储在表空间内。模式和表空间之间没有关系:一个表空间可以包含来自不同模式的对象,一个模式的对象可以包含在不同的表空间中。每个对象的数据实际包含在一个或多个数据文件中。
图 2-2显示了表和索引段、表空间和数据文件的可能配置。一个表的数据段跨越两个数据文件,这两个数据文件都是同一表空间的一部分。一个段不能跨越多个表空间。
2.1.1.3 模式对象依赖
一些模式对象引用其他对象,从而创建模式对象依赖关系。例如,一个视图包含引用表或其他视图的查询,而一个PL/SQL子程序调用其他子程序。如果对象A的定义引用了对象B,那么A是相对于B的依赖对象,而B是相对于A的被引用对象。
Oracle数据库提供了一种自动机制,以确保依赖对象始终与其引用的对象保持同步。当创建依赖对象时,数据库会跟踪依赖对象与其引用对象之间的依赖关系。当引用对象发生可能影响依赖对象的更改时,依赖对象会被标记为无效。例如,如果用户删除了一个表,那么基于该表的视图将不可用。
无效的依赖对象必须在引用对象的新定义下重新编译后才能使用。重新编译会在引用无效的依赖对象时自动进行。
以下示例脚本演示了模式对象如何创建依赖关系,该脚本创建了一个表 test_table
,然后创建了一个查询该表的过程:
CREATE TABLE test_table ( col1 INTEGER, col2 INTEGER );
CREATE OR REPLACE PROCEDURE test_proc
AS BEGIN FOR x IN ( SELECT col1, col2 FROM test_table )
LOOP
-- process data
NULL;
END LOOP;
END;
/
以下查询过程 test_proc 的状态,显示其有效:
SQL> SELECT OBJECT_NAME, STATUS FROM USER_OBJECTS WHERE OBJECT_NAME = 'TEST_PROC';
OBJECT_NAME STATUS
---------------- ----------
TEST_PROC VALID
在向 test_table 添加列 col3 后,该过程仍然有效,因为该过程对该列没有依赖关系:
SQL> ALTER TABLE test_table ADD col3 NUMBER;
Table altered.
SQL> SELECT OBJECT_NAME, STATUS FROM USER_OBJECTS WHERE OBJECT_NAME = 'TEST_PROC';
OBJECT_NAME STATUS
----------- -------
TEST_PROC VALID
然而,更改 test_proc 过程中依赖的 col1 列的数据类型会使该过程失效
SQL> ALTER TABLE test_table MODIFY col1 VARCHAR2(20);
Table altered.
SQL> SELECT OBJECT_NAME, STATUS FROM USER_OBJECTS WHERE OBJECT_NAME = 'TEST_PROC';
OBJECT_NAME STATUS
----------- -------
TEST_PROC INVALID
运行或重新编译该过程会使其再次有效,如以下示例所示:
SQL> EXECUTE test_proc
PL/SQL procedure successfully completed.
SQL> SELECT OBJECT_NAME, STATUS FROM USER_OBJECTS WHERE OBJECT_NAME = 'TEST_PROC';
OBJECT_NAME STATUS
----------- -------
TEST_PROC VALID
2.1.1.4 SYS和System模式
所有的 Oracle 数据库都包含默认的管理员账户。管理员账户拥有极高的权限,仅供授权执行诸如启动和停止数据库、管理内存和存储、创建和管理数据库用户等任务的 DBA 使用。
在创建数据库时,会自动创建管理员账户SYS。该账户可以执行所有的数据库管理功能。SYS模式存储了数据字典的基本表和视图。这些基本表和视图对于 Oracle 数据库的运行至关重要。SYS 模式中的表只能由数据库操作,任何用户都不得修改。
SYSTEM 账户也会在创建数据库时自动创建。SYSTEM 模式存储了显示管理信息的附加表和视图,以及各种 Oracle 数据库选项和工具使用的内部表和视图。永远不要使用 SYSTEM 模式存储对非管理员用户感兴趣的表。
2.1.1.5 Sample模式
Oracle数据库可能包含示例模式,这是一组相互关联的模式,使得Oracle文档和教学材料能够展示常见的数据库任务。hr模式是一个示例模式,其中包含有关员工、部门和地点、工作历史等信息。图 2-3是hr模式中表的实体关系图。本手册中的大多数示例都使用了该模式中的对象。
2.1.2. 表概述
2.1.2.1. 列与行
表的定义包括表名和一组列。列标识了表所描述实体的属性。例如,employees 表中的 employee_id 列指的是员工实体的员工ID属性。
一般情况下,在创建表时,您为每一列指定列名、数据类型和宽度。例如,employee_id 的数据类型是 NUMBER(6),表示该列只能包含最多 6 位数的数字数据。宽度可以由数据类型预先确定,如 DATE 类型。
表可以包含虚拟列,与非虚拟列不同的是,虚拟列不会占用磁盘空间。数据库根据需要通过计算一组用户指定的表达式或函数来推导虚拟列的值。例如,income 虚拟列可以是 salary 和 commission_pct 列的函数。
创建表后,您可以使用 SQL 插入、查询、删除和更新行。一行是对应于表中记录的一组列信息。例如,employees 表中的一行描述了特定员工的属性。
2.1.2.2. 示例:CREATE TABLE and ALTER TABLE 语句
Oracle SQL创建表的命令是CREATE TABLE。示例2-1展示了hr示例模式中employees表的CREATE TABLE语句。该语句指定了诸如employee_id、first_name 等列,并为每个列指定了诸如NUMBER或DATE等的数据类型。
示例 2-2 展示了一个 ALTER TABLE 语句,该语句向 employees 表添加完整性约束。完整性约束可以强制执行业务规则,防止无效信息进入表中。
示例 2-3展示了hr.employees 表的8行和6列数据。
示例 2-3 中的输出展示了表、列和行的一些重要特征,包括:
- 表中的一行描述了一个员工的属性:姓名、工资、部门等。例如,输出中的第一行显示了名为 Steven King 的员工的记录。
- 列描述了员工的属性。在示例中,employee_id 列是主键,这意味着每个员工都通过员工ID唯一标识。任何两个员工都保证不具有相同的员工ID。
- 非主键列可以包含具有相同值的行。在示例中,员工101 和102 的工资值相同:17000。
- 外键列引用同一表或不同表中的主键或唯一键。在此示例中,department_id 中的值 90 对应于 departments 表的 department_id 列。
- 字段是行和列的交集。它只能包含一个值。例如,员工104的部门 ID 的字段包含值 60。
- 字段可能缺少值。在这种情况下,字段被称为空值。员工 100 的 commission_pct 列的值为空,而员工 149 的字段中的值为 0.2。除非在该列上定义了 NOT NULL 或主键完整性约束,否则列允许空值,此时不允许插入行时不为该列提供值。
2.1.2.3. Oracle数据类型
每列都有一个数据类型,与特定的存储格式、约束和有效值范围相关联。值的数据类型将一组固定的属性与该值相关联。这些属性导致 Oracle 数据库以不同于另一种数据类型的方式处理一种数据类型的值。例如,你可以将 NUMBER 数据类型的值相乘,但不能将 RAW 数据类型的值相乘。
当创建表时,必须为每个列指定数据类型。随后插入列中的每个值都假定为该列的数据类型。
Oracle 数据库提供了几种内置数据类型。最常用的数据类型分为以下几类:
- 字符数据类型
- 数值数据类型
- 日期时间数据类型
- ROWID数据类型
- 格式模型和数据类型
其他重要的内置类型类别包括原始数据类型(RAW)、大对象(LOBs)和集合。PL/SQL 具有用于常量和变量的数据类型,这些类型包括布尔型(BOOLEAN)、引用类型、复合类型(记录)和用户定义类型。
1. Character数据类型
字符数据类型以字符串形式存储字符(字母数字)数据。最常用的字符数据类型是 VARCHAR2,这是存储字符数据最有效的选项。
字节值对应于字符编码方案,通常称为字符集或代码页。数据库字符集在数据库创建时确定。字符集的示例包括 7 位 ASCII、EBCDIC 和 Unicode UTF-8。
字符数据类型的长度语义可以以字节或字符为单位进行测量。字节语义将字符串视为字节序列。这是字符数据类型的默认设置。字符语义将字符串视为字符序列。字符在技术上是数据库字符集的一个代码点。
2. VARCHAR2与CHAR数据类型
VARCHAR2数据类型用于存储变长的字符字面量。术语“字面量”和“常数值”是同义的,指的是固定的数据值。例如,'LILA'、'St. George Island' 和 '101' 都是字符字面量;5001 是一个数字字面量。字符字面量被包含在单引号中,这样数据库可以将其与模式对象名称区分开来。
当你创建一个带有 VARCHAR2 列的表时,你需要指定一个最大字符串长度。在示例 2-1 中,last_name 列的数据类型为 VARCHAR2(25),这意味着存储在该列中的任何名字最多可以有 25 个字节。对于每一行,Oracle 数据库将列中的每个值存储为变长字段,除非值超过了最大长度,在这种情况下,数据库会返回错误。例如,在单字节字符集中,如果你在某一行的 last_name 列值中输入 10 个字符,那么该行的列片段只存储 10 个字符(10 字节),而不是 25 个。使用 VARCHAR2 可以减少空间消耗。与 VARCHAR2 相比,CHAR 存储固定长度的字符字符串。当你创建一个带有 CHAR 列的表时,该列需要一个字符串长度。默认长度是 1 字节。数据库使用空格填充值到指定的长度。
Oracle 数据库在进行非填充比较语义(nonpadded comparison semantics)的情况下比较 VARCHAR2 值,并在进行空白填充比较语义(blank-padded comparison semantics)的情况下比较 CHAR 值。
3. NCHAR与NVARCHAR2数据类型
NCHAR 和 NVARCHAR2 数据类型用于存储 Unicode 字符数据。Unicode 是一种通用的编码字符集,可以使用单一字符集存储任何语言的信息。NCHAR 存储对应于国家/地区字符集的固定长度字符字符串,而 NVARCHAR2 存储变长字符字符串。
在创建数据库时,你需要指定一个国家/地区字符集。NCHAR 和 NVARCHAR2 数据类型的字符集必须是 AL16UTF16 或 UTF8。这两种字符集都使用 Unicode 编码。
当你创建一个带有 NCHAR 或 NVARCHAR2 列的表时,最大大小始终基于字符长度语义。字符长度语义是 NCHAR 或 NVARCHAR2 的默认且唯一的长度语义。
4. 数值数据类型
Oracle数据库的数值数据类型用于存储固定小数和浮点数、零、以及无穷大。某些数值类型还会存储运算未定义结果的值,这被称为’非数字’或NaN。
Oracle数据库以变长格式存储数值数据。每个值都以科学记数法存储,使用1字节存储指数。数据库最多使用20字节来存储尾数,这是浮点数中包含其有效数字的部分。Oracle数据库不存储前导零和尾随零。