Эта страница переведена машинным переводом. Читайте английский оригинал. English

Библиотека IBSurgeon

Типы данных

NOTICE: Этот документ является главой из книги “Мир InterBase”, написанной Алексеем Ковязиным и Сергом Востриковым.

Несмотря на то, что типы данных подробно описаны в документации (см. [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 приведен ниже.

< тип данных> =

{SMALLINT | INTEGER | FLOAT | DOUBLE PRECISION}[ ]

| {DATE | TIME | TIMESTAMP} [ ]

| {DECIMAL | NUMERIC} [( точность [, масштаб])] [ ]

| {CHAR | CHARACTER | CHARACTER VARYING | VARCHAR} [( целое)]

[ ] [CHARACTER SET имя_набора_символов]

| {NCHAR | NATIONAL CHARACTER | NATIONAL CHAR}

[VARYING] [( целое)] [ ]

| BLOB [SUB_TYPE { целое | имя_подтипа}] [SEGMENT SIZE целое]

[CHARACTER SET имя_набора_символов]

| BLOB [(длина_сегмента [, подтип])]

Такие характеристики типов данных, как размер, точность и диапазон возможных значений, подробно описаны в таблице 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 имеет только три механизма хранения любого целочисленного выражения, и все типы, как бы они ни назывались, реализованы в соответствии с этими вариантами.

Вот таблица из [1., которая иллюстрирует, как хранятся различные целочисленные типы (таблица 1.1). Как вы можете видеть, хранение данных в 3-м диалекте отличается для чисел с большой разрядностью:

Таблица 1.1. Хранение чисел с фиксированной точкой

Разрядность Диалект 1 Диалект 3
от 1 до 4 SMALLINT для NUMERIC
INTEGER для DECIMAL
SMALLINT
от 5 до 9 INTEGER INTEGER
от 10 до 18 DOUBLE PRECISION INT64

Теперь мы можем точно сказать, в чем основные различия между типами NUMERIC и DECIMAL: в случае определения поля (или переменной) с малой разрядностью (до четырех) первый хранится как 2-байтовое целое SMALLINT, а второй - как 4-байтовое INTEGER. Таким образом, если разрядность больше четырех, типы DECIMAL и NUMERIC будут эквивалентны.

Обратите внимание на разницу в реализации типов с большой разрядностью в первом и третьем диалектах. В первом диалекте число с фиксированной точкой превращалось из целого в вещественное, где применялись механизмы округления. В третьем диалекте эта особенность была устранена - большие целые числа хранятся действительно как целые - с использованием механизма INT64, который может хранить 64-битные числа в диапазоне +/-2^32. Поэтому лучше хранить данные о денежных средствах в базах данных, созданных с использованием 3-го диалекта. Только использование механизма INT64 гарантирует сохранность мелких денежных остатков.

Типы для хранения даты и времени

Типы для хранения даты и времени изменились в версии InterBase 6.x и его клонах по сравнению с 4.х и 5.х. Чтобы не запутаться в исторических дебрях с этими типами, мы рассмотрим ситуацию в InterBase 6-й версии. Затем на её основе кратко упомянем, что было раньше. Это сделано для тех пользователей, которые до сих пор работают с ранними версиями InterBase. Итак, в InterBase 6.x существует 3 типа для хранения даты и времени - DATE, TIME и TIMESTAMP.

  • Тип DATE хранит даты с точностью до дня. Диапазон возможных значений - от 1 января 100 года н.э. до 29 февраля 32768 года.
  • Тип TIME хранит данные о времени с точностью до десятитысячной доли секунды. Диапазон возможных значений - от 00:00 до 23:59.9999.
  • Тип TIMESTAMP представляет собой комбинацию типов DATE и TIME.

Как работать с датами? Если речь идёт о работе на уровне сервера в хранимых процедурах или триггерах, всё довольно просто - мы всегда можем объявить переменную нужного типа и присвоить ей значение из таблиц и наоборот. Однако необходимо передавать данные из базы данных в приложение и обратно. В этом случае есть два подхода: использовать библиотеки, которые применяют оригинальный формат дат InterBase для доступа к объектам этих типов и преобразуют этот формат в обычные внутриязыковые типы даты/времени (примером такой библиотеки является FIBPlus), либо использовать встроенный в InterBase механизм преобразования дат в строки.

Что делать, если нужно выделить только год или месяц из полной даты? Для этой цели придётся использовать группу функций EXTRACT (доступных во всех клонах InterBase 6.х), позволяющих извлечь из даты только нужную часть. Эти функции используются следующим образом:

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(

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)

Как вы можете видеть, после значения ‘Test’, выбранного из поля c1, появились пробелы. Это означает, что при выборке данных из поля типа 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.

Помимо указания набора символов для символьных полей, можно также указать порядок сортировки (collation), который определяет, как будут сортироваться символы этого набора данных. Для русского языка существует два варианта сортировки - WIN1251 и PXW_CYRL. Более подробно об использовании COLLATION ORDER см. главу «Русификация InterBase».

Полный список наборов символов и COLLATION ORDER, применяемых для них, можно найти в документации [1, глава 13..

Внимание! Согласно документации по InterBase 6, существует 4 символьных типа: помимо вышеупомянутых типов данных существуют ещё 2 - NCHAR и NCHAR VARYING, однако та же документация ниже объясняет, что последние два типа - это те же типы CHAR и VARCHAR, только с набором символов ISO8859.1, используемым по умолчанию. Это означает, что фактически использование псевдотипа NCHAR эквивалентно применению CHAR DEFAULT CHARACTER SET ISO8859.1. Аналогично для NCHAR VARYING, только там вместо CHAR используется VARCHAR. Очевидно, что применение этих псевдотипов предназначено для пользователей в Западной Европе и США, где набор символов ISO8859.1 создан для поддержки языков.

Тип данных BLOB

Тип данных BLOB предназначен для хранения больших объёмов данных переменного размера. Тип BLOB позволяет хранить данные, которые невозможно разместить в полях других типов, - например, изображения, музыкальные файлы, видеофрагменты и т.д. Требования к определению простейшего поля типа BLOB в таблице такие же, как и для определения поля любого элементарного типа:

CREATE TABLE testBLOB(

myBlobField BLOB);

В результате будет создано поле myBlobField, в котором можно хранить данные значительного размера. Несмотря на то, что BLOB-поля не отличаются от других по способу определения, их реализация внутри базы данных сильно отличается. Не-BLOB-поля располагаются на странице данных (см. главу «Структура базы данных InterBase» (часть 4)) рядом друг с другом, а в случае BLOB на странице данных хранится только идентификатор BLOB, сам же BLOB размещается на специальной странице. Такая структура данных позволяет хранить данные нефиксированного размера.

Тип BLOB имеет возможность определить набор нескольких подтипов и специальных процедур, называемых фильтрами (BLOB filters), для работы с этими подтипами. В InterBase встроено несколько предопределённых подтипов BLOB. Все эти подтипы имеют неотрицательные номера, например подтип 0 - данные неопределённого типа, подтип 1 - текст, подтип 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.);

Таким образом, будут созданы три поля типа массив: myOneDimArray - поле, содержащее одномерный массив длиной 12 чисел, myTwoDimArray - содержащее двумерный массив (матрицу) - 5х4 целых чисел, и myThreeDimArray - поле, содержащее трехмерный массив 2х10х8. Следует отметить, что при таком определении элементы массива нумеруются начиная с «единицы», т.е. первый элемент имеет номер 1, второй - номер 2 и т.д. Если кто-то хочет задать границы массива самостоятельно, например от 0 до 5, следует указать определение поля следующим образом:

myArray INTEGER[0:5.

Массивы реализованы на основе полей типа BLOB, поэтому не стоит бояться, что многомерный массив «засорит» вашу таблицу большим количеством данных: InterBase аккуратно разместит данные массива на отдельных страницах для оптимизации операций ввода-вывода в этих полях. Как использовать массивы? Они предоставляют удобный механизм для хранения объектов одного типа. Однако в 80% случаев вместо массивов разработчики предпочитают хранить множественные данные в подчиненных (детальных) таблицах, поэтому массивы не так часто используются в клиентских приложениях СУБД InterBase. Это происходит потому, что библиотеки доступа, поставляемые с Delphi и C++ Builder, такие как BDE и IBX, не умеют работать с массивами. Согласно документации по InterBase, с массивами можно работать с помощью препроцессора gpre, однако это не самый удобный способ для разработчика на Delphi/C++ Builder. К счастью, в библиотеке FIBPlus есть поддержка полей-массивов в InterBase. Подробно об этом можно прочитать в главе «Особые возможности FIBPlus». Клиентская библиотека IBProvider, позволяющая создавать клиентские приложения для InterBase с использованием средств разработки компании Microsoft, также поддерживает работу с массивами (см. главу «Разработка клиентских приложений СУБД InterBase с использованием технологии Microsoft OLE DB» (часть 3)).

Заключение

Следует отметить, что невозможно рассказать о типах данных, не забегая вперед, поскольку они проникают во все ключевые области, связанные с разработкой приложений баз данных. Поэтому при чтении этой книги лучше использовать данную главу как краткий справочник, к которому можно обращаться всякий раз, когда нужно освежить основы InterBase.