Автоматический ввод-вывод XML в IDEF1-хранилище

XML имеет смысл только как коммуникационный протокол, хранить его - большая ошибка, подробнее вы можете ознакомиться с рассуждениями на debank.com. Однако писать код для ввода и вывода XML в реляционное хранилище - лишняя работа, если оно само может принимать и размещать данные в таблицах в соответствии с некоторыми соглашениями. Автор хотел бы вынести на суд свои предложения относительно таких соглашений и заинтересован во мнениях, комментариях и возможной реализации этих новшеств. Чертежи представлены в sql50.euro.ru/sql5.19.2.pdf, и ниже будут использоваться ссылки на страницы этого документа.

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

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

Третье, одноименные и не вложенные, а последовательно расположенные xml-элементы превращаются в список записей, т.е. первичный ключ одной записи также копируется во внешний ключ другой. Если таблица ссылается на саму себя несколькими внешними ключами, то неоднозначность также разрешается в имени открывающего xml-тега, только вместо знака # ставится $ (закрывающий тег также остается без изменений). И это также будем называть детерминацией (с.84-85). Примем, что детерминация должна быть указана в каждом из последовательно расположенных одноименных xml-элементов - чтобы пользователю меньше думать. Мы применяем разные знаки - # и $ - чтобы оба вида детерминации можно было использовать одновременно, например, tag#field1$field2

Обратная задача - вывод записей в xml-виде. Использование функций SQL/XML, XQuery, проприетарных веб-серверов (относительно последних подробнее на lists.xml.org/archives/xml-dev/200802/msg00213.html) дают очень громоздкий код. В то же время, как правило, извлекаются записи, уже связанные внешним ключом, а значит, работа происходит с деревом, уже сформированным в схеме хранилища. Предлагаем в таких случаях лаконичное 'SELECT * FROM a.b.c' для выбора данных из таблиц 'a', 'b', 'c'. Будем называть запись 'a.b.c' термином XTree - по аналогии с XPath.

 

Если одна таблица ссылается на другую несколькими внешними ключами или две таблицы ссылаются друг на друга одновременно, то после имени таблицы ставится знак # и имя нужного ссылающегося поля. Будем называть это рафинированием. Выглядит как имя таблицы с символом # в середине имени (с.12-14). Аналогично, если таблица, содержащая список, ссылается на саму себя несколькими внешними ключами, то после имени таблицы ставится знак $ и имя нужного ссылающегося поля. Это также будем называть рафинированием (с.15-16). Оба вида рафинирования можно использовать одновременно, например, table#field1$field2.

Как записать детерминацию и рафинирование при нескольких ссылающихся полях? Перечислим ситуации.

Распорка (nog) - соединение двумя внешними ключами трех таблиц, причем ссылающиеся поля находятся в одной промежуточной таблице.

create table a (
 id num primary key,
 data float
);
create table b (
 id num primary key,
 ref1 num references a(id), - нужно
 ref2 num references a(id), - не нужно
 ref3 num references c(id), - нужно
 ref4 num references c(id), - не нужно
 data float
);
create table c (
 id num primary key,
 data float
);

И в детерминации, и в рафинировании ссылающиеся поля указываются через двоеточие; таблица-предок упоминается первой, потомок - второй (ссылающиеся поля могут быть одноименными). Таким образом, ввод выглядит как

<a>
 <b#ref1:ref3>
 <c>
 </b>
</a>

вывод (XTree) как a.b#ref1:ref3.c, а маршрут по схеме к нужной таблице (XPath) как a/b#ref1:ref3/c

Стяжка (buckle) - также два ключа с тремя таблицами, но ссылающиеся поля ссылаются на промежуточную таблицу.

create table a (
 id num primary key,
 ref1 num references b(id), - нужно
 ref2 num references b(id), - не нужно
 data float
);
create table b (
 id num primary key,
 data float
);
create table c (
 id num primary key,
 ref1 num references b(id), - нужно
 ref2 num references b(id), - не нужно
 data float
);

Соответственно, ввод

<a#ref1>
 <b>
 <c#ref3>
 </b>
</a>

вывод a#ref1.b.c#ref3, маршрут a#ref1/b/c#ref3.

Перекрёсток (crossroad) мы имеем, когда таблиц всего две.

create table a (
 id num primary key,
 ref num references b(id),
 data float
);
create table b (
 id num primary key,
 lnk num references a(id),
 data float
);

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

<a#ref>
 <b>
</a>

и

<a>
 <b#lnk>
</a>

XTree и XPath также две пары a#ref.b, a#ref/b и a.b#lnk, a/b#lnk.

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

create table a (
 id num primary key,
 data float
);
create table b (
 id num primary key,
 ref1 num references a(id), - нужно
 ref2 num references a(id), - не нужно
 data float
);
create table c (
 id num primary key,
 ref1 num references a(id), - нужно
 ref2 num references a(id), - не нужно
 data float
);

Входящий XML выглядит как

<a>
 <b#ref1>
 <c#ref1>
</a>

а вот XTree и XPath имеют два варианта, ведь предикат может требовать дерево как со всеми ветвями, так и только с одной. Со всеми - это a.(b#ref1 c#ref1) и a/(b#ref1 c#ref1). А вот с одной существует только XTree, т.е. a.(b#ref1 | c#ref1), т.к. в случае a/(b#ref1 | c#ref1) не понятно, какой марштут брать. Теперь рассмотрим ситуацию, когда ссылается родительская таблица.

create table a (
 id num primary key,
 ref1 num references b(id), - нужно
 ref2 num references b(id), - не нужно
 ref3 num references c(id), - нужно
 ref4 num references c(id), - не нужно
 data float
);
create table b (
 id num primary key,
 data float
);
create table c (
 id num primary key,
 data float
);

Здесь ввод

<a#ref1+ref3>
 <b>
 <c>
</a>

вывод и маршрут с требованием записей во всех дочерних таблицах есть a#ref1+ref3.(b c) и a#ref1+ref3/(b c). Аналогично в случае, если достаточно только одной дочерней записи, существует XTree, т.е. a#ref1^ref3.(b | c), но не существует XPath. Обратили внимание, что стоит #ref1^ref3, а не #ref1+ref3 ? Этим мы избегаем когнитивного диссонанса между 'или', обозначаемом вертикальной чертой, и плюсиком.

Своеобразной инверсией разветвления является продолжение (continuation) - ситуация, в которой одна таблица одновременно является потомком двух других. Здесь ввода нет вообще! Потому что xml-элемент не может быть потомком двух других (специальные изголения для программистов оставим этим последним). Однако вывод существует, ведь можно одну запись извлечь дважды - как дочернюю двух разных родительских. У продолжения, опять же, две разновидности, и сначала рассмотрим ту, в которой ссылаются дочерние таблицы.

create table b (
 id num primary key,
 data float
);
create table c (
 id num primary key,
 data float
);
create table d (
 id num primary key,
 ref1 num references b(id), - нужно
 ref2 num references b(id), - не нужно
 ref3 num references c(id), - нужно
 ref4 num references c(id), - не нужно
 data float
);

Чтобы работа происходила, только если существуют все возможные xml-деревья, мы пишем .(b.d#ref1 c.d#ref3). и /(b/d#ref1 c/d#ref3)/ ; если существует хотя бы одно дерево /(b/d#ref1 | c/d#ref3)/. Теперь рассмотрим ситуацию, когда ссылается родительская таблица.

create table b (
 id num primary key,
 ref1 num references d(id), - нужно
 ref2 num references d(id), - не нужно
 data float
);
create table c (
 id num primary key,
 ref1 num references d(id), - нужно
 ref2 num references d(id), - не нужно
 data float
);
create table d (
 id num primary key,
 data float
);

Ну, в общем-то все то же самое - в смысле .(b#ref1 c#ref3).d., /(b#ref1 c#ref3)/d/ и .(b#ref1 | c#ref3).d.

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

<a>
 <b#lnk1*lnk2>
</a>

в выводе и маршруте - как a.b#lnk1*lnk2 и a/b#lnk1*lnk2.

Наши предложения позволяют именно простому пользователю делать репликацию и аггрегировать информацию из множества источников. Прозрачный, без участия программиста ввод-вывод иерархических данных сохранит актуальность даже в следующей эпохе, когда все HTTP, FTP, SMTP/POP3, FE/BE и прочие со всеми их ETL-ами будут заменены единым протоколом и форматом, а каждая передача данных будет копированием записей или обновлением полей хранилища - как это описано, например, в статье "Ошибки и их исправление в эргономике API и GUI" ("КВ" №25). Сейчас же неотложно применение автоматического I/O в браузерах и СУБД; причем выведенные xml-аттрибуты превратить в xml-элементы можно как трудоемкими способами - XSL, CSS-конструкциями вида 'tagname::before { content: attr(attributename) }' - так и легкими, т.е. CSS-конструкциями вида '§attributename { }', которые изымают атрибут из xml-элемента и превращают его во вложенный элемент, см. lists.w3.org/Archives/Public/www-style/2007Jun/0106.html.

Дмитрий ТЮРИН,
dmitryturin.narod.ru

Версия для печатиВерсия для печати

Номер: 

34 за 2010 год

Рубрика: 

Размышлизмы
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!