Глава 18. ОГРАНИЧЕНИЕ ЗНАЧЕНИЙ ВАШИХ ДАННЫХ


В Главе 17 вы узнали, как создаются таблицы. Теперь мы более основательно покажем вам, как вы можете устанавливать ограничения в таблицах. Ограничения это часть определений таблицы, вводящая ограничения на значения, которые вы можете вводить в столбцы. До этого места в данной книге ограничениями на значения, которые вы могли вводить, были тип данных и размер вводимых значений, которые должны быть совместимы с теми столбцами, куда эти значения помещаются (как определено для команды CREATE TABLE или команды ALTER TABLE).

Ограничения дают вам значительно б́ольшие возможности, и скоро вы это увидите. Вы также узнаете, как определять значения по умолчанию. По умолчанию - это значение, которое вставляется автоматически в любой столбец таблицы, когда значение для этого столбца отсутствует в команде INSERT для этой таблицы. NULL это наиболее широко используемое значение по умолчанию, но в этой главе будет показано, как определять и другие значения по умолчанию.

ОГРАНИЧЕНИЕ ТАБЛИЦ

Когда вы создаёте таблицу (или, когда вы её изменяете), вы можете указывать ограничения на значения, которые могут быть введены в поля. Если вы это сделали, SQL будет отклонять любые значения, нарушающие критерии, которые вы определили.

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

ОБЪЯВЛЕНИЕ ОГРАНИЧЕНИЙ

Вы вставляете ограничение столбца в конец имени столбца после типа данных и перед запятой. Ограничение таблицы помещается в конец имени таблицы после последнего имени столбца, но перед заключительной круглой скобкой. Далее показан синтаксис для команды CREATE TABLE, расширенной для включения в неё ограничения:

      CREATE TABLE <table name>
         (<column name> <data type> <column constraint>,
         <column name> <data type> <column constraint> ...
         <table constraint> (<column name>
         [, <column name> ])...);

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

ИСПОЛЬЗОВАНИЕ ОГРАНИЧЕНИЙ ДЛЯ ИСКЛЮЧЕНИЯ ПУСТЫХ (NULL) ЗНАЧЕНИЙ

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

Очевидно, что первичные ключи никогда не должны быть пустыми, поскольку это будет нарушать их функциональные возможности. Кроме того, такие поля как имена требуют в большинстве случаев определённых значений. Например, вы, вероятно, захотите иметь имя для каждого заказчика в таблице Заказчиков. Если вы поместите ключевые слова NOT NULL сразу после типа данных (включая размер) столбца, любая попытка поместить значение NULL в это поле будет отклонена. В противном случае SQL принимает, что NULL разрешен.

Например, давайте улучшим наше определение таблицы Продавцов, не позволяя помещать NULL-значения в столбцы snum или sname:

       CREATE TABLE Salespeople
          (Snum    integer NOT,
            Sname  char (10) NOT,
            city   char (10),
            comm   decimal);

Важно помнить, что любому столбцу с ограничением NOT NULL должно быть установлено значение в каждом предложении INSERT, воздействующем на таблицу. При отсутствии NULL, SQL может не иметь значений для установки в эти столбцы, если, конечно, значение по умолчанию, описанное ранее в этой главе, уже не было назначено. Если ваша система поддерживает использование ALTER TABLE, чтобы добавлять новые столбцы к уже существующей таблице, вы можете, вероятно, помещать ограничение столбцов типа NOT NULL для этих новых столбцов. Однако, если вы предписываете новому столбцу значение NOT NULL, текущая таблица должна быть пустой.

УБЕДИТЕСЬ, ЧТО ЗНАЧЕНИЯ УНИКАЛЬНЫ

В Главе 17 мы обсудили использование уникальных индексов, чтобы заставить поля иметь различные значения для каждой строки. Эта практика осталась с прежних времен, когда SQL поддерживал ограничение UNIQUE. Уникальность это свойство данных в таблице, и поэтому его более логично назвать ограничением этих данных, а не просто свойством логического отличия, связывающим объект данных (индекс). Несомненно, уникальные индексы - один из самых простых и наиболее эффективных методов предписания уникальности. По этой причине некоторые реализации ограничения UNIQUE используют уникальные индексы; то есть они создают индекс, не сообщая вам об этом. Остается фактом, что вероятность беспорядка в базе данных достаточно мала, если вы предписываете уникальность вместе с ограничением.

УНИКАЛЬНОСТЬ КАК ОГРАНИЧЕНИЕ СТОЛБЦА

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

Вот дальнейшее усовершенствование нашей команды создания таблицы Продавцов:

      CREATE TABLE Salespeople
       (Snum    integer NOT NULL UNIQUE,
         Sname  char (10) NOT NULL UNIQUE,
         city   char (10),
         comm   decimal);

Когда вы объявляете поле sname уникальным, убедитесь, что две Mary Smith будут введены различными способами, например, Mary Smith и M. Smith. В то же время это не так уж необходимо с функциональной точки зрения, потому что поле snum в качестве первичного ключа всё равно обеспечит отличие этих двух строк, что проще, нежели помнить, что эти Smith не идентичны. Столбцы (не первичные ключи), чьи значения требуют уникальности, называются ключами-кандидатами или уникальными ключами.

УНИКАЛЬНОСТЬ КАК ОГРАНИЧЕНИЕ ТАБЛИЦЫ

Вы можете также определить группу полей как уникальную с помощью команды UNIQUE ограничения таблицы. Объявление группы полей уникальной отличается от объявления уникальными индивидуальных полей, так как это комбинация значений, а не просто индивидуальное значение, которое обязано быть уникальным.
Уникальность группы это представление порядка так, что бы пары строк со значениями столбцов "a", "b" и "b", "a" рассматривались отдельно одна от другой. Наша БД сделана так, чтобы каждый заказчик был назначен одному, и только одному, продавцу. Это означает, что каждая комбинация номера заказчика (cnum) и номера продавца (snum) в таблице Заказчиков должна быть уникальной. Вы можете убедиться в этом, создав таблицу Заказчиков таким способом:

       CREATE TABLE Customers
          (cnum       integer NOT NULL,
            cname     char (10) NOT NULL,
            city      char (10),
            rating    integer,
            snum      integer NOT NULL,
            UNIQUE    (cnum, snum));

Обратите внимание, что оба поля в ограничении таблицы UNIQUE всё ещё используют ограничение столбца NOT NULL. Если бы мы использовали ограничение столбца UNIQUE для поля cnum, такое ограничение таблицы было бы необязательным. Если значения поля cnum различны для каждой строки, то не может быть двух строк с идентичной комбинацией значений полей cnum и snum. То же самое получится, если мы объявим поле snum уникальным, хотя это и не будет соответствовать нашему примеру, так как продавец будет назначен многочисленным заказчикам. Следовательно, ограничение UNIQUE таблицы наиболее полезно, когда вы не хотите, чтобы отдельные поля были уникальными.

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

         CREATE TABLE Salestotal
          (cnum      integer NOT NULL,
            odate    date NULL,
            totamt   decimal,
            UNIQUE   (snum, odate));

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

            INSERT INTO Salestotal
               SELECT snum, odate, SUM (amt)
                  FROM Orders
                  GROUP BY snum, odate;

ОГРАНИЧЕНИЕ ПЕРВИЧНЫХ КЛЮЧЕЙ

До этого мы воспринимали первичные ключи исключительно как логические понятия. Хоть мы и знаем, что такое первичный ключ и как он должен использоваться в любой таблице, мы не в курсе, "знает" ли об этом SQL. Поэтому мы использовали ограничение UNIQUE или уникальные индексы в первичных ключах, чтобы предписывать им уникальность. В более ранних версиях языка SQL это было необходимо и могло выполняться данным способом. Однако теперь SQL поддерживает первичные ключи непосредственно ограничением Первичный Ключ (PRIMARE KEY). Это ограничение может быть доступным или недоступным в вашей системе. PRIMARY KEY может ограничивать таблицы или их столбцы. Это ограничение работает так же, как и ограничение UNIQUE, за исключением случая, когда только один первичный ключ (для любого числа столбцов) может быть определен для данной таблицы.  Имеется также различие между первичными ключами и уникальностью столбцов в способе их использования с внешними ключами, о которых будет рассказано в Главе 19. Синтаксис и определение их уникальности - те же, что и для ограничения UNIQUE. Первичные ключи не могут позволить значений NULL. Это означает, что, подобно полям в ограничении UNIQUE, любое поле, используемое в ограничении PRIMARY KEY, должно уже быть объявлено NOT NULL. Имеется улучшенный вариант создания нашей таблицы Продавцов:

       CREATE TABLE Salestotal
          (snum     integer NOT NULL PRIMARY KEY,
            sname   char(10) NOT NULL UNIQUE,
            city    char(10),
            comm    decimal);

Как видите, уникальность (UNIQUE) полей может быть объявлена для той же самой таблицы. Лучше всего помещать ограничение PRIMARY KEY в поле (или в поля), которое будет образовывать ваш уникальный идентификатор строки, и сохранять ограничение UNIQUE для полей, которые должны быть уникальными логически (такие как номера телефона или поле sname), а не для идентификации строк.

ПЕРВИЧНЫЕ КЛЮЧИ БОЛЕЕ ЧЕМ ОДНОГО ПОЛЯ

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

Мы можем применить ограничение таблицы PRIMARY KEY для пар:

        CREATE TABLE  Namefield
        (firstname      char (10) NOT NULL,
          lastname      char (10) NOT NULL
          city          char (10),
          PRIMARY KEY  (firstname, lastname));
 

Одна проблема в этом подходе - мы можем вынудить появление уникальности, например, введя Mary Smith и M. Smith. Это может ввести в заблуждение, потому что ваши служащие могут не знать, кто из них кто. Обычно более надежный способ определения числового поля, которое могло бы отличать одну строку от другой, - иметь первичный ключ и применять ограничение UNIQUE для двух имен полей.

ПРОВЕРКА ЗНАЧЕНИЙ ПОЛЕЙ

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

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

Давайте рассмотрим ещё раз таблицу Продавцов. Столбец комиссионных выражается десятеричным числом и поэтому может быть умножен непосредственно на сумму приобретений, в результате чего будет получена сумма комиссионных (в долларах) продавца с установленным справа значком доллара ($). Кто-то может использовать понятие процента, однако можно ведь об этом и не знать. Если человек введёт по ошибке 14 вместо .14 для указания, в процентах, своих комиссионных, это будет расценено как 14.0 , что является законным десятеричным значением и будет нормально воспринято системой. Чтобы предотвратить эту ошибку, мы можем наложить ограничение CHECK столбца, чтобы убедиться, что вводимое значение меньше 1.

       CREATE TABLE Salespeople
          (snum      integer NOT NULL PRIMARY KEY,
            sname    char(10) NOT NULL UNIQUE,
            city     char(10),
            comm     decimal CHECK (comm < 1));

ИСПОЛЬЗОВАНИЕ CHECK ДЛЯ ПРЕДОПРЕДЕЛЕНИЯ ДОПУСТИМОГО ВВОДИМОГО ЗНАЧЕНИЯ

Мы можем также использовать ограничение CHECK, чтобы защитить от ввода в поле определённых значений, и таким образом предотвратить ошибку. Например, предположим, что городами, в которых мы имеем офисы сбыта, являются Лондон, Барселона, Сан-Хосе и Нью-Йорк. Если вам известны все продавцы, работающие в каждом из этих офисов, нет необходимости разрешать ввод других значений. Если же нет, использование ограничения может предотвратить опечатки и другие ошибки.

    CREATE TABLE Salespeople
        (snum     integer NOT NULL UNIQUE,
         sname    char(10) NOT NULL UNIQUE,
         city     char(10) CHECK,
        (city IN ('London', 'New York', 'San Jose', 'Barselona')),
         comm     decimal CHECK (comm < 1));

Конечно, если вы собираетесь сделать это, вы должны быть уверены, что ваша компания не открыла уже других новых офисов сбыта. Большинство программ баз данных поддерживают команду ALTER TABLE (см. Главу 17), которая позволяет изменять определение таблицы, даже когда она находится в использовании. Однако изменение или удаление ограничений не всегда возможно для этих команд, даже там, где это вроде бы поддерживается.
Если вы используете систему, которая не может удалять ограничения, вы должны будете создавать (CREATE) новую таблицу и передавать информацию из старой таблицы в неё всякий раз, когда вы хотите изменить ограничение. Конечно же, вы не захотите делать это часто и со временем вообще перестанете это делать. Создадим таблицу Заказов:

  CREATE TABLE Orders
    (onum     integer NOT NULL UNIQUE,
     amt      decimal,
     odate    date NOT NULL,
     cnum     integer NOT NULL,
     snum     integer NOT NULL);

Как мы уже говорили в Главе 2, тип DATЕ (ДАТА) широко поддерживается, но не является частью стандарта ANSI. Что же делать, если мы используем БД, которая, следуя ANSI, не распознаёт тип DATЕ? Если мы объявим поле odate с любым числовым типом, мы не сможем использовать слэш (/) или тире (-) в качестве разделителя. Так как печатаемые номера это символы ASCII, мы можем объявить тип поля date - CHAR. Основная проблема в том, что мы должны будем использовать одинарные кавычки всякий раз, когда ссылаемся на значение поля odate в запросе. Нет более простого решения этой проблемы там, где тип DATЕ стал таким популярным. В качестве иллюстрации, давайте объявим поле odate типом CHAR. Мы можем, как минимум, наложить на него наш формат с ограничением CHECK:

       CREATE TABLE Orders
         (onum         integer NOT NULL UNIQUE,
           amt         decimal,
           odate       char (10) NOT NULL CHECK (odate LIKE '--/--/----'),
           cnum        NOT NULL,
           snum        NOT NULL);

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

ПРОВЕРКА УСЛОВИЙ, БАЗИРУЮЩИХСЯ НА НЕСКОЛЬКИХ ПОЛЯХ

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

       CREATE TABLE Salespeople
         (snum      integer NOT NULL UNIQUE,
           sname    char (10) NOT NULL UNIQUE,
           city     char(10),
           comm     decimal,
           CHECK    (comm < .15 OR city = 'Barcelona'));

Как видите, два различных поля должны быть проверены, чтобы определить, верен предикат или нет. Имейте в виду, что это - два разных поля одной и той же строки. Хотя вы можете использовать несколько полей, SQL не может проверить более одной строки одновременно. Вы не можете, например, использовать ограничение CHECK, чтобы удостовериться, что все комиссионные в данном городе одинаковы. Чтобы сделать это, SQL должен всякий раз, просматривая другие строки таблицы, когда вы модифицируете или вставляете строку, видеть, что значение комиссионных указано для текущего города. SQL этого делать не умеет.
Фактически вы могли бы использовать сложное ограничение CHECK для вышеупомянутого, если бы знали заранее, каковы должны быть комиссионные в разных городах. Например, вы могли бы установить такое ограничение:

     CHECK ((comm = .15 AND city = 'London')
        OR  (comm = .14 AND city = 'Barcelona')
        OR  (comm = 11  AND city = 'San Jose')..)

Мы подали вам идею. Чем налагать такой комплекс ограничений, вы могли бы просто использовать представление с предложением WITH CHECK OPTION, которое имеет все эти условия в своем предикате (смотри в Главах 20 и 21 информацию о представлении и о WITH CHECK OPTION).

Пользователи могут обращаться к представлению таблицы вместо самой таблицы. Одним из преимуществ этого будет то, что процедура изменения в ограничении не будет такой болезненной или трудоёмкой. Представление с WITH CHECK OPTION - хороший заменитель ограничению CHECK, что будет показано в Главе 21.

УСТАНОВКА ЗНАЧЕНИЙ ПО УМОЛЧАНИЮ

Когда вы вставляете строку в таблицу без указания в ней значений для каждого поля, SQL должен иметь значение по умолчанию для включения его в определённое поле, или же команда будет отклонена. Наиболее общим значением по умолчанию является NULL. Это - значение по умолчанию для любого столбца, которому не было дано ограничение NOT NULL или который имеет другое значение по умолчанию. Значение DEFAULT (ПО УМОЛЧАНИЮ) указывается в команде CREATE TABLE тем же способом, что и ограничение столбца, хотя, с технической точки зрения, значение DEFAULT - не ограничительного свойства: оно не ограничивает значения, которые вы можете вводить, а просто определяет, что может случиться, если вы не введёте любое из них.

Предположим, что вы работаете в офисе Нью-Йорка и подавляющее большинство ваших продавцов живут в Нью-Йорке. Вы можете указать Нью-Йорк в качестве значения поля city по умолчанию для вашей таблицы Продавцов:

CREATE TABLE Salespeople (snum integer NOT NULL UNIQUE,
	sname char(10) NOT NULL UNIQUE,
	city char(10) DEFAULT = 'New York',
	comm decimal CHECK (comm < 1);

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

Другой способ использования значения по умолчанию - использовать его как альтернативу NULL. Так как NULL (фактически) является false при любом сравнении, ином, нежели IS NULL, он может быть исключён с помощью большинства предикатов.

Иногда вам нужно видеть пустые значения ваших полей, не обрабатывая их каким-то определённым образом. Вы можете установить значения по умолчанию, типа нуль или пробел, которые функционально меньше по значению, чем просто не установленное значение - пустое значение (NULL). Различие между ними и обычным NULL в том, что SQL будет обрабатывать их так же, как и любое другое значение.

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

         SELECT *
            FROM Customers
            WHERE rating < = 100;

Однако, если вы назначили значение по умолчанию = 000, в поле rating, заказчики без оценок будут выбраны наряду с другими. Приоритет каждого метода зависит от ситуации. Если вы будете делать запрос с помощью поля оценки, то захотите ли вы включить строки без значений, или исключите их?
Другая характеристика значений по умолчанию этого типа позволит объявить поле оценки как NOT NULL. Если вы используете его по умолчанию, чтобы избежать значений = NULL, то это, вероятно, хорошая защита от ошибок. Вы можете также использовать ограничения UNIQUE или PRIMARY KEY в этом поле. Если вы сделаете это, то имеете в виду, что только одна строка одновременно может иметь значение по умолчанию. Любую строку, которая содержит значение по умолчанию нужно будет модифицировать, прежде чем другая строка с установкой по умолчанию будет вставлена. Это не так, как при обычном использовании значений по умолчанию, поэтому ограничения UNIQUE и PRIMARY KEY (особенно последнее) обычно не устанавливаются для строк со значениями по умолчанию.

РЕЗЮМЕ

Вы теперь владеете несколькими способами управления значениями, которые могут быть введены в ваши таблицы. Вы можете использовать ограничение NOT NULL, чтобы исключать NULL; ограничение UNIQUE, чтобы вынуждать все значения в группе из одного или более столбцов отличаться друг от друга; ограничение PRIMARY KEY, для того чтобы делать в основном то же самое что и UNIQUE, но с различным окончанием, и наконец ограничение CHECK - для определения ваших собственных специальных условий, чтобы значения, встреченные перед ними, могли бы быть введены.

Кроме того, вы можете использовать предложение DEFAULT, которое будет автоматически вставлять значение по умолчанию в любое поле с именем, не указанным в INSERT, так же, как вставляется значение NULL, когда предложение DEFAULT не установлено и отсутствует ограничение NOT NULL.
Ограничения FOREIGN KEY или REFERENCES, о которых вы узнаете в Главе 19, очень похожи на них, за исключением того, что они связывают группу из одного или более полей с другой группой, и таким образом, сразу воздействуют на значения, которые могут быть введены в любую из этих групп.

РАБОТА СО SQL

1. Создайте таблицу Заказов так, чтобы все значения полей onum, а также
   все комбинации полей cnum и snum, отличались друг от друга и чтобы
   значения NULL исключались из поля даты.

2. Создайте таблицу Продавцов так, чтобы комиссионные по умолчанию
   составляли 10%, не разрешались значения NULL, поле snum являлось первичным ключом
   и чтобы все имена были в алфавитном порядке между A и M включительно
   (учитывая, что все имена будут напечатаны в верхнем регистре).

3. Создайте таблицу Заказов учётом того, что поле onum
   больше, чем поле cnum, а cnum больше, чем snum. Запрещены значения
   NULL в любом из этих трех полей.

(См. ответы в Приложении A.)