Ограничение PRIMARY KEY.

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.

Рассмотрены вопросы, необходимые разработчику для создания клиент-серверных приложений с использованием СУБД Firebird, явившейся развитием СУБД Borland Interbase 6. Содержится обзор концепций и моделей архитектуры клиент/сервер, а также практические рекомендации по работе с клиентскими библиотеками Firebird. Детально описаны особенности типов данных SQL, язык манипулирования данными (Data Manipulation Language, DML), а также синтаксис и операторы языка определения данных (Data Definition Language, DDL). Большое внимание уделено описанию транзакций и приведены советы по их использованию при разработке приложений. Описано программирование на стороне клиента и сервера написание триггеров и хранимых процедур, создание и использование событий базы данных, обработка ошибок в коде на сервере и многое другое. Материал сопровождается многочисленными примерами, советами и практическими рекомендациями.

Для разработчиков баз данных

Книга:

Ограничение PRIMARY KEY

Разделы на этой странице:

Ограничение PRIMARY KEY

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

Если вы пришли в Firebird из СУБД, которые поддерживают концепцию "первичного индекса" для определения ключа (обычно основанные на файлах системы, такие как Paradox, Access и MySQL), то Firebird и мир стандартов SQL вам понятны. Первичный ключ является не индексом, а ограничением. Одним из правил для такого ограничения является то, что ограничение должно иметь определенный уникальный индекс из одного или более связанных с ним непустых элементов.

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

ВНИМАНИЕ! Не надо импортировать существующий "первичный индекс" из наследуемой системы, основанной на файлах, или создавать такой индекс в ожидании объявления ограничения первичного ключа. Firebird не может накладывать ограничение первичного ключа поверх существующего индекса - по крайней мере в существующих версиях, включая 1.5, - а оптимизатор запросов не будет правильно работать при дублировании индексов.

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

ВНИМАНИЕ! Если вы конвертируете базу данных в Firebird из любого другого источника за исключением InterBase или Oracle, то вы должны обратить особое внимание на схему в отношении имен и ограничений первичного ключа.

Хотя само ограничение PRIMARY KEY не является ссылочным ограничением, оно обычно является обязательной частью любого ссылочного ограничения, будучи потенциальным объектом предложения REFERENCES ограничения FOREIGN KEY. Подробности см. в главе 17.

Выбор первичного ключа

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

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

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

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

К этим теоретическим "установкам" должна быть добавлена третья.

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

Как реальные данные могут привести вас к неудаче

Используя таблицу EMPLOYEE базы данных employee.fdb из каталога /examples корневого каталога Firebird (employee.gdb в версии 1,0.x), давайте посмотрим, как реальные данные могут привести к ошибочности ваших теоретических предположений об уникальности. Вот объявление, которое показывает имеющие смысл данные, хранимые в этой таблице:

CREATE TABLE EMPLOYEE (

FIRST_NAME VARCHAR(15) NOT NULL,

/* предположение: служащий должен иметь имя */

LAST_NAME VARCHAR(20) NOT NULL,

/* предположение: служащий должен иметь фамилию */

PHONE_EXT VARCHAR(4),

HIRE_DATE DATE DEFAULT CURRENT_DATE NOT NULL,

DEPT_NO CHAR(3) NOT NULL,

JOB_CODE VARCHAR (5) NOT NULL,

JOB_GRADE SMALLINT NOT NULL,

JOB_COUNTRY VARCHAR(15) NOT NULL,

SALARY NUMERIC (15, 2) DEFAULT 0 NOT NULL,

FULL_NAME COMPUTED BY FIRST_NAME || " " || LAST_NAME) ;

Фактически эта структура не имеет кандидата в ключи. Невозможно идентифицировать одну строку служащего, используя (FIRST_NAME, LAST_NAME) в качестве ключа, поскольку комбинация двух элементов с вероятностью от средней до высокой может дублироваться в организации. Мы не сможем сохранить записи двух служащих с именем John Smith.

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

Суррогатные ключи

Мы уже рассматривали суррогатный ключ во вводной теме о ключах в главе 14. Суррогатный первичный ключ - значение, гарантирующее уникальность и не имеющее смыслового содержания, которое используется в качестве заменителя ключа в структуре таблицы, которая не может предоставить кандидата на ключ в собственной структуре. По этой причине в таблицу EMPLOYEE добавляется EMP_NO (объявляется через домен) для выполнения роли суррогатного ключа:

CREATE DOMAIN EMPNO SMALLINT ;

ALTER TABLE EMPLOYEE

ADD EMP_NO EMPNO NOT NULL,

ADD CONSTRAINT PK_EMPLOYEE

PRIMARY KEY(EMP_NO) ;

Эта база данных также содержит генератор с именем EMP_NO_GEN и триггер Before insert (перед добавлением) с именем SET_EMP_NO для таблицы EMPLOYEE для получения значения данного ключа в момент добавления новой строки. В разд. "Реализация автоинкрементных ключей" главы 31 эта техника описывается в деталях. Это рекомендованный способ реализации суррогатных ключей в Firebird.

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

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

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

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

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

* Составные ключи обычно являются составленными из неатомарных элементов ключа- т.е. выбранные столбцы имеют смысловое значение (они являются "значимыми данными") и, несомненно, уязвимы для внешних изменений и ошибок ручного ввода.

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

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

* Составные индексы имеют тенденцию к большим размерам. Большие индексы используют больше страниц базы данных, что приводит к тому, что индексные операции (сортировка, соединение и сравнение) выполняются медленнее, чем необходимо.

Атомарность столбцов первичного ключа

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

Чтобы первичный ключ был атомарным, нужно быть вне человеческих решений. Если люди составляют его или классифицируют его, он не является атомарным. Если он является субъектом любого правила за исключением требований NOT NULL и уникальности, он не является атомарным. В приведенном ранее примере даже водительские права или номер социального обеспечения не соответствуют требованиям атомарности для первичного ключа, потому что они являются субъектами внешних систем.

Синтаксис объявления первичного ключа

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

Ограничение PRIMARY KEY может применяться в любой из следующих фаз создания метаданных:

* в определении столбца в операторах CREATE TABLE или ALTER TABLE как часть определения столбца;

* в определении таблицы в операторах CREATE TABLE или ALTER TABLE как отдельно определенное ограничение таблицы.

Определение первичного ключа как часть определения столбца

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

CREATE DOMAIN D_IDENTITY AS BIGINT NOT NULL;

CREATE TABLE PERSON (

PERSON_ID D_IDENTITY PRIMARY KEY,

Firebird создает ограничение таблицы с именем INTEG_M и индекс с именем RDB$PRIMARYnn. (пл в каждом случае - число, полученное от генератора. Эти два числа не связаны друг с другом.) Вы не можете повлиять на то, какими будут эти имена, и не можете поменять их.

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

ALTER TABLE BOOK

ADD BOOK_ID D_IDENTITY PRIMARY KEY;

Определение первичного ключа как именованного ограничения

Другой способ определения первичного ключа в определении таблицы - добавить объявление ограничения в конце определений столбцов. Объявления ограничений помещаются последними, потому что они зависят от существования столбцов, к которым они обращаются. Этот метод дает вам возможность именования ограничений. Следующее объявление именует ограничение первичного ключа как PK_ATABLE:

CREATE TABLE ATABLE (

ID BIGINT NOT NULL,

ANOTHER_COLUMN VARCHAR (20),

CONSTRAINT PK_ATABLE PRIMARY KEY(ID));

Теперь вместо использования сгенерированного системой имени RDB$PRIMARYnnn Firebird использует PK_ATABLE В качестве имени этого ограничения. В Firebird 1.5 и выше он также применяет определенное пользователем имя ограничения для поддерживающего уникального индекса. В этом примере индекс получит имя PK_ATABLE, когда в других версиях его имя будет RDB$PRIMARYnnn.

Элемент 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 .

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: