Определение ограничений целостности. Примеры использования элемента. Как реальные данные могут привести вас к неудаче

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

  • Столбцы какого типа и размера будут составлять каждую из таблиц, какие требуется выбрать имена для столбцов таблиц?
  • Какие столбцы могут содержать значение NULL ?
  • Будут ли использованы ограничения целостности , значения по умолчанию и правила для столбцов?
  • Необходимо ли индексирование столбцов, какие типы индексов будут применены для конкретных столбцов?
  • Какие столбцы будут входить в первичные и внешние ключи .

Для создания таблиц в среде MS SQL Server используется команда:

<определение_таблицы> ::= CREATE TABLE [ имя_базы_данных.[владелец]. | владелец. ]имя_таблицы (<элемент_таблицы>[,...n])

<элемент_таблицы> ::= {<определение_столбца>} | <имя_столбца> AS <выражение> | <ограничение_таблицы>

Обычно владельцем таблицы (dbo) является тот, кто ее создал.

<Выражение> задает значение для вычисляемого столбца . Вычисляемые столбцы - это виртуальные столбцы, т. е. физически в таблице они не хранятся и вычисляются с использованием значений столбцов той же таблицы. В выражении для вычисляемого столбца могут присутствовать имена обычных столбцов, константы и функции, связанные одним или несколькими операторами. Подзапросы в таком выражении участвовать не могут. Вычисляемые столбцы могут быть включены в раздел SELECT при указании списка столбцов, которые должны быть возвращены в результате выполнения запроса. Вычисляемые столбцы не могут входить во внешний ключ , для них не используются значения по умолчанию. Кроме того, вычисляемые столбцы не могут участвовать в операциях INSERT и DELETE .

<определение_столбца> ::= { имя_столбца <тип_данных>} [ [ DEFAULT <выражение> ] | [ IDENTITY (начало, шаг) ]]] [<ограничение_столбца>][...n]]

В определении столбца обратим внимание на параметр IDENTITY , который указывает, что соответствующий столбец будет столбцом-счетчиком . Для таблицы может быть определен только один столбец с таким свойством. Можно дополнительно указать начальное значение и шаг приращения. Если эти значения не указываются, то по умолчанию они оба равны 1. Если с ключевым словом IDENTITY указано NOT FOR REPLICATION , то сервер не будет выполнять автоматического генерирования значений для этого столбца, а разрешит вставку в столбец произвольных значений.

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

<ограничение_столбца>::= [ CONSTRAINT имя_ограничения ] { [ NULL | NOT NULL ] | [ {PRIMARY KEY | UNIQUE } [ CLUSTERED | NONCLUSTERED ] [ WITH FILLFACTOR=фактор_заполнения ] [ ON {имя_группы_файлов | DEFAULT } ] ] ] | [ [ FOREIGN KEY ] REFERENCES имя_род_таблицы [(имя_столбца_род_таблицы) ] [ ON DELETE { CASCADE | NO ACTION } ] [ ON UPDATE { CASCADE | NO ACTION } ] [ NOT FOR REPLICATION ]] | CHECK [ NOT FOR REPLICATION](<лог_выражение>) } <ограничение_таблицы>::= { [ {PRIMARY KEY | UNIQUE } [ CLUSTERED | NONCLUSTERED ] {(имя_столбца [,...n])} ] |FOREIGN KEY[(имя_столбца [,...n])] REFERENCES имя_род_таблицы [(имя_столбца_род_таблицы [,...n])] [ ON DELETE { CASCADE | NO ACTION } ] [ ON UPDATE { CASCADE | NO ACTION } ] | NOT FOR REPLICATION ] | CHECK [ NOT FOR REPLICATION ] (лог_выражение) }

Рассмотрим отдельные параметры представленных конструкций, связанные с ограничениями целостности данных . Ограничения целостности имеют приоритет над триггерами, правилами и значениями по умолчанию. К ограничениям целостности относятся ограничение первичного ключа PRIMARY KEY , ограничение внешнего ключа FOREIGN KEY , ограничение уникальности UNIQUE , ограничение значения NULL , ограничение на проверку CHECK .

Ограничение первичного ключа (PRIMARY KEY)

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

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

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

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

Ограничение внешнего ключа (FOREIGN KEY)

Ограничение внешнего ключа - это основной механизм для поддержания ссылочной целостности между таблицами реляционной базы данных. Столбец дочерней таблицы, определенный в качестве внешнего ключа в параметре FOREIGN KEY , применяется для ссылки на столбец родительской таблицы, являющийся в ней первичным ключом . Имя родительской таблицы и столбцы ее первичного ключа указываются в предложении REFERENCES . Данные в столбцах, определенных в качестве внешнего ключа , могут принимать только такие же значения, какие находятся в связанных с ним столбцах первичного ключа родительской таблицы. Совпадение имен столбцов для связи дочерней и родительской таблиц необязательно. Первичный ключ может быть определен для столбца с одним именем, в то время как столбец, на который наложено ограничение FOREIGN KEY , может иметь совершенно другое имя. Единственным требованием остается соответствие столбцов по типу и размеру данных.

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

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

Столбцы внешнего ключа могут содержать значение NULL , однако проверка на ограничение FOREIGN KEY игнорируется. Внешний ключ может быть проиндексирован, тогда сервер будет быстрее отыскивать нужные данные. Внешний ключ определяется как при создании, так и при изменении таблиц .

Ограничение ссылочной целостности задает требование, согласно которому для каждой записи в дочерней таблице должна иметься запись в родительской таблице. При этом изменение значения столбца связи в записи родительской таблицы при наличии дочерней записи блокируется, равно как и удаление родительской записи (запрет каскадного изменения и удаления), что гарантируется параметрами ON DELETE NO ACTION и ON UPDATE NO ACTION , принятыми по умолчанию. Для разрешения каскадного воздействия следует использовать параметры ON DELETE CASCADE и ON UPDATE CASCADE .

annotation?, (simpleType | complexType)?, (unique | key | keyref)*

(Знак? указывает на то, что элемент может появляться ноль или один раз, знак * указывает на то, что элемент может появляться ноль, один или больше раз внутри элемента element .)

Атрибуты

Атрибут Описание
id Не обязательный. Определяет уникальный идентификатор для элемента
name Не обязательный. Определяет имя элемента. Этот атрибут требуется, если родительским элементом является элемент schema
ref Не обязательный. Ссылается на имя другого элемента. Атрибут ref может включать префикс пространства имен. Этот атрибут нельзя использовать, если родительским элементом является элемент schema
type Не обязательный. Определяет либо имя встроенного типа данных, либо имя элемента simpleType или complexType
substitutionGroup Не обязательный. Определяет имя элемента, который может быть замещен этим элементом. Этот атрибут нельзя использовать, если родительским элементом является не элемент schema
default Не обязательный. Определяет значение элемента по умолчанию (может использоваться только если содержимое элемента простого типа или текст)
fixed Не обязательный. Определяет фиксированное значение элемента (может использоваться только если содержимое элемента простого типа или текст)
form Не обязательный. Определяет форму элемента. Значение "qualified" указывает на то, что этот элемент должен уточняться префиксом пространства имен. Значение "unqualified" указывает на то, что этот элемент не требует уточнения префиксом пространства имен. Значением по умолчанию является значение атрибута elementFormDefault атрибута элемента schema. Этот атрибут нельзя использовать, если родительским элементом является элемент schema
maxOccurs Не обязательный. Определяет, сколько раз максимально может появляться элемент в родительском элементе. Значением может быть любое целое число >= 0, если же нужно снять лимит на использование, то следует указать ключевое слово "unbounded". Значение по умолчанию 1. Этот атрибут нельзя использовать, если родительским элементом является элемент schema
minOccurs Не обязательный. Определяет, сколько раз минимально может появляться элемент в родительском элементе. Значением может быть любое целое число >= 0. Значение по умолчанию 1. Этот атрибут нельзя использовать, если родительским элементом является элемент schema
nillable Не обязательный. Определяет, можно ли элементу присваивать явное нулевое значение nil. Значение true позволяет элементу устанавливать атрибут nil в значение true. Атрибут nil определен как часть пространства имен XML схемы. Значение по умолчанию false
abstract Не обязательный. Определяет, можно ли использовать этот элемент в документе. Значение true определяет, что элемент не может использоваться в документе. Вместо этого, на месте данного элемента должен появляться другой элемент, атрибут substitutionGroup которого содержит имя с префиксом (QName) этого элемента. Значение по умолчанию false
block

Не обязательный. Препятствует использованию элемента, который имеет заданный тип наследования, вместо данного элемента. Может принимать значение #all или список из расширений/ограничений/замещений:

  • extension - запрещает использование элементов, производных при помощи расширения
  • restriction - запрещает использование элементов, производных при помощи ограничения
  • substitution - запрещает использование замещенных элементов
  • #all - запрещает использование элементов, производных при помощи всех методов наследования
final

Не обязательный. Запрещает указанный метод наследования элемента. Может принимать значение #all или список из расширений/ограничений:

  • extension - запрещает наследование элемента при помощи расширения
  • restriction - запрещает наследование элемента при помощи ограничения
  • #all - запрещает все методы наследования
любые атрибуты Не обязательный. Любые другие атрибуты вне пространства имен схемы

Примеры использования элемента

Пример №1
В следующем примере декларируется XML схема с четырьмя простыми элементами "fname", "lname", "age" и "dateborn":

Пример №2
В следующем примере декларируется XML схема с элементом "note" сложного типа. Элемент "note" содержит четыре простых элемента - "to", "from", "heading" и "body":

Пример №3
Следующий пример аналогичен примеру №2. Разница лишь в том, что для ссылки на имена элементов используется атрибут ref:

Элемент key определяет значение атрибута или элемента как ключ (ненулевой, уникальный и всегда присутствующий) в содержащем элементе формируемого документа. Элемент имеет следующий формат:

< префикс : key

id = ID

name = NCName

(annotation ?, (selector , field + ))

префикс : key>

Атрибуты id иname unique .

key , selector и field :

xs : key >

В этом примере поля " forename " и" surname " используются в качестве ключа с именемfullName .

Элемент keyref определяет, что значение атрибута или элемента соответствует значениям заданного ключа или уникального элемента и имеет следующий формат:

< префикс : keyref

id = ID

name = NCName

refer = QName

(annotation ?, (selector , field +))

префикс: keyref >

Атрибуты id иname имеют тот же смысл, что и для элементаunique , а обязательный атрибутrefer определяет имя ключа или уникального элемента, определенного в данной или другой схеме.

Пример использования элементов keyref , selector и field :

В этом примере поля "@ first " и"@ last " соответствуют значениям ключа определенного выше элемента с именемfullName .

4.2.9. Определение комплексного типа в схемеXml

Комплексный элемент типа complexType – это элементXML, содержащий другие элементы и/или атрибуты. Существует четыре вида комплексных элементов:

    пустые элементы;

4.2.9.1. Формат определения элементаcomplexType

Определение комплексного типа имеет следующий формат:

< префикс : complexType

name = NCName

id = ID

abstract = boolean : false

mixed = boolean : false

block = (#all | List (extension | restriction ))

final = (#all |List (extension | restriction ))

(annotation ?, (simpleContent | complexContent |

((sequence | group | all | choice )?,

((attribute | attributeGroup )*, anyAttribute ?))))

префикс : complexType>

Атрибуты name иid в элементеcomplexType имеют тот же смысл, что и в элементеattribute , а атрибуты abstract , block и final – тот же смысл, что и в элементе element . Атрибут mixed определяет, могут ли символьные данные появляться между дочерними элементами определения комплексного типа.

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

    элемент simpleContent ;

    элемент complexContent ;

    один из элементов group , all , choice или sequence (0 или 1 раз), один из элементов attribute или attributeGroup (0 и более раз) и элемент anyAttribute (0 или 1 раз).

Рассмотрим дочерние элементы элемента complexType .

4.2.9.2. Элементы sequence, any, choice, all и group

Элемент sequence определяет, что дочерние элементы данного элемента должны появляться в заданной последовательности и имеет следующий формат:

< префикс : sequence

id = ID

maxOccurs = (nonNegativeInteger | unbounded ) : 1

minOccurs = nonNegativeInteger : 1

(annotation ?, (element | group | choice | sequence | any )*)

префикс : sequence >

Необязательный атрибут id maxOccurs иminOccurs задают максимальное и минимальное значение для количества включений данной последовательности в родительский элемент.

Пример использования элемента sequence:

xs : element >

В примере задан элемент с именем personinfo (данные о сотруднике). Этот элемент содержит дочерние элементыfirstname иlastname , а также последовательность элементовchild (ребенок) – эта последовательность может отсутствовать или содержать неограниченное количество элементовchild .

Как видно, из объявления элемента sequence , в содержимом этого элемента может быть задан дочерний элементany . Этот элемент позволяет расширить документXMLэлементами, которые не определены в схеме, и имеет следующий формат:

< префикс : any

id = ID

maxOccurs = (nonNegativeInteger | unbounded ) : 1

minOccurs = nonNegativeInteger : 1

namespace = ((##any | ##other ) | List (anyURI |

(##targetNamespace | ##local ))) : ##any

processContents = (strict | lax | skip ) : strict

(annotation ?)

префикс : any >

Необязательный атрибут id задает уникальный идентификатор элемента, а необязательные атрибутыmaxOccurs иminOccurs задают максимальное и минимальное значение для количества включений элементаany в родительский элемент. Необязательный атрибутnamespace определяет пространства имен, содержащих элементы, которые могут быть использованы в родительском элементе, и может иметь одно из следующих значений:

    ## any – допустимы элементы из любого пространства имен;

    ## other – могут присутствовать элементы из любого пространства имен, отличного от пространства имен, заданного для родительского элемента;

    ## local – элементы должны задаваться не из пространства имен;

    ## target Namespace – могут присутствовать элементы из пространства имен, заданного для родительского элемента;

    List (anyURI | (## targetNamespace |## local ))) – могут присутствовать элементы из заданного списка.

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

    strict – процессорXMLдолжен получить схему необходимых пространств имен и проверить действительность элементов;

    lax – аналогичноstrict , но если схема не может быть получена, сообщение об ошибке не генерируется;

    skip – процессорXMLпропускает проверку действительности элементов.

any :

< xs : element name =" person ">

xs : complexType >

xs : element >

С помощью использования элемента any можно расширить содержимое элементаperson (после элементаlastname ) любым другим элементом.

Элемент choice разрешает задавать в родительском элементе только один из присутствующих в списке элементов и имеет следующий формат:

< префикс : choice

id = ID

maxOccurs = (nonNegativeInteger | unbounded ) : 1

minOccurs = nonNegativeInteger : 1

(annotation ?, (element | group | choice | sequence | any )*)

префикс : choice >

Необязательный атрибут id задает уникальный идентификатор элемента, а необязательные атрибутыmaxOccurs иminOccurs задают максимальное и минимальное значение для количества включений выбранного элемента в родительский элемент.

Пример использования элемента choice:

< / xs:element>

Элемент person должен содержать один из описанных ранее элементов:employee (постоянный сотрудник) илиfreelance (контрактник).

Элемент all определяет, что элементы, содержащийся в нем, могут появляться в родительском элементе в любом порядке, и каждый элемент может появляться 0 или 1 раз. Элемент имеет следующий формат:

< префикс : all

id = ID

maxOccurs = 1 : 1

minOccurs = (0 | 1 ) : 1

(annotation ?, element *)

префикс : all >

Необязательный атрибут id задает уникальный идентификатор элемента. Необязательный атрибутmaxOccurs должен иметь значение 1, а необязательный атрибутminOccurs задает минимальное значение для количества включений выбранного элемента в родительский элемент (0 или1 ).

Пример использования элемента all :

< xs : element name =" person ">

Элементы firstname иlastname могут появляться в элементе в элементеperson в любом порядке и один или оба элемента могут отсутствовать.

Элемент group используется для задания группы элементов в определении комплексного типа и имеет следующий формат:

< префикс : group

id = ID

name = NCName

ref = QName

maxOccurs = nonNegativeInteger | unbounded : 1

minOccurs = nonNegativeInteger : 1

(annotation ?, (all | choice | sequence ))

префикс : group>

Атрибут name задает имя элемента. Он является обязательным только в том случае, если родительским элементом данного элемента является элементschema или элементredefine . Атрибутref задает ссылку на имя другой группы (атрибутыname иref являются взаимоисключающими). Необязательные атрибутыmaxOccurs иminOccurs задают максимальное и минимальное значение для количества включений элементов группы в родительский элемент.

Пример использования элемента group :

< xs : group name =" custGroup ">

xs : complexType >

В этом примере определена группаcustGroup , состоящая из двух элементов:customer (покупатель) иorderDetails (детали заказа), а затем эта группа использована (с помощью ссылки) в определении комплексного типаorderType .

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

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

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

Ограничение FOREIGN KEY используется в команде CREATE TABLE (или ALTER TABLE (предназначена для модификации стуктуры таблицы), содержащей поле, которое объявлено внешним ключом. Родительскому ключу дается имя, на которое имеется ссылка внутри ограничения FOREIGN KEY .

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

Синтаксис ограничения таблицы FOREIGN KEY :

FOREIGN KEY REFERENCES

[ ]

Первый список столбцов - это список из одного или более столбцов таблицы, которые отделены запятыми и будут созданы или изменены этой командой.

Pktable - это таблица содержащая родительский ключ. Она может быть таблицей, которая создается или изменяется текущей командой.

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

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

FOREIGN KEY Пример 1

CREATE TABLE Student
(Kod_stud integer NOT NULL PRIMARY KEY ,
Kod_spec integer NOT NULL,

Adres char(50),
Ball decimal),
FOREIGN KEY (Kod_spec) REFERENCES Spec (Kod_spec)
);

При использовании ALTER TABLE вместо CREATE TABLE, для применения ограничения FOREIGN KEY , значения, указываемые во внешнем ключе и родительском ключе, должны быть в состоянии ссылочной целостности. Иначе команда будет отклонена.

Используя ограничение FOREIGN KEY таблицы или столбца, можно не указывать список столбцов родительского ключа, если родительский ключ имеет ограничение PRIMARY KEY . Естественно, в случае ключей со многими полями, порядок столбцов во внешних и первичных ключах должен совпадать, и, в любом случае, принцип совместимости между двумя ключами все еще применим.

FOREIGN KEY Пример 2

CREATE TABLE Student (
Kod_stud integer NOT NULL PRIMARY KEY ,
Fam char(30) NOT NULL UNIQUE,
Adres char(50),
Ball decimal),
Kod_spec integer REFERENCES Spec
);

Поддержание ссылочной целостности требует некоторых ограничений на значения, которые могут быть представлены в полях, объявленных как внешний ключ и родительский ключ. Родительский ключ должен быть структурен, чтобы гарантировать, что каждое значение внешнего ключа будет соответствовать одной указанной строке. Это означает, что он (ключ) должен быть уникальным и не содержать никаких пустых значений(NULL).

Этого не достаточно для родительского ключа в случае выполнения такого требования, как при объявлении внешнего ключа. SQL должен быть уверен, что двойные значения или пустые значения (NULL) не были введены в родительский ключ. Следовательно необходимо убедиться, что все поля, которые используются как родительские ключи, имеют или ограничение PRIMARY KEY или ограничение UNIQUE, наподобие ограничения NOT NULL.

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

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

FOREIGN KEY Пример 3

CREATE TABLE payment (
sh_payout integer,
sh_eml integer,
date_payout date,
summ_payout real,
FOREIGN KEY (sh_eml) REFERENCES k_sotr2 (eid)
);

В данном примере FOREIGN KEY столбец sh_eml связывается со столбцом eid из таблицы k_sotr2.

Элемент xsl:key определяет в преобразовании именованный ключ.

Элемент верхнего уровня xsl:key определяет в преобразовании ключ именем, заданным в значении обязательного атрибута name , значением которого для каждого узла документа, соответствующего обязательному паттерну match , будет результат вычисления выражения, заданного в обязательном атрибуте use . Ни атрибут use , ни атрибут match не могут содержать переменных.

Синтаксис

XSLT 1.0

Атрибуты

  • name - обязательный
  • match - обязательный
  • use - обязательный атрибут, выражение XPath, которое определяет свойство индексируемых узлов, используемое для выборки узлов из индекса.

Спецификация

XSLT 2.0

Атрибуты

  • name - обязательный атрибут, определяет имя ключа.
  • match - обязательный атрибут, выражение XPath, которое определяет узлы, индексируемые по данному ключу.
  • use - необязательный атрибут, выражение XPath, которое определяет свойство индексируемых узлов, используемое для выборки узлов из индекса. В XSLT 2.0 этот атрибут является необязательным. Элемент может содержать конструктор последовательности, который создает или выбирает индексируемые узлы.
  • collation - необязательный атрибут, определяет последовательность упорядочения, используемую для проверки равенства двух значений ключа.

XSLT 3.0

Описание и примеры

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

Листинг 8.19. Входящий документ

Пусть входящий документ представляет собой список объектов (элементов item), каждый из которых имеет имя (атрибут name) и источник (атрибут source). Требуется сгруппировать объекты по своим источникам и получить документ приблизительно следующего вида.

Листинг 8.20. Требуемый результат

Первым шагом на пути решения этой задачи является формулировка в терминах XSLT предложения “сгруппировать объекты по своим источникам”. Источник каждого объекта определяется его атрибутом source , значит множество объектов, принадлежащих одному источнику “ а ”, будет определяться путем выборки

/items/item[@source="a"]

Тогда для каждого элемента item в его группу войдут элементы, которые будут выбраны выражением

/items/item[@source=current()/@source]

Попробуем использовать этот факт в следующем шаблоне:

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

Preceding-sibling::item[@source=current()/@source]

будет возвращать пустое множество.

С небольшими добавлениями искомое преобразование целиком будет иметь вид.

Листинг 8.21. Преобразование

Бесспорно, решение было несложным, но довольно громоздким. Самым же узким местом в этом преобразовании является обращение к элементам item источника текущего элемента посредством сравнения атрибутов source .

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

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

Попробуем разобраться в смысле фразы “узел обладает определенными свойствами”. Очевидно, это означает, что для этого узла выполняется некое логическое условие, иначе говоря, некий предикат обращается в “истину”.

Однако какого именно типа условия мы чаще всего проверяем? Анализируя различные классы задач, можно придти к выводу, что в большинстве случаев предикаты являются равенствами - выражениями, которые обращаются в “истину” тогда и только тогда, когда некоторый параметр узла, не зависящий от текущего контекста, равен определенному значению. В нашем примере смысл предиката на самом деле состоит не в том, чтобы проверить на истинность выражение @source=current()/@source , а в том, чтобы проверить на равенство @source и current()/@source .

Если переформулировать это для общего случая, то нам нужно выбрать не те узлы, для которых истинно выражение A=B , скорее нужно выбрать те, для которых значение A равно значению B . Иначе говоря, узел будет идентифицироваться значением B своего свойства A . И если мы заранее вычислим значения свойств A , проблема поиска узлов в дереве сведется к классической проблеме поиска элементов множества (в нашем случае - узлов дерева) по определенным значениям ключей (в нашем случае - значениями свойств A).

Чтобы пояснить это, вернемся к нашему примеру: мы ищем элементы item со значением атрибута source , равным заданному. Свойством, идентифицирующим эти элементы, в данном случае будут значения их атрибутов source , которые мы можем заранее вычислить и включить в табл. 8.2.

Таблица 8.2. Значения атрибута source элементов item

Идентификатор (значение атрибута source) Элемент item
a
a
a
b
b
b
c
c

Таким образом, значение “ c ” идентифицирует объекты с именами D и G , а значение “ a ” - объекты с именами A , C и H , причем находить соответствующие элементы в таблице по их ключевому свойству не составляет никакого труда.

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

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

Пример 1

В нашем примере элементы item идентифицируются значениями своих атрибутов source . Для их идентификации мы можем определить ключ с именем src следующим образом:

Следуя строгому определению, данному в спецификации языка, ключом называется тройка вида (node, name, value) , где node - узел, name - имя и value - строковое значение ключа. Тогда элементы xsl:key , включенные в преобразование, определяют множество всевозможных ключей обрабатываемого документа. Если этому множеству принадлежит ключ, состоящий из узла x , имени y и значения z , говорят, что узел x имеет ключ с именем y и значением z или что ключ y узла x равен z .

Пример 2

Ключ src из предыдущего примера определяет множество, которое состоит из следующих троек:

(, "src", "a") (, "src", "b") (, "src", "a") (, "src", "c") ... (, "src", "a")

В соответствии с нашими определениями мы можем сказать, что элемент

имеет ключ с именем “ src ” и значением “ b ” или что ключ “ src ” элемента

равен “ a ”.

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

Функция key

Ниже приведена синтаксическая конструкция данной функции:

Node-set key(string, object)

Итак, элементы xsl:key нашего преобразования определили множество троек (node, name, value) . Функция key(key-name, key-value) выбирает все узлы x такие, что значение их ключа с именем key-name (первым аргументом функции) равно key-value (второму аргументу функции).

Пример 3

Значением выражения key("src", "a") будет множество элементов item таких, что значение их ключа “ src ” будет равно “ a ”. Попросту говоря, это будет множество объектов источника “ a ”.

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

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

Определение множества ключей

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

атрибут use показывает, что значением ключа src элемента item будет значение атрибута source . Но что можно сказать о следующем определении:

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

Узел x обладает ключом с именем y и строковым значением z тогда и только тогда, когда в преобразовании существует элемент xsl:key такой, что одновременно выполняются все нижеперечисленные условия:

  • узел x соответствует паттерну, указанному в его атрибуте match ;
  • значение его атрибута name равно имени y ;
  • результат u вычисления выражения, указанного в значении атрибута use в контексте текущего множества, состоящего из единственного узла x , удовлетворяет одному из следующих условий:
    • u является множеством узлов и z равно одному из их строковых значений;
    • u не является множеством узлов и z равно его строковому значению.

Без сомнения, определение не из простых. Но как бы мы действовали, если бы физически создавали в памяти множество ключей? Ниже представлен один из возможных алгоритмов:

  • для каждого элемента xsl:key найти множество узлов документа, удовлетворяющих его паттерну match (множество X);
  • для каждого из найденных узлов (x ? X) вычислить значение выражения атрибута use (значение u(x));
  • если u(x) является множеством узлов (назовем его Ux), то для каждого uxi ? Ux создать ключ (x, n, string(uxi)) , где n - имя ключа (значение атрибута name элемента xsl:key);
  • если u(x) является объектом другого типа (назовем его ux), создать ключ (x, n, string(ux)) .

Пример 4

Найдем множество ключей, создаваемое определением

Имена всех ключей будут одинаковы и равны “ src ”. Множество x узлов, удовлетворяющих паттерну item , будет содержать все элементы item обрабатываемого документа. Значением выражения, заданного в атрибуте use , будет множество всех узлов атрибутов каждого из элементов item . Таким образом, множество узлов будет иметь следующий вид:

(, "src", "a") (, "src", "A") (, "src", "b") (, "src", "В") (, "src", "а") (, "src", "С") (, "src", "с") (, "src", "D") ... (, "src", "a") (, "src", "H")

В итоге функция key("src", "a") будет возвращать объекты с именами A , C и H , а функция key("src", "A") - единственный объект с именем A (поскольку ни у какого другого элемента item нет атрибута со значением “ A ”).

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

Использование нескольких ключей в одном преобразовании

В случае, когда к узлам в преобразовании нужно обращаться по значениям различных свойств, можно определить несколько ключей - каждый со своим именем. Например, если мы хотим в одном случае обращаться к объектам, принадлежащим одному источнику, а во втором - к объектам с определенными именами, мы можем определить в документе два ключа - один с именем src , второй - с именем name:

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

(, "src", "а") (, "name", "А") (, "src", "b") (, "name", "В") (, "src", "a") (, "name", "С") (, "src", "с") (, "name", "D") ... (, "src", "a") (, "name", "H")

В этом случае функция key("src", "a") возвратит объекты с именами A , C и H , а функция key("name", "A") - объект с именем A .

Имя ключа является расширенным именем. Оно может иметь объявленный префикс пространства имен, например

В этом случае функция key(key-name, key-value) будет возвращать узлы, значение ключа с расширенным именем key-name которых равно key-value . Совпадение расширенных имен определяется как обычно - по совпадению локальных частей и URI пространств имен.

Использование нескольких определений одного ключа

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

Пример 5

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

То есть для тех элементов item , у которых есть атрибут source , значением ключа будет значение этого атрибута, для тех же элементов, у которых атрибута source нет, его значением будет “ a ”.

Для входящего документа вида

...

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

(, "src", "а") (, "src", "b") (, "src", "а") (, "src", "c") ... (, "src", "a") (, "src", "a") (, "src", "a") (, "src", "a")

Функция key("src", "a") возвратит объекты с именами A , C , H , I , J и K .

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

Это определение позволит по значению “ #default ” обращаться к объектам, принадлежащим источнику по умолчанию.

Использование множеств узлов в функции key

Функция key принимает на вход два аргумента: первым аргументом является строка, задающая имя ключа, в то время как вторым аргументом может быть объект любого типа. В том случае, если аргумент key-value в функции key(key-name, key-value) является множеством узлов, функция key возвратит все узлы, имеющие ключ key-name со значением, равным хотя бы одному из строковых значений узла множества key-value .

Пример 6

Предположим, что источники объектов будут сгруппированы следующим образом:

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

Key("src", sources/source/@name)

Множество узлов, выбираемое путем sources/source/@name , будет содержать атрибуты name элементов source . Их строковые значения будут равны a и c , значит, наше выражение возвратит множество элементов item , значение атрибута source которых равно либо a либо c .

Использование ключей в нескольких документах

Ключи, определенные в преобразовании, могут использоваться для выбора узлов в различных обрабатываемых документах. Функция key возвращает узлы, которые принадлежат текущему документу, то есть документу, содержащему текущий узел. Значит, для того, чтобы выбирать узлы из внешнего документа, необходимо сделать текущим узлом один из узлов этого внешнего документа. Контекстный документ может быть легко изменен элементом xsl:for-each , например, для того, чтобы текущим документом стал документ a.xml , достаточно написать

Пример 7

Предположим, что нам нужно выбрать объекты, принадлежащие источнику a , причем принадлежность объектов определена в двух внешних документах, a.xml и b.xml .

Листинг 8.22. Входящий документ

Листинг 8.23. Документ a.xml

Листинг 8.24. Документ b.xml

Листинг 8.25. Преобразование

Листинг 8.26. Выходящий документ

Составные ключи

В теории реляционных баз данных существует такое понятие, как составной ключ. Согласно определению К. Дж. Дейта [Дейт 1999], составной ключ - это “потенциальный ключ; состоящий из более чем одного атрибута”.

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

Главная проблема состоит в том, что значение ключа в XSLT всегда является строкой, одним из самых примитивных типов. И выбирать множества узлов можно только по одному строковому значению за один раз. Ничего похожего на key(key-name, key-value-1, key-value-2, ...) для выбора узлов, первое свойство которых равно key-value-1 , второе - key-value-2 и так далее, XSLT не предоставляет.

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

Пример 8

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

  • найти объект с определенным именем и источником;
  • найти объекты с определенным именем;
  • найти объекты с определенным источником.

Листинг 8.27. Входящий документ

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

Листинг 8.28. Входящий документ

Листинг 8.29. Выходящий документ

У приведенного здесь способа формирования ключа есть определенные ограничения: необходимо иметь априорную информацию о строковых значениях каждого из свойств, составляющих наш композитный ключ для того, чтобы корректно формировать его строковые представления. Например, если бы в приведенном выше документе имена объектов и источников могли бы содержать символ “ - ”, было бы непонятно, к какому объекту относится составной ключ “ a-b-c ”: к объекту с источником a-b и именем c или к объекту с источником a и именем b-c . К счастью, в большинстве случаев такая информация имеется, и генерировать составные ключи не очень сложно.

Функция key в паттернах

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

IdKeyPattern::= "id" "(" Literal ")" | "key" "(" Literal "," Literal ")"

Функция key(key-name, key-value) в паттерне будет соответствовать узлам, значение ключа key-name которых равняется или принадлежит объекту key-value . Это позволяет использовать возможности ключей при проверке узлов на соответствие образцу.

Пример 9

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

Листинг 8.30. Шаблон, использующий функцию key в паттерне

Этот шаблон будет применяться к любым узлам, имеющим ключ src со значением a .



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: