Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Использование XML совместно с SQL.doc
Скачиваний:
2
Добавлен:
01.05.2025
Размер:
1 Мб
Скачать
☆

Хранение xml-типа

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

XML-тип сохраняется как большой объект (lob - large object) в бинарном формате, структура которого неизвестна, но точно можно сказать, что она линейна. Т.е. сохраняется относительный порядок узлов в документе – атрибуты следуют после элемента, к которому они принадлежат, дочерние элементы следуют после атрибутов, и последующие элементы (элементы того же уровня) – после дочерних элементов.

Для осуществления запроса XQuery SQL Server каждый раз выполняет преобразование этой линейной структуры в реляционную – в таблицу узлов (node table). Таблица узлов более формализована и понятна, однако занимает в несколько раз (даже на порядки) больше места. Любой запрос XQuery трансформируется в SQL запрос к этой таблице.

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

Структура этой таблицы следующая (назначение некоторых полей я придумал сам, потому что разработчики пока не признаются, что эти поля означают):

  • pk1 – значение первичного ключа основной таблицы, где расположено XML-поле.

  • id – идентификатор строки в данной таблице.

  • nid – идентификатор узла.

  • tid – неизвестно.

  • value – значение узла.

  • lvalue – неизвестно.

  • lvaluebin – неизвестно.

  • hid – неизвестно.

  • xsinil – равно ли значение узла NULL.

  • xsitype – тип узла.

Вот собственно и все, что мне удалось раскопать по внутреннему устройству XML-типа.

XML DML

На данный момент не существует стандарта и даже рабочей группы по вопросам создания языка для изменения содержимого XML-документов.

Существует самостоятельная инициатива разработчиков – XUpdate, которая представлена сообществом XMLDB (xmldb.org) и доступна по адресу [4]. Есть несколько продуктов, в том числе и коммерческих, поддерживающих этот язык, однако большого распространения он не получил.

Разработчики SQL Server 2005 решили пойти своим путем: они разработали язык xml dml – XML Data Manipulation Language, который является расширением XQuery. Он не выражается в терминах самого XML (как XUpdate), и в нем можно применять выражения XQuery. Расширение заключается во введении трех ключевых слов – update, delete и insert, которые можно использовать в методе modify XML-типа.

Ниже будем использовать несколько более сложный XML-документ, удовлетворяющий схеме my-first-schema:

drop xmlschema namespace 'my-first-schema'

go

create xmlschema '

<xs:schema

targetNamespace="my-first-schema"

xmlns:tns="my-first-schema"

xmlns:xs="http://www.w3.org/2001/XMLSchema"

>

<xs:element name="root">

<xs:complexType>

<xs:sequence>

<xs:element name="a"

maxOccurs="unbounded" minOccurs="0"

form="qualified"/>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:schema>'

Здесь мы расширили содержимое XML-элемента root: теперь он может содержать бесчисленное множество элементов a любого типа (так как тип не задан, используется по умолчанию xs:Any).

Delete

Это самый простой оператор. Вот его синтаксис

delete Expression

Где Expression – выражение XQuery, выбирающее последовательность узлов, подлежащих удалению. Оператор delete не может удалить узлы, находящиеся на оси namespace – это единственная ось, которая есть в XPath 2.0, но отсутствует в XQuery.

Любое выражение в XQuery возвращает последовательность элементов (sequence items). Элементами могут быть либо скалярные величины, либо узлы. Последовательность – совершенно новое понятие, которое заменяет понятие набора узлов в XPath 1.0, более подробно о нем мы поговорим в разделе "Поддержка XQuery".

Рассмотрим пример применения оператора delete:

declare @xml xml

set @xml = '<?xml version="1.0" ?>

<root xmlns="test-unique">

<a>1</a>

<a>2</a>

<a>10</a>

</root>'

set @xml::modify('

namespace tns="test-unique"

delete /tns:root/tns:a[. = 10]

')

select @xml

Здесь удаляется узел a, значение которого равно 10. Единственное, что отличает это выражение от старого XPath – пролог, в котором декларируется пространство имен. Попробую продемонстрировать некоторые возможности XQuery:

declare @xml xml

set @xml = '<?xml version="1.0" ?>

<root xmlns="test-unique">

<a>1</a>

<a>2</a>

<a>30</a>

</root>'

set @xml::modify('

namespace tns="test-unique"

delete

if (sum(/tns:root/tns:a) > 10) then

tns:root/tns:a[. = max(/tns:root/tns:a)]

else /..

')

select @xml

В этом примере, если сумма всех числовых значений элементов a больше 10, удаляется XML-элемент а с максимальным значением. Иначе не удаляется ничего. Вместо конструкции "/.." по идее можно было бы использовать пустую последовательность "()". Но в имеющейся у меня бета-версии это приводило к ошибке. Поэтому пришлось применить эту конструкцию, обозначающую пустой элемент (точнее, несуществующего родителя узла документа).

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

<?xml version="1.0" ?>

<root xmlns="test-unique">

<a>1</a>

<a>2</a>

<a>10</a>

</root>

то максимальным элементом оказался бы узел со значением 2. Чтобы выполнять операции над значениями элемента а как над числовыми, необходимо в схеме данных (my-first-schema) задать тип XML-элемента а. Есть другой способ, который заключается в непосредственном указании типа прямо в самом XML-документе с помощью атрибута type пространства имен http://www.w3.org/2001/XMLSchema-instance:

<?xml version="1.0" ?>

<root xmlns="test-unique"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

>

<a xsi:type="xs:integer">1</a>

<a xsi:type="xs:integer">2</a>

<a xsi:type="xs:integer">10</a>

</root>

Однако ни тот, ни другой способ текущей версией процессора в SQL Server не поддерживаются.

Следующий пример демонстрирует объединение результатов двух XPath-запросов в единую последовательность. В нем происходит удаление третьего элемента (в порядке их следования в документе) и элементов, чье числовое представление равно 1:

declare @xml xml

set @xml = '<?xml version="1.0" ?>

<root xmlns="test-unique">

<a>1</a>

<a>2</a>

<a>1</a>

<a>30</a>

</root>'

set @xml::modify('

namespace tns="test-unique"

delete (//tns:a[3], //tns:a[. = 1])

')

select @xml

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

Удалим из таблицы fams мужей из всех семей, чей суммарный доход превышает 440:

update fams

set fam::modify('delete /fam/husband[sum(/fam/*/@income) > 440]')

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

Update

Этот оператор предназначен для изменения существующего значения узла. Синтаксис оператора:

update [value of]

Expression1

to

Expression2

Где:

  • value of – необязательное ключевое слово.

  • Expression1 – выражение XQuery, идентифицирующее один элемент, который должен являться узлом атрибута или текстовым узлом.

  • Expression2 – выражение XQuery, возвращающее последовательность из одного элемента, который может являться узлом любого типа, но не атомарным элементом. Если узел не является узлом атрибута или текстовым узлом, для получения значения этого узла применяется функция data.

Вот примеры использования данного оператора:

declare @xml xml

set @xml = '<?xml version="1.0" ?>

<root>some text

<a>1</a>

<a>2</a>

<a>30</a>

</root>'

--Изменим значение элемента а, чье предыдущее значение

--было равно минимальному, на сумму всех числовых

--значений элементов а

set @xml::modify('

update //a[. = min(//a)]/text() to sum(//a)

')

select @xml

--Изменим текстовый узел элемента root

set @xml::modify('

update /root/text() to "another text"

')

select @xml

Вывод:

------------------------------------------------------

<root>some text

<a>33</a><a>2</a><a>30</a></root>

(1 row(s) affected)

------------------------------------------------------

<root>another text<a>33</a><a>2</a><a>30</a></root>

(1 row(s) affected)

Как видите, везде при изменении значения XML-элемента приходится указывать дочерний текстовый узел. Однако даже этого недостаточно, когда XML-элемент содержит несколько текстовых узлов – в этом случае нужно точно указывать, какой именно текстовый узел необходимо изменить:

declare @xml xml

set @xml = '<?xml version="1.0" ?>

<root>first text node

<a b="9">1</a>

<a>2</a>

<a>30</a>

second text node

</root>'

--Изменим значение атрибута b на среднее арифметическое

--числовых значений элементов а

set @xml::modify('

update //a/@b to avg(//a)

')

select @xml

--Изменим первый текстовый узел элемента root

set @xml::modify('

update /root/text()[1] to "another text node"

')

select @xml

Вывод:

------------------------------------------------------

<root>first text node

<a b="11">1</a><a>2</a><a>30</a>

second text node

</root>

(1 row(s) affected)

------------------------------------------------------

<root>another text node<a b="11">1</a><a>2</a><a>30</a>

second text node

</root>

(1 row(s) affected)

Insert

Последний оператор DML предназначен для вставки узлов в существующий XML-документ. Это самый мощный и сложный оператор. Рассмотрим его синтаксис:

insert

Expression1 (

{as first | as last} into | after | before

Expression2

)

Здесь:

  • Expression1 – выражение XQuery, определяющее вставляемую последовательность.

  • as first – ключевое слово, обозначающее вставку последовательности в первую позицию среди дочерних узлов данного XML-элемента.

  • as last – ключевое слово, обозначающее вставку последовательности в последнюю позицию узла. Если ничего не указано, используется as last.

  • into – ключевое слово, обозначающее вставку последовательности как дочерних узлов данного XML-элемента.

  • after – ключевое слово, обозначающее вставку последовательности как последующих узлов (following siblings) данного XML-элемента.

  • before - ключевое слово, обозначающее вставку последовательности как предыдущих узлов (preceding siblings) данного XML-элемента.

  • Expression2 – определяет узел, относительно которого производится вставка последовательности, указанной в Expression 1.

Ниже приводится пример работы данного оператора:

declare @xml xml

set @xml = '<?xml version="1.0" ?>

<root>

<a>1</a>

<a>2</a>

<a>30</a>

</root>'

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

-- узлов элемента root

set @xml::modify('

insert <?mypi d="a" ?> as first into /root

')

select @xml

-- добавляем комментарий как дочерний узел элемента root

-- после второго элемента а

set @xml::modify('

insert <!--comment--> after /root/a[2]

')

select @xml

-- Невозможно добавить узел пространства имен

-- в элемент а. Не поддерживается в текущей версии

-- set @xml::modify('

-- insert namespace some {"rosa"} into /root/a[1]

--'</str></str>)

-- Добавляем атрибут attr со значением b во

-- второй элемент а

set @xml::modify('

insert attribute attr {"b"} into /root/a[2]

')

select @xml

-- Добавляем два элемента: sum и avеrage с соответствующими значениями

-- как последние узлы элемента root

set @xml::modify('

insert (<sum>{sum(//a)}</sum>, element average {avg(//a)}) into /root

')

select @xml

-- Добавляем текстовый узел как первый дочерний узел элемента root

set @xml::modify('

insert <![CDATA[text node]]> as first into /root

')

select @xml

Вывод:

------------------------------------------------------

<root><?mypi d="a" ?><a>1</a><a>2</a><a>30</a></root>

(1 row(s) affected)

------------------------------------------------------

<root><?mypi d="a" ?><a>1</a><a>2</a><!--comment--><a>30</a></root>

(1 row(s) affected)

------------------------------------------------------

<root><?mypi d="a" ?><a>1</a><a attr="b">2</a><!--comment--><a>30</a></root>

(1 row(s) affected)

------------------------------------------------------

<root>

<?mypi d="a" ?>

<a>1</a>

<a attr="b">2</a>

<!--comment-->

<a>30</a>

<sum>33</sum>

<average>11</average>

</root>

(1 row(s) affected)

------------------------------------------------------

<root>text node<?mypi d="a" ?>

<a>1</a>

<a attr="b">2</a>

<!--comment-->

<a>30</a>

<sum>33</sum>

<average>11</average>

</root>

(1 row(s) affected)

В последних трех инструкциях данного примера я попытался применить конструкторы узлов – специальные выражения XQuery для создания узлов любого типа. К сожалению, конструкторы узлов пространств имен, документа, текста, комментария и инструкции обработки не поддерживаются в текущей версии. Однако часть из них – комментарии и инструкции обработки – можно задавать непосредственно как литералы, а для конструирования текстовых узлов можно использовать секцию CDATA, что и было продемонстрировано в примере. Более подробно о конструкторах узлов см. раздел Поддержка XQuery.