Включение описаний таблицы
Имеется несколько атрибутов таких определений, о которых нужно поговорить. Причина, по которой мы решили сделать поля cnum и snum в таблице Заказов единым внешним ключом — это гарантия того, что для каждого заказчика, содержащегося в Заказах, продавец, кредитующий этот заказ, тот же, что и указанный в таблице Заказчиков. Чтобы создать такой внешний ключ, мы были бы должны поместить ограничение таблицы UNIQUE в два поля таблицы Заказчиков, даже если оно необязательно для самой этой таблицы. Пока поле cnum в этой таблице имеет ограничение PRIMARY KEY, оно будет уникально в любом случае, и, следовательно, невозможно получить еще одну комбинацию поля cnum с каким-то другим полем.
Создание внешнего ключа таким способом поддерживает целостность базы данных, даже если при этом произойдет внутреннее прерывание по ошибке и вам будет запрещено кредитовать любого продавца, иного, чем тот, который назначен именно этому заказчику.
С точки зрения поддержания целостности базы данных, внутренние прерывания (или исключения) конечно же, нежелательны. Если вы их допускаете и в то же время хотите поддерживать целостность вашей базы данных, вы можете объявить поля snum и cnum в таблице Заказов независимыми внешними ключами этих полей в таблице Продавцов и таблице Заказчиков, соответственно.
Фактически, использование поля snum в таблице Заказов, как мы это делали, необязательно, хотя это полезно было сделать для разнообразия. Поле cnum, связывая каждый заказ в таблице Заказов с заказчиком в таблице Заказчиков, должно всегда быть общим, чтобы находить правильное поле snum для данного Заказа (не разрешая никаких исключений). Это означает, что мы записываем фрагмент информации — какой заказчик назначен к какому продавцу дважды, и нужно будет выполнять дополнительную работу чтобы удостовериться, что обе версии согласуются.
Если мы не имеем ограничения внешнего ключа как сказано выше, эта ситуация будет особенно проблематична, потому что каждый заказ нужно будет проверять вручную (вместе с запросом), чтобы удостовериться что именно соответствующий продавец кредитовал каждую соответствующую продажу. Наличие такого типа информационной избыточности в вашей базе данных, называется денормализация (denormalization), что нежелательно в идеальной реляционной базе данных, хотя практически и может быть разрешено. Денормализация может заставить некоторые запросы выполняться быстрее, поскольку запрос в одной таблице выполняется всегда значительно быстрее, чем в объединении.
Действие ограничений
Как такие ограничения воздействуют на возможность и невозможность Вами использовать команды модификации DML? Для полей, определенных как внешние ключи, ответ довольно простой: любые значения, которые вы помещаете в эти поля командой INSERT или UPDATE должны уже быть представлены в их родительских ключах. Вы можете помещать пустые (NULL) значения в эти поля, несмотря на то, что значения NULL не позволительны в родительских ключах, если они имеют ограничение NOT NULL. Вы можете удалять (DELETE) любые строки с внешними ключами, не используя родительские ключи вообще.
Поскольку затронут вопрос об изменении значений родительского ключа, ответ, по определению ANSI, еще проще, но возможно несколько более ограничен: любое значение родительского ключа, на который ссылаеются с помощью значения внешнего ключа, не может быть удалено или изменено. Это означает, например, что вы не можете удалить заказчика из таблицы Заказчиков, пока он еще имеет Заказы в таблице Заказов. В зависимости от того, как вы используете эти таблицы, это может быть или желательно, или нежелательно. Однако, это конечно лучше, чем иметь систему, которая позволит вам удалить заказчика с текущими Заказами и оставить таблицу Заказов ссылающейся на несуществующих заказчиков. Смысл этой системы ограничения в том, что создатель таблицы Заказов, используя таблицу Заказчиков и таблицу Продавцов как родительские ключи, может наложить значительные ограничения на действия в этих таблицах. По этой причине, вы не сможете использовать таблицу, которой вы не распоряжаетесь (т.е. не вы ее создавали и не вы являетесь ее владельцем), пока владелец (создатель) этой таблицы специально не передаст вам на это право (что объясняется в Главе 22).
Имеются некоторые другие возможные действия изменения родительского ключа, которые не являются частью ANSI, но могут быть найдены в некоторых коммерческих программах. Если вы хотите изменить или удалить текущее ссылочное значение родительского ключа, имеется по существу три возможности:
· Вы можете ограничить или запретить изменение (способом ANSI), обозначив, что изменения в родительском ключе ограничены.
· Вы можете сделать изменение в родительском ключе и тем самым сделать изменения во внешнем ключе автоматическим, что называется — каскадным изменением.
· Вы можете сделать изменение в родительском ключе, и установить внешний ключ в NULL автоматически (полагая, что NULL разрешен во внешнем ключе), что называется — пустым изменением внешнего ключа.
Даже в пределах этих трех категорий, вы можете не захотеть обрабатывать все команды модификации таким способом. INSERT, конечно, к делу не относится. Он помещает новые значения родительского ключа в таблицу, так что ни одно из этих значений не может быть вызвано в данный момент. Однако вы можете захотеть позволить модификациям быть каскадными, но без удалений, и наоборот. Лучшей может быть ситуация, которая позволит вам определять любую из трех категорий, независимо от команд UPDATE и DELETE. Мы будем следовательно ссылаться на эффект модификации (update effects) и эффект удаления (delete effects), которые определяют, что случится, если вы выполните команды UPDATE или DELETE в родительском ключе. Эти эффекты, о которых мы говорили, называются:
Ограниченные (RESTRICTED) изменения,
Каскадируемые (CASCADES) изменения, и
Пустые (NULL) изменения.
Фактические возможности вашей системы должны быть в строгом стандарте ANSI — это эффекты модификации и удаления оба, автоматически ограниченные — для идеальной ситуации, описанной выше. В качестве иллюстрации мы покажем несколько примеров того, что вы можете делать с полным набором эффектов модификации и удаления. Конечно, эффекты модификации и удаления, являющиеся нестандартными средствами, испытывают недостаток в стандартном синтаксисе. Синтаксис, который мы используем здесь, прост в написании и будет служить в дальнейшем для иллюстрации функций этих эффектов.
Для полноты эксперимента, позволим себе предположить, что вы имеете причину изменить поле snum таблицы Продавцов в случае, когда наша таблица Продавцов изменяет разделы (обычно изменение первичных ключей это не то, что мы рекомендуем делать практически; просто это еще один из доводов для имеющихся первичных ключей, которые не умеют делать ничего другого кроме как действовать как первичные ключи: они не должны изменяться). Когда вы меняете номер продавца, вы хотите чтобы были сохранены все его заказчики. Однако, если этот продавец покидает свою фирму или компанию, вы можете не захотеть удалить его заказчиков, при удалении его самого из базы данных. Взамен, вы захотите убедиться, что заказчики назначены кому-нибудь еще. Чтобы сделать это вы должны указать UPDATE с Каскадируемым эффектом, и DELETE с Ограниченным эффектом.
CREATE TABLE Customers (
cnum integer NOT NULL PRIMARY KEY,
cname char(10) NOT NULL,
city char(10),
rating integer,
snum integer REFERENCES Salespeople,
UPDATE OF Salespeople CASCADES,
DELETE OF Salespeople RESTRICTED);
Если вы теперь попробуете удалить Peel из таблицы Продавцов, команда будет не допустима, пока вы не измените значение поля snum заказчиков Hoffman и Clemens для другого назначенного продавца. С другой стороны, вы можете изменить значение поля snum для Peel на 1009, и Hoffman и Clemens будут также автоматически изменены.
Третий эффект — Пустые (NULL) изменения. Бывает, что когда продавцы оставляют компанию, их текущие Заказы не передаются другому продавцу. С другой стороны, вы хотите отменить все Заказы автоматически для заказчиков, чьи счета вы удалите. Изменив номера продавца или заказчика, можно просто передать их ему. Пример ниже показывает, как вы можете создать таблицу Заказов с использованием этих эффектов.
CREATE TABLE Orders (
onum integer NOT NULL PRIMARY KEY,
amt decimal,
odate date NOT NULL
cnum integer NOT NULL REFERENCES Customers
snum integer REFERENCES Salespeople,
UPDATE OF Customers CASCADES,
DELETE OF Customers CASCADES,
UPDATE OF Salespeople CASCADES,
DELETE OF Salespeople NULLS);
Конечно, в команде DELETE с эффектом Пустого изменения в таблице Продавцов, ограничение NOT NULL должно быть удалено из поля snum.
Внешние ключи, которые ссылаются
обратно к их подчиненным таблицам
Как было упомянуто ранее, ограничение FOREIGN KEY может представить имя этой частной таблице, как таблицы родительского ключа. Далеко не будучи простой, эта особенность может пригодиться. Предположим, что мы имеем таблицу Employees с полем manager (администратор). Это поле содержит номера каждого из служащих, некоторые из которых являются еще и администраторами.
Но так как каждый администратор в то же время остается служащим, то он, естественно, будут также представлен в этой таблице. Давайте создадим таблицу, где номер служащего (столбец с именем empno), объявляется как первичный ключ, а администратор, как внешний ключ, будет ссылаться на нее:
CREATE TABLE Employees (
empno integer NOT NULL PRIMARY KEY,
name char(10) NOT NULL UNIOUE,
manager integer REFERENCES Employees);
Так как внешний ключ это ссылаемый первичный ключ таблицы, список столбцов может быть исключен. Имеется содержание этой таблицы:
EMPNO | NAME | MANAGER |
Terrence | ||
Atali | NULL | |
McKenna | ||
Collier |
Как вы можете видеть, каждый из них (но не Atali), ссылается на другого служащего в таблице как на своего администратора. Atali, имеющий наивысший номер в таблице, должен иметь значение установленное в NULL. Это дает другой принцип справочной целостности. Внешний ключ, который ссылается обратно к частной таблице, должен позволять значения NULL. Если это не так, как бы вы могли вставить первую строку?
Даже если эта первая строка ссылается к себе самой, значение родительского ключа должно уже быть установлено, когда вводится значение внешнего ключа. Этот принцип будет верен, даже если внешний ключ ссылается обратно к частной таблице не напрямую, а с помощью ссылки к другой таблице, которая затем ссылается обратно к таблице внешнего ключа. Предположим, что наша таблица Продавцов имеет дополнительное поле, которое ссылается на таблицу Заказчиков так, что каждая таблица ссылается на другую, как показано в следующих операторах CREATE TABLE:
CREATE TABLE Salespeople (
snum integer NOT NULL PRIMARY KEY,
sname char(10) NOT NULL,
city char(10),
comm declmal,
cnum integer REFERENCES Customers);
CREATE TABLE Customers (
cnum integer NOT NULL PRIMARY KEY,
cname char(10) NOT NULL,
city char(10),
rating integer,
snum integer REFERENCES Salespeople);
Это называется — перекрестной ссылкой.
SQL поддерживает это теоретически, но практически это может составить проблему. Любая таблица из этих двух, созданная первой, является ссылочной таблицей, которая еще не существует для другой. В интересах обеспечения перекрестной ссылки, SQL фактически позволяет это, но никакая таблица не будет пригодна для использования, пока они обе находятся в процессе создания. С другой стороны, если эти две таблицы создаются различными пользователями, проблема становится еще более трудной. Перекрестная ссылка может стать полезным инструментом, но она не без неоднозначности и опасностей. Предшествующий пример, например, не совсем пригоден для использования, потому что он ограничивает продавца одним заказчиком, и кроме того совсем необязательно использовать перекрестную ссылку, чтобы достичь этого. Мы рекомендуем, чтобы вы были осторожны в его использовании и анализировали, как ваши программы управляют эффектами модификации и удаления, а также процессами привилегий и диалоговой обработки запросов перед тем, как вы создаете перекрестную систему справочной целостности (привилегии и диалоговая обработка запросов будут обсуждаться, соответственно, в Главах 22 и 23).
Резюме
Теперь вы имеете достаточно хорошее понятие об управлении справочной целостностью. Основная идея в том, что все значения внешнего ключа ссылаются к указанной строке родительского ключа. Это означает, что каждое значение внешнего ключа должно быть представлено один раз, и только один раз, в родительском ключе. Всякий раз, когда значение помещается во внешний ключ, родительский ключ проверяется, чтобы удостовериться, что его значение представлено; иначе команда будет отклонена. Родительский ключ должен иметь Первичный Ключ (PRIMARY KEY) или Уникальное(UNIQUE) ограничение, гарантирующее, что значение не будет представлено более чем один раз. Попытка изменить значение родительского ключа, которое в настоящее время представлено во внешнем ключе, будет вообще отклонена. Ваша система может, однако, предложить вам выбор, чтобы получить значение внешнего ключа установленного в NULL или для получения нового значения родительского ключа и указания, какой из них может быть получен независимо для команд UPDATE и DELETE.
Этим завершается наше обсуждение команды CREATE TABLE. Далее мы представим вас другому типу команды — CREATE. В Главе 20 вы обучитесь представлению объектов данных, которые выглядят и действуют подобно таблице, но в действительности являются результатами запросов. Некоторые функции ограничений могут также выполняться представлениями, так что вы сможете лучше оценить вашу потребность к ограничениям, после того, как вы прочитаете следующие три главы.
Работа с SQL
1. Создайте таблицу с именем Cityorders. Она должна содержать такие же поля onum, amt, и snum что и таблица Заказов, и такие же поля cnum и city, что и таблица Заказчиков, так что заказ каждого заказчика будет вводиться в эту таблицу вместе с его городом. Поле оnum будет первичным ключом Cityorders. Все поля в Cityorders должны иметь ограничения при сравнении с таблицами Заказчиков и Заказов. Допускается, что родительские ключи в этих таблицах уже имеют соответствующие ограничения.
2. Усложним проблему. Переопределите таблицу Заказов следующим образом: добавьте новый столбец с именем prev, который будет идентифицирован для каждого Заказа, поле onum предыдущего Заказа для этого текущего заказчика. Выполните это с использованием внешнего ключа ссылающегося на саму таблицу Заказов. Внешний ключ должен ссылаться также на поле cnum заказчика, обеспечивающего определенную предписанную связь между текущим Заказом и ссылаемым.
(См. Приложение A для ответов.)
Введение в представления
ПРЕДСТАВЛЕНИЕ (VIEW) — ОБЪЕКТ ДАННЫХ, КОТОРЫЙ не содержит никаких данных его владельца. Это — тип таблицы, чье содержание выбирается из других таблиц с помощью выполнения запроса. Поскольку значения в этих таблицах меняются, то автоматически, их значения могут быть показаны представлением.
В этой главе вы узнаете, что такое представления, как они создаются и немного об их возможностях и ограничениях. Использование представлений, основанных на улучшенных средствах запросов, таких как объединение и подзапрос, разработанных очень тщательно, в некоторых случаях даст больший выигрыш по сравнению с запросами.
Что такое представление?
Типы таблиц, с которыми вы имели дело до сих пор, назывались — базовыми таблицами. Это — таблицы, которые содержат данные. Однако имеется другой вид таблиц — представления. Представления — это таблицы, чье содержание выбирается или получается из других таблиц. Они работают в запросах и операторах DML точно также как и основные таблицы, но не содержат никаких собственных данных.
Представления подобны окнам, через которые вы просматриваете информацию (как она есть, или в другой форме, как вы потом увидите), которая фактически хранится в базовой таблице. Представление — это фактически запрос, который выполняется всякий раз, когда представление становится темой команды. Вывод запроса при этом в каждый момент становится содержанием представления.
Команда CREATE VIEW
Вы создаете представление командой CREATE VIEW. Она состоит из слов CREATE VIEW (СОЗДАТЬ ПРЕДСТАВЛЕНИЕ), имени представления, которое нужно создать, слова AS (КАК), и далее запроса, как в следующем примере:
CREATE VIEW Londonstaff
AS SELECT *
FROM Salespeople
WHERE city = 'London';
Теперь Вы имеете представление, называемое Londonstaff. Вы можете использовать это представление точно так же, как и любую другую таблицу. Она может быть запрошена, модифицирована, вставлена в, удалена из, и соединена с, другими таблицами и представлениями. Давайте сделаем запрос такого представления (вывод показан в Рисунке 20.1):
Select *
FROM Londonstaff;
=============== SQL Execution Log ============
| SELECT * |
| FROM Londonstaff; |
| ==============================================|
| snum sname city comm |
| ------ ---------- ----------- ------- |
| 1001 Peel London 0.1200 |
| 1004 Motika London 0.1100 |
===============================================
Рисунок 20.1. Представление Londonstaff
Когда вы приказываете SQL выбрать (SELECT) все строки (*) из представления, он выполняет запрос, содержащийся в определении Londonstaff, и возвращает все из его вывода.
Имея предикат в запросе представления, можно вывести только те строки из представления, которые будут удовлетворять этому предикату. Вы могли бы вспомнить, что в Главе 15, вы имели таблицу, называемую Londonstaff, в которую вы вставляли это же самое содержание (конечно, мы понимаем, что таблица не слишком велика; если это так, вы будете должны выбрать другое имя для вашего представления). Преимущество использования представления, по сравнению с использованием основной таблицы в том, что представление будет модифицировано автоматически всякий раз, когда таблица, лежащая в его основе изменяется.
Содержание представления не фиксировано, и переназначается каждый раз, когда вы ссылаетесь на представление в команде. Если вы добавите завтра другого, живущего в Лондоне продавца, он автоматически появится в представлении.
Представления значительно расширяют управление вашими данными. Это превосходный способ дать публичный доступ к некоторой, но не всей информации в таблице. Если вы хотите, чтобы ваш продавец был показан в таблице Продавцов, но при этом не были показаны комиссии других продавцов, вы могли бы создать представление с использованием следующего оператора (вывод показан в Рисунке 20.2)
CREATE VIEW Salesown
AS SELECT snum, sname, city
FROM Salespeople:
=============== SQL Execution Log ============
| SELECT * |
| FROM Salesown; |
| ==============================================|
| snum sname city |
| ------ ---------- ----------- |
| 1001 Peel London |
| 1002 Serres San Jose |
| 1004 Motika London |
| 1007 Rifkin Barcelona |
| 1003 Axelrod New York |
===============================================
Рисунок 20.2. Представление Salesown
Другими словами, это представление — такое же, как для таблицы Продавцов, за исключением того, что поле comm не упоминалось в запросе, и, следовательно, не было включено в представление.