Типы данных
Несмотря на то, что типы данных подробно описаны в документации
(см. [1, гл. 4]), необходимо рассмотреть ряд понятий, которые будут часто
использоваться в последующих главах книги. Помимо изложения сведений общего
характера будут рассмотрены также примеры использования типов данных в базах
данных InterBase и изложены рекомендации по их использованию и преобразованию.
Также подробно рассмотрим отличия в типах данных, существующие 1-м и 3-м
диалектах базы данных InterBase.
О типах данных
Типы данных - это базовые элементы любого языка программирования
или любого сервера СУБД, и 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}[ <array_dim>] {DATE | TIME | TIMESTAMP} [
<array_dim>]
{DECIMAL | NUMERIC) [(
precision [, scale])] [ <array_dim>] {CHAR | CHARACTER | CHARACTER VARYING
| VARCHAR) [( int)]
[ <array_dim>]
[CHARACTER SET charname]
| {NCHAR NATIONAL
CHARACTER | NATIONAL CHAR)
[VARYING] [( int)] [
<array_dim>]
| BLOB [SUB_TYPE { int |
subtype_name}] [SEGMENT SIZE int]
[CHARACTER SET
charname]
| BLOB [(seglen [, subtype])]
Подробно свойства типов данных, такие, как размер, точность и диапазон
возможных значений, описаны в табл. 4.1 в [1], поэтому повторяться здесь не
будем. Далее кратко рассмотрим основные особенности типов данных и
сосредоточимся на их возможном применении.
Целочисленные типы
К целочисленным типам относятся SMALLINT и INTEGER. Надо сказать,
что SMALLINT представляет собой урезанную версию INTEGER и имеет длину 2 байта,
в отличие от 4 байт, выделяемых для хранения INTEGER. Обычно экономить на
дисковом пространстве не следует, и поэтому общей рекомендацией будет
использовать для хранения целых значений тип INTEGER.
Область применения
целочисленных типов очевидна: они нужны для полей, содержащих только целые числа
- для хранения счетчиков, количества и т.д. Обычно тип INTEGER имеют также поля,
содержащие первичные ключи.
Вещественные типы данных
К вещественным типам (их еще называют типами чисел с плавающей
точкой) относятся FLOAT и DOUBLE PRECISION. Сразу следует предостеречь читателя
от использования типа FLOAT - его точность недостаточна для хранения большинства
дробных значений. Особенно не рекомендуется хранить в нем денежные величины - в
переменных типа FLOAT очень быстро нарастают ошибки округления, что может сильно
удивить бухгалтера при подведении итогов.
Если в базе данных предполагается
хранить числа с плавающей точкой (например, в бухгалтерских системах или в
системах для научных расчетов), то лучшим выбором будет тип DOUBLE PRECISION.
Надо отметить, что в 3-м диалекте InterBase для хранения денежных
величин существует механизм хранения типов с фиксированной точкой длиной 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 имеет всего 3 механизма
хранения любого целочисленного выражения, и все типы, как бы они ни назывались,
приводятся к этим вариантам реализации.
Вот таблица из [1], которая
иллюстрирует, как хранятся различные целочисленные типы (табл. 1.1). Как видите,
хранение данных в 3-м диалекте отличается для чисел с большой разрядностью:
Табл 1.1. Хранение чисел с фиксированной точкой
|
|
|
|
|
|
Разрядность |
Диалект 1 |
Диалект З |
|
|
От 1 до 4 |
SMALLINT для NUMERIC INTEGER для DECIMAL |
SMALLINT |
|
|
От 5 до 9 |
INTEGER |
INTEGER |
|
|
От 10 до 18 |
DOUBLE PRECISION |
INT64 |
|
|
|
|
|
|
Итак, теперь мы точно можем сказать, чем отличаются типы NUMERIC и
DECIMAL: в случае определения поля (переменной) с малой разрядностью (до
четырех) первый хранится в виде 2 байтового целого числа SMALLINT, а второй - в
виде 4 байтового INTEGER.
Таким образом, в случае разрядности, большей
четырех, типы DECIMAL и NUMERIC окажутся абсолютно эквивалентными!
Обратите
внимание на отличие реализации типов с большой разрядностью в 1-м и 3-м
диалектах. В 1-м диалекте число с фиксированной точкой превращалось из целого в
вещественное, к которому применялись механизмы округления! В 3-м диалекте эта
странность была ликвидирована - большие целые числа хранятся действительно как
целые - с использованием механизма INT64, который может хранить 64-битовые числа
в диапазоне +/- 2Л32. Поэтому рекомендуется хранить данные о денежных
средствах в базах данных, созданных с использованием 3-го диалекта, - только при
использовании механизма INT64 можно гарантировать сохранность малых денежных
остатков.
Типы для хранения даты и времени
Типы для хранения даты и времени изменились в версии InterBase 6.x
и его клонах по сравнению с 4.x и 5.x. Чтобы не путаться в исторических
хитросплетениях с этими типами, рассмотрим ситуацию именно в 6-й версии
InterBase, а затем на основе этого кратко упомянем о том, что было раньше, - это
делается для тех пользователей, кто все еще работает на ранних версиях InterBase
Итак, в InterBase 6.x существует 3 типа для хранения даты и времени - это
DATE, TIME и ТГМЕ8ТАМР.
- Тип DATE хранит даты с точностью до дня. Диапазон возможных значений - от 1
января 100 года н. э. до 29 февраля 32768 года.
- Тип TIME хранит данные о времени с точностью до десятитысячной доли секунды.
Диапазон возможных значений - от 00:00 AM до 23:59.9999 РМ.
- Тип 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, однако нет никакой причины пользоваться длинными именами -
даже команда Show tables в утилите isql выдает краткие наименования типов.
Чтобы определить поле или переменную символьного типа, необходимо в скобках
после имени типа либо указать число символов, которое будет использоваться в
определяемом объекте, либо опустить число символов - при этом будет создано поле
с длиной 1 символ.
CREATE TABLE testCHARLen(
Fieldl CHAR(255),
Field2
CHAR);
В результате создания этой таблицы поле Fieldl будет иметь
длину 255 символов, a Field2 - 1 символ.
Типы CHAR и VARCHAR во многом схожи
- оба могут содержать до 32768 символов, однако есть и отличия. Хотя хранятся
эти два типа в базе данных одинаково, но работает с ними InterBase по-разному.
Это можно продемонстрировать следующим примером:
SQL> create table testCHAR ( cl char(10), c2
varchar(10));
SQL> insert into
testCHAR(cl,c2) values('Test','Test');
SQL>
SELECT '{' |cl||')', '('||c2 |')' from testCHAR;
В результате
получим следующий результат:
(Test ) (Test)
Как видите, после значения Test', выбранного из поля cl, оказались
пробелы. Это означает, что при выборке данных из поля типа CHAR возвращаемое
значение дополняется пробелами до полной длины поля. Сложно предположить, для
чего необходимо подобное поведение, которое приводит к значительному увеличению
сетевого трафика (загрузки сети).
В любом случае рекомендованным к
использованию символьным типом является VARCHAR.
Одной из важнейших
характеристик символьного типа является его набор символов - CHARACTER
SET. Набор символов определяется для всей базы данных и используется по
умолчанию для всех символьных полей, если не переопределяется явно при создании
поля.
Чтобы создать символьное поле с явным указанием набора символов,
необходимо в описании столбца (в предложениях CREATE TABLE или ALTER TABLE)
добавить описание набора символов. Для поддержки русского языка обычно
используется набор символов WEN1251 (подробнее об использовании русского языка в
InterBase см. главу "Русификация InterBase" (ч. 1)). Вот пример таблицы,
содержащей символьное поле с явно описанным набором символов WIN1251:
CREATE TABLE TestCHARSET(
Fieldl VARCHAR(255),
Field2
VARCHAR(255) CHARACTER SET winl251);
Здесь Fieldl - поле без
явного указания набора символов, поэтому для него будет использоваться тот набор
символов, который был указан при создании базы данных. Для поля Field2 явно
определено, что в нем будут храниться символы в кодировке WIN 1251.
Помимо
указания набора символов, для символьных полей возможно также указывать порядок
сортировки (COLLATION ORDER), который определяет, как будут сортироваться
символы этого набора данных. Для русского языка существуют два варианта
сортировки - WIN1251 и PXW_CYRL. Подробнее об использовании COLLATION ORDER
рассказано в главе "Русификация InterBase".
Полный список наборов символов и
применяемых для них COLLATION ORDER можно найти в документации [1, гл. 13].
Внимание! В документации на InterBase 6 сказано, что символьных типов
4: помимо указанных выше типов данных существуют еще NCHAR и NCHAR VARYING,
однако ниже в той же документации объясняется, что последние два типа являются
теми же типами CHAR и VARCHAR, только используют по умолчанию набор символов
ISO8859_1. To есть фактически использование псевдотипа NCHAR равносильно
применению CHAR DEFAULT CHARACTER SET ISO8859_1. Аналогично и для NCHAR VARYING,
только там вместо CHAR используется VARCHAR. Очевидно, что применение этих
псевдотипов ориентировано на пользователей в Западной Европе и США, для
поддержки языков в которых и создан набор символов ISO8859_1.
Тип данных BLOB
Тип данных BLOB предназначен для хранения большого количества
данных переменного размера. Тип BLOB позволяет хранить данные, которые не могут
быть помещены в поля других типов, - например, картинки, музыкальные файлы,
видеофрагменты и т. д.
Чтобы определить самое простое поле типа BLOB в
таблице, не нужно ничего сверх того, что обычно требуется для определения поля
любого элементарного типа:
CREATE TABLE
testBLOB(
myBlobField BLOB);
В
результате будет создано поле myBlobField, в котором можно хранить данные
большого размера. Но несмотря на то что поля BLOB по способу определения никак
не отличаются от других, реализация их внутри базы данных значительно
отличается. He-BLOB-поля расположены на странице данных (см. главу "Структура
базы данных InterBase" (ч. 4)) рядом друг с другом, а в случае BLOB на странице
данных хранится только идентификатор BLOB, а сам BLOB располагается на
специальной странице. Именно такая организация данных позволяет хранить данные
нефиксированного размера.
У типа BLOB имеется возможность определять набор
нескольких подтипов и специальных процедур, называемых фильтрами (BLOB
filters), для работы с этими подтипами. Существует несколько предопределенных
подтипов BLOB, которые встроены в InterBase. Все эти подтипы имеют
неотрицательные номера, например subtype 0 - это данные неопределенного типа,
subtype 1 - текст, subtype 2 - BLR (Binary Language Representation, см.
глоссарий и главу "Структура базы данных InterBase") и т. д. Пользователь также
может определять свои подтипы BLOB, которые могут иметь отрицательные значения.
Каждому типу может быть поставлен в соответствие фильтр, который преобразует
поле этого подтипа в другой подтип.
Надо отметить, что использование
BLOB-полей обычно служит альтернативой хранению внешних относительно базы данных
файлов. Что касается фильтров BLOB, то они используются достаточно редко по
причине своей ориентации на узкий класс задач.
Массивы
СУБД InterBase была одной из первых, в которой появились массивы.
Поддержка массивов в базе данных является расширением традиционной реляционной
модели. Наличие массивов позволяет упростить работу со множествами данных одного
типа.
Массив - это совокупность значений одного типа, имеющая общее имя и
позволяющая обратиться к любому элементу массива по его номеру. Массивы в
InterBase могут быть одномерными и многомерными.
Для того чтобы создать в
таблице поле типа массив чисел INTEGER, необходимо написать что-то вроде
следующего:
CREATE TABLE test(
myOneDimArray INTEGER[12],
myTwoDimArray INTEGER[5,4],
myThreeDimArray INTEGER[2,10,8]);
При этом
создадутся 3 поля типа массив: поле myOneDimArray, содержащее одномерный
массив длиной 12 чисел, myTwoDimArray, содержащее двумерный массив (матрицу) 5x4
чисел Integer, и поле myThreeDimArray - трехмерный массив 2x10x8. Надо отметить,
что при таком определении элементы массива нумеруются начиная с единицы, т. е.
первый элемент имеет номер 1, второй - номер 2 и т. д. Если кто-то хочет указать
границы массива самостоятельно, например с 0 до 5, то он должен задать
определение поля так:
myArray INTEGER[0:5]
Массивы реализованы на базе полей типа BLOB, поэтому не следует
опасаться, что многомерный массив "загрязнит" вашу таблицу невероятным
количеством данных: InterBase аккуратно разместит данные массива на отдельных
страницах, чтобы оптимизировать операции ввода-вывода в этих полях.
Как
использовать массивы? Они предоставляют удобный механизм для хранения однотипных
объектов. Однако в 80 % случаев вместо массивов разработчики предпочитают
держать множественные данные в подчиненных (detail) таблицах, поэтому массивы не
так часто используются в клиентских приложениях СУБД InterBase. Этому немало
способствует то, что поставляемые в комплекте с Delphi и C++Builder библиотеки
доступа, такие, как BDE и ГВХ, не имеют возможности работать с массивами. В
документации по InterBase упоминается о возможности работать с массивами с
помощью препроцессора gpre, однако это не самый удобный способ для разработчика
Delphi/C-H-Builder. К счастью, в библиотеке FIBPlus имеется поддержка
полей-массивов в InterBase, о чем подробно рассказано в главе "Специальные
возможности FIBPlus". Клиентская библиотека IBProvider, позволяющая создавать
клиентские приложения для InterBase с помощью средств разработки компании
Microsoft, также поддерживает работу с массивами (см. главу "Разработка
клиентских приложений СУБД InterBase с использованием технологии Microsoft OLE
DB" (ч. 3)).
Заключение
Надо отметить, что невозможно рассказать о типах данных, не
забегая вперед, — настолько они проникают во все ключевые области, связанные с
разработкой приложений баз данных. Поэтому в процессе чтения этой книги стоит
использовать данную главу как справочник, к которому можно обращаться всякий
раз, когда надо освежить в памяти основы InterBase.