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
Горячие темы