データ型
NOTICE: 本文档是阿列克谢·科维亚津和谢尔盖·沃斯特里科夫所著《InterBase世界》一书中的一章。
尽管数据类型在文档中有详细描述(参见[1,第4章]),但有必要考虑本书后续章节中经常使用的一系列概念。除了通用信息外,还将回顾在InterBase数据库中使用数据类型的一些示例以及关于其使用和转换的建议。我们将详细讨论第1和第3种InterBase数据库方言中存在的数据类型差异。
关于数据
数据类型是任何编程语言或任何DBMS服务器的基本元素。InterBase也不例外。当我们说数据库存储某些信息时,我们必须意识到这些信息不能存储在一个地方。恰恰相反,数据必须被分类并放到其“架子”上。数据类型定义了可以放到某个“架子”上的内容以及不能放的内容。当我们谈论“架子”时,首先指的是数据库表的字段(参见“表。主键和生成器”(第1部分)一章),以及触发器和存储过程中的变量等。每种数据类型都有一组可以对该类型的值执行的操作。因此,在设计数据库时选择正确的数据类型非常重要。这将有助于在开发客户端程序时避免许多问题。InterBase中有12种数据类型可以满足开发人员存储数据的需求。这些类型有条件地分为以下6组:
- 用于存储整数 - INTEGER和SMALLINT;
- 用于存储实数 - FLOAT和DOUBLE PRECISION;
- 用于存储固定精度数字 - NUMERIC和DECIMAL;
- 用于存储日期、时间和时间戳 - DATE、TIME和TIMESTAMP;
- 用于存储字符 - CHARACTER(缩写形式为CHAR)和VARYING CHARACTER(VARCHAR);
- 用于存储动态扩展数据 - BLOB
此外,还可以定义原始类型值的数组,即除BLOB之外的所有枚举类型。
InterBase的大多数数据类型对应于SQL92标准中定义的类型,但除此之外,它们还有自己的特点–原始数据类型的数组和BLOB。InterBase中的数组可以在一个字段中包含一组相同类型的数据。例如,我们可以定义INTEGER类型值的数组。数组可以有多个维度。BLOB数据类型是一种动态扩展的数据类型,其名称通常被解释为Binary Large OBject。应该说,BLOB是InterBase开发人员的发明,后来传播开来并在所有现代SQL服务器中扎根。
定义数据类型的语法
数据类型用于描述表中的字段、触发器中的变量和存储过程。下面给出在InterBase中定义所有可能数据类型的通用语法。
< datatype> =
{SMALLINT | INTEGER | FLOAT | DOUBLE PRECISION}[ ]
| {DATE | TIME | TIMESTAMP} [ ]
| {DECIMAL | NUMERIC} [( precision [, scale])] [ ]
| {CHAR | CHARACTER | CHARACTER VARYING | VARCHAR} [( int)
[ ] [CHARACTER SET charname]
| {NCHAR | NATIONAL CHARACTER | NATIONAL CHAR}
[VARYING] [( int)] [ ]
| BLOB [SUB_TYPE { int | subtype_name}] [SEGMENT SIZE int]
[CHARACTER SET charname]
| BLOB [(seglen [, subtype])]
数据类型的特点,如大小、精度和可能值的范围,在[1.的表4.1中有详细描述,因此我们在这里不再重复。现在让我们简要回顾数据类型的主要特点,并关注它们的可能应用。
整数类型
SMALLINT和INTEGER属于整数类型。我应该说,SMALLINT是INTEGER的一个缩减版本。它的长度是2字节,而INTEGER是4字节。通常你不应该为了节省磁盘空间而这样做。基于这个原因,最好使用INTEGER类型来存储整数值。
整数类型的应用领域是显而易见的:它们对于只包含整数的字段是必需的–用于存储计数器、编号等。通常INTEGER类型也用于包含主键的字段。
实数数据类型
FLOAT和DOUBLE PRECISION类型属于实数类型(它们也被称为浮点数类型)。首先,我想警告读者不要使用FLOAT类型–它的精度不足以存储大多数小数值。特别不建议在其中存储货币值–在FLOAT类型变量中舍入误差会很快出现,这可能会在计算时让会计感到惊讶。在数据库中存储浮点数(例如,在会计系统和科学计算系统中)的最佳方式是使用DOUBLE PRECISION类型存储它们。
您应该考虑到,在InterBase第3种方言中,有一种存储64字节固定小数点类型的机制。该机制用于存储货币值。使用这些类型可提供最佳精度。
固定小数点数据类型
NUMERIC和DECIMAL属于这些数据类型。关于NUMERIC和DECIMAL之间区别的问题经常被问到。这两种类型具有相同的位数容量–从1到18位,相同的精度–从零到位数容量。
让我们提醒一下:位数容量是数字的总位数,精度是逗号后的位数。
最有趣的是,根据文档,这些类型在最大位数容量上有所不同,但实际上,它们的实现几乎相同,两者之间没有区别。
您可以轻松验证这一点,启动isql实用程序并执行下面提到的操作序列。我们创建以下视图的表:
SQL> CREATE TABLE test (
CON> Num_field NUMERIC(15,2),
CON> Dec_field DECIMAL(15,2));
然后我们给出显示表结构的命令:
SQL> show tables test;
我们看到以下内容
NUM_FIELD NUMERIC(15, 2) Nullable
DEC_FIELD NUMERIC(15, 2) Nullable
如您所见,InterBase告知这两个给定列都具有NUMERIC类型。这种行为的原因在于固定小数点数据类型的实现。问题是InterBase只有三种存储任何整数表达式的机制,所有类型,无论它们如何命名,都按照这些变体实现。
以下是[1.中的表格,说明了各种整数类型(表1.1)如何存储。如您所见,在第3种方言中,对于大位数容量的数字,数据存储是不同的:
表1.1. 固定小数点数字的存储
| 位数容量 | 方言1 | 方言3 |
|---|---|---|
| 从1到4 | NUMERIC为SMALLINT DECIMAL为INTEGER |
SMALLINT |
| 从5到9 | INTEGER | INTEGER |
| 从10到18 | DOUBLE PRECISION | INT64 |
现在我们可以确定地说NUMERIC和DECIMAL类型之间的主要区别是什么:在定义具有小位数容量(最多四位)的字段(或变量)时,前者存储为2字节整数SMALLINT,后者存储为4字节INTEGER。因此,如果位数容量超过四,DECIMAL和NUMERIC类型将是等效的。
请注意第一种和第三种方言中大位数容量类型实现的差异。在第一种方言中,固定小数点数字从整数变为实数,其中应用了舍入机制。在第三种方言中,这个特点已被消除–大整数真正作为整数存储–使用INT64机制,它可以存储范围为+/-2^32的64位数字。因此,最好在使用第3种方言创建的数据库中存储货币数据。只有使用INT64机制才能保证小额货币余额的安全。
存储日期和时间的类型
用于存储日期和时间的类型在 InterBase 6.x 及其克隆版本中与 4.x 和 5.x 相比发生了变化。为了不在这些类型的历史纠葛中迷失,我们将以 InterBase 第 6 版的情况来讨论。然后在此基础上简要提及早期版本的情况。这样做是为了那些仍在使用早期版本 InterBase 的用户。因此,InterBase 6.x 中有 3 种用于存储日期和时间的类型:DATE、TIME 和 TIMESTAMP。
- DATE 类型存储日期,精确到天。可能值的范围–从公元 100 年 1 月 1 日到 32768 年 2 月 29 日。
- TIME 类型存储时间数据,精确到秒的万分之一。可能值的范围–从 00:00 AM 到 23:59.9999 PM。
- TIMESTAMP 类型表示 DATE 和 TIME 类型的组合。
如何处理日期?如果问题是在服务器端的存储过程或触发器中工作,一切都相当简单–我们总是可以声明所需类型的变量,并从表中赋值,反之亦然。然而,有必要将数据从数据库传输到应用程序,然后再传回来。在这种情况下有两种方法:使用应用 InterBase 日期原始格式的库来访问这些类型的对象,并将此格式转换为普通的语言内日期/时间类型(FIBPlus 就是此类库的一个例子),或者使用 InterBase 内置的将日期转换为字符串的机制。
如果你需要从完整日期中只提取年份或月份,你会怎么做?为此,你必须使用 EXTRACT 函数组(在 InterBase 6.x 的所有克隆版本中可用),它允许你从日期中只提取所需的部分。这些函数的使用方式如下:
EXTRACT (MONTH FROM DATE_FIELD)
EXTRACT (YEAR FROM DATE_FIELD)
EXTRACT 函数中参数的完整列表如下:YEAR、MONTH、DAY、HOUR、MINUTE、SECOND、WEEKDAY、YEARDAY。它们的功能从其名称即可看出,因此我们在此不再赘述。
用于存储文本的数据类型
InterBase 中有两种用于存储文本信息的类型–CHAR 和 VARCHAR。它们的全称是 CHARACTER 和 CHARACTER VARYING,但没有理由使用长名称–即使是 isql 实用程序中的 Show tables 命令也给出类型的简短名称。
为了定义符号类型的字段或变量,有必要在类型名称后的括号中指定将在所定义对象中使用的符号数量,或者省略符号数量–这样将创建一个长度为 1 个符号的字段。
CREATE TABLE testCHARLen(
Field1 CHAR(255),
Field2 CHAR);
当创建此表时,Field1 的长度将为 255 个符号,而 Field2 为 1 个符号。
CHAR 和 VARCHAR 类型在许多方面相似–两者都可以包含最多 32768 个符号,但也有一些区别。虽然这两种类型在数据库中的存储方式相同,但 InterBase 对它们的处理方式不同。以下示例说明了这一点:
SQL> create table testCHAR ( c1 char(10), c2 varchar(10));
SQL> insert into testCHAR(c1,c2) values(‘Test’,‘Test’);
SQL> SELECT ‘(’||c1.|’)’, ‘(’||c2.|’)’ from testCHAR;
结果,我们将得到以下内容:
(Test ) (Test)
如你所见,在从 c1 字段中选择的值 ‘Test’ 之后,出现了空格。这意味着当从 CHAR 类型的字段中选择数据时,返回的值会被补充空格直到字段的完整长度。很难想象为什么需要这种导致网络流量(网络负载)大幅增长的行为。无论如何,VARCHAR 是推荐使用的符号类型。
符号类型的主要特征之一是其字符集–CHARACTER SET。字符集是为整个数据库定义的,并且默认用于所有符号字段,除非在创建字段时显式重新定义。为了创建带有显式字符集指示的符号字段,有必要在列的描述中添加字符集的描述(在 CREATE TABLE 或 ALTER TABLE 语句中)。字符集 WIN1251 通常用于支持俄语(如果你想详细了解在 InterBase 中使用俄语的情况,请参阅“InterBase 俄化”一章(第 1 部分))。以下是一个包含显式描述字符集 WIN1251 的符号字段的表示例:
CREATE TABLE TestCHARSET(
Field1 VARCHAR(255),
Field2 VARCHAR(255) CHARACTER SET win1251);
这里 Field1 是没有显式字符集指示的字段,因此创建数据库时指定的字符集将用于它。对于 Field2,明确确定它将存储 WIN1251 编码的符号。
除了为符号字段指定字符集外,你还可以指定排序规则,该规则定义此数据集中的符号将如何排序。对于俄语,有两种排序变体–WIN1251 和 PXW_CYRL。有关使用 COLLATION ORDER 的更多详细信息,请参阅“InterBase 俄化”一章。
你可以在文档 [1,第 13 章] 中找到完整的字符集及其应用的 COLLATION ORDER 列表。
注意!根据 InterBase 6 的文档,有 4 种符号类型:除了上述数据类型外,还有 2 种–NCHAR 和 NCHAR VARYING,但同一文档下面解释说,后两种类型与 CHAR 和 VARCHAR 相同,只是默认使用字符集 ISO8859.1。这意味着实际上使用伪类型 NCHAR 等同于应用 CHAR DEFAULT CHARACTER SET ISO8859.1。类似地,对于 NCHAR VARYING,只是用 VARCHAR 代替 CHAR。显然,这些伪类型的应用是针对西欧和美国的用户,那里创建了字符集 ISO8859.1 以支持各种语言。
Blob 数据类型
BLOB 数据类型旨在存储大量可变大小的数据。BLOB 类型允许存储无法放入其他类型字段的数据–例如,图片、音乐文件、视频片段等。在表中定义最简单的 BLOB 类型字段的要求与定义任何基本类型字段的要求相同:
CREATE TABLE testBLOB(
myBlobField BLOB);
结果,将创建字段 myBlobField,可以在其中存储大量数据。尽管 BLOB 字段在定义方式上与其他字段没有区别,但它们在数据库内部的实现方式却大不相同。非 BLOB 字段位于数据页上(参见“InterBase 数据库结构”一章(第 4 部分))彼此相邻,而在数据页上只存储 BLOB 标识符的情况下,BLOB 本身被分配在特殊的页面上。这种数据结构允许存储大小不固定的数据。
BLOB 类型可以定义一组多个子类型以及称为过滤器(BLOB filters)的特殊过程来处理这些子类型。InterBase 内置了一些预定义的 BLOB 子类型。所有这些子类型都有非负数,例如子类型 0–未定义类型的数据,子类型 1–文本,子类型 2–BLR(二进制语言表示,参见词汇表和“InterBase 数据库结构”一章)等。用户还可以定义可以具有负值的 BLOB 子类型。过滤器可以应用于每种类型。它将把一种子类型的字段转换为另一种。
应该指出,使用 BLOB 字段通常是存储相对于数据库而言的外部文件的替代方案。至于 BLOB 过滤器,由于面向狭窄的任务类别,它们使用得相当少。
数组
InterBase DBMS 是最早出现数组的数据库之一。数据库中数组的支持是传统关系模型的扩展。数组的存在允许简化对同一数据类型集合的处理。
配列とは、同じ型の値の集合であり、共通の名前を持ち、番号によって任意の配列要素にアクセスすることを可能にします。InterBaseの配列は、1次元および多次元にすることができます。
テーブルにINTEGER型の数値の配列フィールドを作成するには、次のように記述します。
CREATE TABLE test(
myOneDimArray INTEGER[12.,
myTwoDimArray INTEGER[5,4.,
myThreeDimArray INTEGER[2,10,8.);
これにより、3つの配列型フィールドが作成されます。myOneDimArray - 12個の数値を含む1次元配列のフィールド、myTwoDimArray - 2次元配列(行列)5×4の整数を含むフィールド、myThreeDimArray - 2×10×8の3次元配列を含むフィールドです。このような定義では、配列要素は「1」から番号が付けられることに注意してください。つまり、最初の要素は番号1、2番目は番号2などとなります。配列の境界を自分で設定したい場合、例えば0から5までにする場合は、フィールド定義を次のように指定します。
myArray INTEGER[0:5.
配列はBLOB型のフィールドに基づいて実装されているため、多次元配列がテーブルを大量のデータで「汚染」することを心配する必要はありません。InterBaseは、これらのフィールドの入出力操作を最適化するために、配列のデータを別々のページにきれいに割り当てます。配列をどのように使用するのでしょうか?配列は、同じ型のオブジェクトを格納するための便利なメカニズムを提供します。しかし、80%のケースでは、開発者は配列の代わりに複数のデータを従属(詳細)テーブルに格納することを好むため、配列はInterBase DBMSのクライアントアプリケーションではそれほど頻繁には使用されません。これは、DelphiおよびC++ Builderに付属するBDEやIBXなどのアクセスライブラリが配列を扱うことができないためです。InterBaseのドキュメントによると、プリプロセッサgpreを使用して配列を操作することは可能ですが、これはDelphi/C++ Builder開発者にとって最も便利な方法ではありません。幸いなことに、FIBPlusライブラリにはInterBaseの配列フィールドのサポートがあります。詳細については、「FIBPlusの特別な機能」の章で読むことができます。Microsoft社の開発ツールを使用してInterBase用のクライアントアプリケーションを作成できるクライアントライブラリIBProviderも、配列の操作をサポートしています(「Microsoft OLE DBテクノロジを使用したInterBase DBMSクライアントアプリケーションの開発」の章(パート3)を参照してください)。
結論
データ型について、先走らずに語ることは不可能であることに注意すべきです。なぜなら、データ型はデータベースアプリケーションの開発に関わるすべての主要な領域に浸透しているからです。したがって、この本を読むときは、InterBaseの基礎を再確認する必要があるたびに参照できるクイックリファレンスとして、この章を使用することをお勧めします。