Delphi-Help

Главная Статьи FireBird/Interbase Ограничения базы данных

Ограничения базы данных

Оцените материал
(3 голосов)


Ограничения базы данных

Эта статья посвящена ограничениям базы данных InterBase. Ограничения базы данных, - это правила, которые определяют взаимосвязи между таблицами и могут проверять и изменять данные в базе данных Реализованы эти правила в виде особых объектов базы данных.

Главное преимущество использования ограничений состоит в возможности реализовать проверку данных, а значит, и часть бизнес-логики приложения на уровне базы данных, т. е. централизовать и упростить ее, а значит, сделать разработку приложений баз данных проще и надежнее.

Часто начинающие разработчики пренебрегают использованием ограничений базы данных, считая, что они стесняют возможность творчества. Однако на самом деле такое мнение происходит от недостаточного знания теории и практики проектирования баз данных.

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

В рамках данной книги мы не рассматриваем проектирование баз данных, поэтому для получения дополнительной информации по этому вопросу следует обратиться к списку литературы в конце книги. Здесь же мы лишь проведем обзор всех видов ограничений в базе данных InterBase и рассмотрим примеры их применения.

Виды ограничений в базе данных

Существуют следующие виды ограничений в базе данных InterBase:

  • первичный ключ - PRIMARY KEY;
  • уникальный ключ - UNIQUE KEY;
  • внешний ключ - FOREIGN KEY

- может включать автоматические триггеры ON UPDATE и ON DELETE;

  • проверки - CHECK.

Ограничения базы данных бывают двух типов - на основе одного поля и на основе нескольких полей таблицы. Синтаксис обоих видов ограничений приведен ниже.

<col_constraint> = [CONSTRAINT constraint] <constraint_def>  
[ <col_constramt> . . . ] 
<constraint_def> = {UNIQUE | PRIMARY KEY  
| CHECK ( <search_condition>) 
| REFERENCES other_table [( other_col [, other_col ...])]  
[ON DELETE (NO ACTION|CASCADE|SET DEFAULT|SET NULL}]  
[ON UPDATE (NO ACTION CASCADEjSET DEFAULTjSET NULL}]  
} 

Для ограничений на основе нескольких полей синтаксис следующий:

<tconstraint> = [CONSTRAINT constraint] <tconstraint_def> 
[< tconstraint> ...] 
<tconstraint_def> = {{PRIMARY KEY | UNIQUE} ( col [, col ...] ) 
FOREIGN KEY ( col [, col ...] ) REFERENCES other_table [ ( other_col [ , other_col ...] ) ] 
[ON DELETE (NO ACTION|CASCADE SET DEFAULT|SET NULL}] [ON UPDATE {NO ACTION|CASCADE|SET DEFAULT|SET NULL}] 
| CHECK ( <search_condition>)} 

Разница в синтаксисе между ограничениями на основе одного поля и на основе нескольких очевидна - в последних молено указать несколько полей, которые входя i в ограничение. В сд\чае ограничения на основе одного поля все описанные опции относятся только к текущему полю.

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

Пример типичного ограничения

Фактически ограничения на основе одного поля являются частным сл\чаем ограничений на основе нескольких полей.

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

Первичный ключ с использованием синтаксиса ограничения на основе одного поля.

CREATE TABLE testl( 
ID_PK INTEGER CONSTRAINT pktest NOT NULL PRIMARY KEY) ; 

В этом примере создается первичный ключ с именем pktest на поле ID_PK. Получаем весьма компактное описание в одну строчку.

Для той же самой цели можно воспользоваться синтаксисом ограничений на основе нескольких полей:

CREATE TABLE test2( 
ID_PK INTEGER NOT NULL, 
CONSTRAINT pktst PRIMARY KEY (ID_PK)); 

Создание ограничений

Давайте рассмотрим создание ограничений подробнее. Первой в описании общего синтаксиса ограничений идет опция [CONSTRAINT constraint]. Как видите, эта опция взята в квадратные скобки и, значит, необязательна.

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

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

Назначив имя ограничению, необходимо определить его тип. Рассмотрим различные типы ограничений в том порядке, как они указаны в описании общего синтаксиса ограничений.

Первичный и уникальный ключи

Первичные ключи являются одним из основных видов ограничений в базе данных. Они применяются для однозначной идентификации записей в таблице. Допустим, мы храним в базе данных список людей. Вполне вероятно, что могут появиться два (или больше) человека с одинаковыми фамилией, именем и отчеством Как же гарантированно отличить одного человека от другого (конечно. речь идет о том, чтобы отличить одного человека от другого на основании информации, хранящейся в базе данных)?

В данном случае "человек" представлен одной записью в таблице, поэтому можно задаться более общим вопросом — как отличить одну запись в (любой) таблице от другой записи в этой же таблице. Для этого используются ограничения - первичные ключи. Первичный ключ представляет собой одно или несколько полей в таблице, сочетание которых уникально для каждой записи. Для одной таблицы не существует повторяющихся значений первичного ключа.

Уникальные ключи несут аналогичную нагрузку - они также служат для однозначной идентификации записей в таблице. Отличие первичных ключей от уникальных состоит в том, что первичный ключ может быть в таблице только один, а уникальных ключей - несколько. Надо отметить, что и первичный и уникальный ключ могут быть использованы в качестве ссылочной основы для внешних ключей (см. далее).

Синтаксис создания первичного и уникального ключа на основе единственного поля следующий:

<pkukconstraint> = [CONSTRAINT constraint] {PRIMARY KEY | 
UNIQUE} 

Примеры первичных и уникальных ключей:

CREATE TABLE pkuk( 
pk NUMERIC(15,0) NOT NULL PRIMARY KEY, /*первичный ключ*/ 
ukl VARCHAR(SO) NOT NULL UNIQUE,/*уникальный ключ */  
uk2 INTEGER NOT NULL UNIQUE /* еще уникальный ключ */); 

Синтаксис создания первичного и уникального ключей на основе нескольких полей:

<pkuktconstraint> = [CONSTRAINT constraint] {PRIMARY KEY | 
UNIQUE) ( col [, col ...] ) 

Такой синтаксис позволяет создавать ключи на основе комбинации полей. Вот примеры создания первичных и уникальных ключей из нескольких полей:

CREATE TABLE pkuk2( 
Number1 INTEGER NOT NULL, 
Namel VARCHAR(SO) NOT NULL, 
Kol INTEGER NOT NULL, 
Stoim NUMERIC(15,4) NOT NULL, 
CONSTRAINT pkt PRIMARY KEY (Numberl, Namel), /*первичный ключ pkt на 
основе двух полей*/ 
CONSTRAINT uktl UNIQUE (kol, Stoim) ); /*уникальный ключ uktl на основе 
двух полей*/ 

Обратите внимание, что все поля, входящие в состав первичного и уникального ключей, должны быть объявлены как NOT NULL, так как эти ключи не могут иметь неопределенного значения.

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

ALTER TABLE tablename 
ADD [CONSTRAINT constraint] {PRIMARY KEY | UNIQUE) ( col [, col ...] ) 

Давайте рассмотрим пример создания первичного и уникального ключа с помощью ALTER TABLE. Сначала создаем таблицу:

CREATE TABLE pkalter(  
ID1 INTEGER NOT NULL,  
ID2 INTEGER NOT NULL,  
UID VARCHAR(24)); 

Затем добавляем ключи. Сначала первичный:

ALTER TABLE pkalter 
ADD CONSTRAINT pkall PRIMARY KEY (idl, id2); 

Затем уникальный ключ:

ALTER TABLE pkalter 
ADD CONSTRAINT ukal UNIQUE (uid) ; 

Важно отметить, что добавление (а также удаление) ограничений первичных и уникальных ключей к таблице может производить только владелец этой таблицы или системный администратор SYSDBA.

Внешние ключи

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

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

Для хранения такого документа в базе данных создается две таблицы - одна для хранения заголовков накладных, а вторая - для хранения содержимого накладной - записей о товарах и их количестве. Такие таблицы называются главной и подчиненной или таблицей-мастером и деталь-таблицей.

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

Для реализации такого поведения таблица заголовка соединяется с таблицей подробностей с помощью ограничения внешнего ключа.

Давайте рассмотрим смысл наложения ограничений внешнего ключа на примере таблиц, содержащих информацию о накладных.

Для этого создадим две таблицы для хранения накладной - таблицу TITLE для хранения заголовка и таблицу INVENTORY для хранения информации о товарах, входящих в накладную.

CREATE TABLE TITLE( 
IDJTITLE INTEGER NOT NULL Primary Key,  
DateNakl DATE,  
NumNakl INTEGER,  
NoteNakl VARCHAR(255)); 

Обратите внимание на то, что мы сразу определили первичный ключ в таблице заголовка на основе поля ID_TITLE. Остальные поля таблицы TITLE содержат тривиальную информацию о заголовке накладной - дату, номер, примечание.

Теперь определим таблицу для хранения информации о товарах, входящих в накладную:

CREATE TABLE INVENTORY( 
ID_INVENTORY INTEGER NOT NULL PRIMARY KEY,  
FK_TITLE INTEGER NOT NULL,  
ProductName VARCHAR (255),  
Kolvo DOUBLE PRECISION,  
Positio INTEGER); 

Давайте рассмотрим, какие поля входят в таблицу INVENTORY. Во-первых, это ID_INVENTORY - первичный ключ этой таблицы. Затем идет целочисленное поле FK_TITLE, которое служит для ссылки на идентификатор заголовка ID_TITLE в таблице заголовков накладных. Далее идут поля ProductName, Kolvo и Positio. описывающие наименование товара, его количество и позицию в накладной.

Для нашего примера важнее всего поле FK_TITLE. Если мы захотим вывести информацию о товарах определенной накладной, то нам следует воспользоваться следу ющиУ1 запросом, в котором параметр mas_ID_TITLE определяет идентификатор заголовка:

SELECT * 
FROM INVENTORY II 
WHERE II.FK_TITLE=?mas_ID_TITLE 

В сущности, в описываемой ситуации ничто не мешает заполнить таблицу INVENTORY записями, ссылающимися на несуществующие записи в таблице TITEE. Также ничего не препятствует удалению заголовка уже существующей накладной, в результате чего записи о товарах могут стать "бесхозными".

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

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

Такое ограничение можно организовать с помощью внешнего ключа. Для данного примера необходимо наложить ограничения внешнего ключа на поле FK_TITLE и связать его с первичным ключом ID_TITLE в TITEE. Добавить внешний ключ в уже существующую таблицу можно следующей командой:

ALTER TABLE INVENTORY 
ADD CONSTRAINT fktitlel FOREIGN KEY(FK_TITLE) REFERENCES 
TITLE(ID_TITLE) 

Часто при добавлении внешнего ключа возникает ошибка object is in use (объект используется) Дело в ю, что для создания внешнею ключа, необходимо открьпь базу данных в монопольном режиме - чтобы оиювременно не бьпо других пользователей Также нетьзя производить никаких обращений к модифицируемой таблице-это может вызвать object is in use

Здесь INVENTORY - имя таблицы, на которую накладывается ограничение внешнего ключа; fktitlel - имя внешнего ключа; FK_TITLE - поля, составляющие внешний ключ; TITLE — имя таблицы, предоставляющей значения (ссылочную ОСНОВУ) для внешнего ключа; ID_TITLE — поля первичного или уникального ключа в таблице TITLE которые являются ссылочной основой для внешнего ключа.

Полный синтаксис ограничения внешнего ключа (с возможностью создавать ограничения на основании нескольких полей) приведен ниже:

<tconstraint> = [CONSTRAINT constraint] FOREIGN KEY ( col [,  
col } ) REFERENCES other_table [ ( other__col [ , other_col ...] } ] 
[ON DELETE {NO ACTION CASCADE|SET DEFAULT|SET NULL}]  
[ON UPDATE (NO ACTION|CASCADE|SET DEFAULT SET NULL}] 

Как видите, определения содержат большой набор опций. Для начала давайте рассмотрим базовое определение внешнего ключа, которое наиболее часто используется в реальных базах данных, а затем разберем возможные опции.

Чаще всего употребляются декларативная форма ограничения внешнего ключа, когда указывается набор полей (col [, col ...]), которые будут составлять ограничение; таблица other_table, которая содержит в полях [( other_col [, other_col ...]) список возможных значений для внешнего ключа.

Пример такого определения при создании таблицы:

CREATE TABLE Inventory2( 
... 
FK_TABLE INTEGER NOT NULL CONSTRAINT fkinv REFERENCES  
TITLE(ID_TITLE)  
...) ; 

Обратите внимание, что в этом определении опущены ключевые слова FOREIGN KEY, а также подразумевается, что в качестве внешнего ключа будет использоваться единственное поле - FK_TITLE.

А в следующем примере приведена более полная форма создания внешнего ключа одновременно с таблицей:

CREATE TABLE Inventory2( 
... 
FK_TABLE INTEGER NOT NULL, 
CONSTRAINT fkinv FOREIGN KEY (FKJTABLE) REFERENCES 
TITLE(IDJTITLE) 
...) ; 

Использование NULL в полях внешнего ключа

В полях, на основе которых создается внешний ключ, допускается применение NULL-полей. Эта возможность добавлена для разрешения взаимных ссылок. Например, еспи есть две таблицы, ссылающиеся друг на друга с помощью внешних ключей Ьсли не разрешить пустую ссылку (т. е. на NULL) в этих внешних ключах, то в связанные таблицы невозможно будет добавить ни одной записи: чтобы добавить запись в первую таблицу, надо будет иметь запись во второй таблице, и наоборот.

Использование NULL в качестве пустой ссылки позволяет организовать взаимные ссылки двух перекрестно ссылающихся таблиц, а также хранить иерархические структуры в реляционных таблицах - при этом корневые узлы ссылаются на "п\стые" записи (т. е. просто содержат NULL).

Расширенные возможности поддержки ссылочной целостности с помощью внешнего ключа

Обычно вполне достаточно декларативного варианта ограничения внешнего ключа, при котором сервер только следит за тем, чтобы в таблицу с внешним ключом нельзя было вставить некорректные значения или - при попытке сделать это возникает ошибка. Но InterBase позволяет выполнять ряд автоматических действий при изменении/удалении внешнего ключа. Для этого служит следующий набор опций внешнего ключа:

[ON DELETE {NO ACTION|CASCADE|SET DEFAULT|SET NULL}]  
[ON UPDATE {NO ACTIONjCASCADEjSET DEFAULT]SET NULL}] 

Эти опции позволяют определить различные действия при изменении или удалении значения внешнего ключа.

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

ALTER TABLE INVENTORY 
ADD CONSTRAINT fkautodel 
FOREIGN KEY (FK_TITLE) REFERENCES TITLE(ID_TITLE) 
ON DELETE CASCADE 

Фактически для реализации этих действий создается системный триггер, который и выполняет определенные действия. В табл. 1.2 приведено описание происходящих действий при различных опциях (обратите внимание, что опции NO ACTION|CASCADE|SET DEFAULT|SET NULL не могут использоваться совместно в одном предложении ON XXX).

Таблица

Событие

Действие

NO ACTION

CASCADE

SET DEFAULT

SET NULL

ON DELETE

При удалении внешнего ключа ничего не делать - используется по умолчанию

При удалении удалить все связанные записи из подчиненной таблицы

При изменении установить поле внешнего ключа в значение по умолчанию

При изменении установить поле внешнего ключа в NULL

ON UPDATE

При изменении ничего не делать - используется по умолчанию

При изменении записи изменить во всех связанных записях в подчиненных таблицах

При удалении установить поле внешнего ключа в значение по умолчанию

При удалении установить поле внешнего ключа в NULL

Если мы ничего не указываем или указываем NO ACTION, то необходимо позаботиться об изменении внешнего ключа (в случае изменения первичного) самостоятельно, а при удалении первичного ключа предварительно удалить записи из подчиненной таблицы.

Осторожно надо обращаться с опцией CASCADE: неосторожное ее использование может привести к удалению большого количества связанных записей.

Ограничение CHECK

Одним из наиболее полезных ограничений в базе данных является ограничение проверки. Идея его очень проста - проверять вставляемое в таблицу значение на какое-либо условие и, в зависимости от выполнения условия, вставлять или не вставлять данные.

Синтаксис его достаточно прост:

<tconstraint> = [CONSTRAINT constraint] CHECK (  
<search_condition>)} 

Здесь constraint - имя ограничения; <search_condition> - условие поиска, в котором в качестве параметра может участвовать вставляемое/изменяемое значение. Если условие поиска выполняется, то вставка/изменение этого значения разрешаются, если нет - возникает ошибка.

Самый простой пример проверки:

create table checktst( 
 ID integer CHECK(ID>0)); 

Эта проверка устанавливает, больше ли нуля вставляемое/изменяемое значение поля ID, и в зависимости от результата позволяет вставить/изменить новое значение или возбудить исключение (см. главу "Расширенные возможности языка хранимых процедур InterBase" (ч. 1)).

Возможны и более сложные варианты проверок. Полный синтаксис условия поиска <search_condition> следующий:

<search_condition> = {<val> <operator>  
{ <val> | (<select_one>)} 
|<val> [NOT] BETWEEN <val> AND <val>  
| <val> [NOT] LIKE <val> [ESCAPE <val>] 
| <val> [NOT] IN ( <val> [ , <val> ...] | <select_list>) 
| <val> IS [NOT] NULL 
| <val> {[NOT] {= | < | >} | >= | <=}  
{ALL | SOME | ANY} (<select_list>) 
|EXISTS ( <select_expr>)  
| SINGULAR ( <select_expr>)  
| <val> [NOT] CONTAINING <val> 
|<val> [NOT] STARTING [WITH] <val>  
| (<search_condition>)  
| NOT <search_condition> 
| <search_condition> OR <search_condition>  
| <search_condition> AND <search_condition>} 

Таким образом, CHECK предоставляет большой набор опций для проверки вставляемых/изменяемых значений. Необходимо помнить о следующих ограничениях в использовании СНЕК:

Данные в CHECK берутся только из текущей записи. Не следует брать данные для выражения в CHECK из других записей этой же таблицы - они могут быть изменены другими пользователями.

Поле может иметь только одно ограничение CHECK.

Если для описания поля использовался домен, который имеет доменное ограничение CHECK, то его нельзя переопределить на уровне конкретного поля в таблице.

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

Удаление ограничений

Часто приходится удалять различные ограничения по самым разным причинам. Чтобы удалить ограничение, необходимо воспользоваться предложением ALTER TABLE следующего вида:

ALTER TABLE cablename 
DROP CONSTRAINT constraintname 

где constraintname - имя ограничения, которое следует удалять. Если при создании ограничения было задано какое-то имя, то следует им воспользоваться, а если нет, то надо открыть какое-либо средство администрирования InterBase, поискать все связанные с ним ограничения и выяснить, какое системное имя сгенерировал InterBase для искомого ограничения.

Надо отметить, что удалять ограничения может только владелец таблицы или системный администратор SYSDBA.

 

Прочитано 15313 раз
Другие материалы в этой категории: « Индексы Представления »

Авторизация



Счетчики