4.5.5 用户定义的类型

4.5.5 用户定义的类型

SQL支持两种形式的用户定义数据类型。

  • 第一种称为独特类型( distinct type),我们将在这里介绍。
  • 另一种称为结构化数据类型( structured data type),允许创建具有嵌套记录结构、数组多重集的复杂数据类型

在本章我们不介绍结构化数据类型,而是在后面第22章描述。
一些属性可能会有相同的数据类型。例如,用于学生名和教师名的name属性就可能有相同的域:所有人名的集合。
然而, budgetdept_name的域肯定应该是不同的。namedept_name是否应该有相同的域,这一点就不那么明显了。在实现层,教师姓名和系的名字都是字符串。然而,我们通常不认为“找出所有与某个系同名的教师”是一个有意义的查询。因此,如果我们在概念层而不是物理层来看待数据库的话,namedept_name应该有不同的域。

独特类型

更重要的是,在现实中,把一个教师的姓名赋给一个系名可能是一个程序上的错误;
类似地,把个以美元表示的货币值直接与一个以英镑表示的货币值进行比较几乎可以肯定是程序上的错误。
一个好的类型系统应该能够检测出这类赋值或比较。为了支持这种检测,SQL提供了独特类型(distincttype)的概念。

如何自定义新类型

可以用create type子句来定义新类型。例如,下面的语句:

1
2
create type Dollars as numeric(12,2) final;
create type Pounds as numeric(12,2) final;

把两个用户定义类型DollarsPounds定义为总共12位数字的十进制数,其中两位放在十进制小数点后。(在此关键字final并不是真的有意义,它是SQL:1999标准要求的,其原因我们不在这里讨论了;一些系统实现允许忽略血final关键字。)然后新创建的类型就可以用作关系属性的类型。例如,我们可以把department表定义为:

1
2
3
4
5
create table department (
dept_name varchar(20),
building varchar(15),
budget Dollars
);

尝试为Pounds类型的变量赋予一个Dollars类型的值会导致一个编译时错误,尽管这两者都是相同的数值类型。这样的赋值很可能是由程序错误引起的,或许是程序员忘记了货币之间的区别。为不同的货币声明不同的类型能帮助发现这些错误。
由于有强类型检查,表达式( department.budget+20)将不会被接受,因为属性和整型常数20具有不同的类型。一种类型的数值可以被转换(也即cast)到另一个域,如下所示:

1
cast(department.budget to numeric( 12, 2));

我们可以在数值类型上做加法,但是为了把结果存回到一个Dollars类型的属性中,我们需要用另一个类型转换表达式来把数值类型转换回Dollars类型。

删除或就该自定义类型

SQL提供了drop typealter type子句来删除或修改以前创建过的类型。

在把用户定义类型加入到SQL(在SQL:1999中)之前,SQL有一个相似但稍有不同的概念:(domain)(domainSQL-92中引入),它可以在基本类型上施加完整性约束。例如,我们可以定义一个域DDollars,如下所示:

1
create domain DDollars as numeric(12, 2) not null;

域和自定义类型的区别

DDollars域可以用作属性类型,正如我们用Dollars类型一样。然而,类型和域之间有两个重大的差别

  1. 在域上可以声明约束,例如not null,也可以为域类型变量定义默认值,然而在用户自定义类型上不能声明约束或默认值。设计用户定义类型不仅是用它来指定属性类型,而且还将它用在不能施加约束的地方对SQL进行过程扩展。
  2. 域并不是强类型。因此一个域类型的值可以被赋给另一个域类型,只要它们的基本类型是相容的

当把check子句应用到域上时,允许模式设计者指定一个谓词,被声明为来自该域的任何变量都必须满足这个谓词。例如, check子句可以保证教师工资域中只允许出现大于给定值的值:

1
2
create domain YearlySalary numeric(8,2)
constraint salary_value_test check(value >29000.00);

YearlySalary域有一个约束来保证年薪大于或等于290000美元。 constraint salary_value_test子句是可选的,它用来将该约束命名为salary_value_test。系统用这个名字来指出一个更新违反了哪个约束。
作为另一个例子,使用in子句可以限定一个域只包含指定的一组值

在数据库实现中对类型和域的支持

尽管本节描述的create typecreate domain结构是SQL标准的部分,但这里描迷的这些结构形式还没有被大多数数据库实现完全支持。

PostgreSQL

PostgreSQL支持create domain结构,但是其create type结构具有不同的语法和解释。

IBM DB2

IBM DB2支持create type的一个版本,它使用create distinct type语法,但不支持create domain

SQL Server

微软的SQL Server实现了create type结构的一个版本,支持域约束,与SQLcreate domain结构类似。

Oracle

Oracle不支持在此描述的任何一种结构。

MySQL

经过我的测试MySQL也不支持自定义类型create type结构和自定义域create domain结构。

面向对象类型

然而,SQL还定义了一个更复杂的面向对象类型系统,我们将在后面第22章学习。通过使用不同形式的create type结构, OracleIBM DB2PostgreSQLSQL Server都支持面向对象类型系统