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

Логические операторы

Логические операторы and и or остались на своем месте. Не поменялась ни семантика, ни синтаксис. В качестве типов обоих операндов всегда выступает логический тип – xs:boolean. Если операнд имеет отличный от xs:boolean тип, для него находится действительное булево значение (effective boolean value) путем применения функции fn:boolean.

Функция fn:boolean возвращает false в случае, когда операнд:

  • Пустая последовательность.

  • Логическое значение false.

  • Строка нулевой длины.

  • Числовое значение, равное нулю.

  • Вещественное значение, равное NaN – not a number.

Во всех остальных случаях функция возвращает true.

Например:

declare @xml xml

set @xml = '<a>1</a><a>2</a>'

select @xml::query('

{-- true - действительное булево значение 2 является true --}

true() and 2

,

{-- false - второй операнд является пустой последовательностью --}

true() and ()

,

{-- true - действительное булево значение обоих операндов - true --}

"rosa" and "dima"

,

{-- выберет оба узла а --}

/a[. = 1 or . = 2]

')

К сожалению, SQL Server не захотел принимать этот пример из-за последнего выражения, где выбираются XML-элементы a, выдавая такую ошибку: XQuery: Heterogeneous sequences are not allowed: found 'xs:boolean +' and 'element a *'. Очередной баг.

Условные выражения

Условное выражение присутствует в XQuery почти в паскалевском синтаксисе – if then else. Порядок обработки этой конструкции следующий: для выражения после if вычисляется эффективное булево значение. Если результат равен истине, вычисляется выражение после then, если ложь, вычисляется выражение после else. Конструкция else обязательна. Выражение после if обязано заключаться в скобки.

Например:

declare @xml xml

set @xml = ''

select @xml::query('if (1 eq 2) then "alex" else "rosa"')

В качестве выражений then и else в этом примере используются конструкторы текстовых узлов.

Flwor-выражения

FLWOR расшифровывается как for let where order by return. К сожалению, SQL Server не поддерживает ключевых слов LET и ORDER BY.

Бессмысленно говорить об отличиях в реализации SQL Server от текущего черновика стандарта, потому что фактически у них нет ничего общего, кроме трех ключевых слов – for, where и return.

FLWOR-выражения, являются, пожалуй самым мощным средством обработки последовательностей в XQuery. Синтаксис выражения в SQL Server примерно следующий (примерно, потому что в документации по SQL Server нигде не указан точный синтаксис FLWOR-выражения):

("for" "$" VariableName "in" Expression) +

("where" WhereExpression)? "return" ReturnExpression

Где:

  • VariableName – имя переменной.

  • Expression – выражение XQuery, возвращающее последовательность.

Смысл выражения FLWOR: в цикле каждый элемент последовательности, возвращаемой Expression, связывается с переменной, чье имя задается в VariableName. Далее, в каждой итерации выполняется выражение WhereExpression, и если его результат равен true – выполняется выражение ReturnExpression. Важно понимать, что ReturnExpression выполняется для каждого элемента последовательности, т.е. n раз, где n – размер последовательности. Если выражение WhereExpression вернуло false, выполняется следующая итерация. Общее количество итераций равно размеру последовательности Expression.

Названия переменных в XQuery всегда начинаются со знака доллара. Объявлять переменные можно только в FLWOR-выражениях, кванторных выражениях (см. ниже), в прологе запроса и при объявлении параметров функций. SQL Server не поддерживает переменные в прологе запроса и функции, так что объявить переменную можно только в FLWOR-выражениях и кванторных выражениях.

В самом FLWOR-выражении переменную можно объявлять в разделе for и в разделе let. Так как раздел let не поддерживается в SQL Server, переменные можно объявлять только в разделе for. Примеры объявления переменных в настоящем FLWOR-выражении находятся в конце этого раздела.

FLWOR-выражение в SQL Server нельзя применять для связывания нескольких XML-документов, так как отсутствует функция doc, которая возвращает узел документа по заданному URI. Возможно, в финальной версии появится подобная функция или функция расширения, которая будет принимать значение первичного ключа для данной таблицы и имя XML-поля. Фактически же, текущая версия вообще не поддерживает узел документа.

Пожалуй, FLWOR-выражение в SQL Server можно применять только для фильтрации элементов последовательности и трансформации структуры XML-фрагмента. В следующем примере демонстрируется и то, и другое: сначала выполняется фильтрация, а затем конструирование последовательности узлов элемента b.

declare @xml xml

set @xml = '<a>10</a>, <a>2</a>, <a>3</a>'

select @xml::query('

for $a in /a

where $a < 10

return <b>{$a/text()}</b>

')

Результат:

<b>2</b><b>3</b>

Выражения FLWOR могут быть вложенными. Например:

declare @xml xml

set @xml = '<a>10</a>, <a>2</a>, <b>4</b>, <a>3</a>, <b>6</b>'

select @xml::query('

{-- для каждого элемента а --}

for $a in /a

{-- для каждого элемента b --}

for $b in /b

{-- этот раздел return относится к внутреннему циклу for

Поэтому исполняется 3 * 2 раз --}

return <r>

<expression>{$a/text()} * {$b/text()}</expression>

<equals>{$a*$b}</equals>

</r>

')

Здесь во внешнем цикле производится перебор элементов а, а во внутреннем – элементов b, и все элементы a умножаются на все элементы b. Результат:

<r><expression>10 * 4</expression><equals>40</equals></r>

<r><expression>10 * 6</expression><equals>60</equals></r>

<r><expression>2 * 4</expression><equals>8</equals></r>

<r><expression>2 * 6</expression><equals>12</equals></r>

<r><expression>3 * 4</expression><equals>12</equals></r>

<r><expression>3 * 6</expression><equals>18</equals></r>

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

В качестве исходных данных будем использовать небольшую базу данных по форумам RSDN из статьи [6], которая, допустим, находится в файле forums.xml:

<?xml version="1.0" encoding="windows-1251" ?>

<rsdn>

<forums date="09.01.03">

<forum name="WinAPI" totalposts="16688"

description="Системное программирование">

<moderators/>

<top-poster>Alex Fedotov</top-poster>

</forum>

<forum name="COM" totalposts="10116"

description="Компонентные технологии">

<moderators/>

<top-poster>Vi2</top-poster>

</forum>

<forum name="Delphi" totalposts="5001"

description="Delphi и Builder">

<moderators>

<moderator name="Sinclair"/>

<moderator name="Hacker_Delphi"/>

</moderators>

<top-poster>Sinclair</top-poster>

</forum>

<forum name="DB" totalposts="6606" description="Базы данных">

<moderators>

<moderator name="_MarlboroMan_"/>

</moderators>

<top-poster>Merle</top-poster>

</forum>

</forums>

</rsdn>

И базу данных пользователей, которая находится в файле users.xml:

<?xml version="1.0" encoding="windows-1251"?>

<rsdn date="20/04/2004">

<users>

<user nickname="Alexey Shirshov" name="Ширшов Алексей Николаевич"

posts="4327" articles="16" rate="5779" total-rating="27">

<location from="Уфа" workin="Москва"/>

</user>

<user nickname="Alex Fedotov" name="Alex Fedotov"

posts="2617" articles="11" rate="6885" total-rating="31">

<location from="" workin="San Francisco"/>

</user>

<user nickname="Vi2" name="Шарахов Виктор"

posts="3094" articles="1" rate="5922" total-rating="26">

<location from="Ижевск" workin="Ижевск"/>

</user>

<user nickname="Sinclair" name="Антон Злыгостев"

posts="4325" articles="5" rate="11136" total-rating="39">

<location from="Новосибирск" workin="Новосибирск"/>

</user>

<user nickname="Hacker_Delphi" name="Michael Polyudov"

posts="2740" articles="2" rate="1880" total-rating="12">

<location from="Новосибирск" workin="Новосибирск"/>

</user>

<user nickname="_MarlboroMan_" name="Полюдов Дмитрий Петрович"

posts="2104" articles="1" rate="2798" total-rating="13">

<location from="Новосибирск" workin="Новосибирск"/>

</user>

<user nickname="Merle" name="Ivan D. Bodiaguine"

posts="2049" articles="5" rate="4974" total-rating="23">

<location from="Москва" workin="Москва"/>

</user>

</users>

</rsdn>

Выберем всех пользователей, которые являются модераторами форумов:

(: для каждого модератора :)

for $moders in doc("forums.xml")/rsdn/forums/forum/moderators/moderator/@name

(: загружаем его профиль по имени :)

let $user := doc("users.xml")/rsdn/users/user[@nickname = $moders]

(: и копируем его в результирующую последовательность :)

return $user

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

(: выбираем всех пользователей :)

let $user := doc("users.xml")/rsdn/users/user

(: считаем средний бал для всех пользователей :)

let $avg-rate := avg($user/@rate)

(: в цикле для каждого модератора :)

for $moders in doc("forums.xml")/rsdn/forums/forum/moderators/moderator/@name

(: находим соответствующий ему профиль :)

let $moder-user := $user[@nickname = $moders]

(: и фильтруем по баллам :)

where $moder-user/@rate > $avg-rate

(: копируем модератора в результирующую последовательность :)

return $moder-user

Следующий запрос возвращает модифицированный элемент top-poster из файла forums.xml:

(: для каждого топ-постера :)

for $top-poster in doc("forums.xml")/rsdn/forums/forum/top-poster

(: получаем его профиль :)

let $user := doc("users.xml")/rsdn/users/user[@nickname = $top-poster]

return

(: атрибут nick - имя пользователя :)

<top-poster nick="{$top-poster}">

<name>

{

(: копируем атрибут name с использованием

конструктора xs:string (имя пользователя в миру) :)

xs:string($user/@name)

}</name>

{

(: копируем элемент location из профиля пользователя :)

$user/location

}

<rating>

{

(: копируем значение атрибута total-rating :)

$user/@total-rating

}</rating>

</top-poster>

Результат:

<?xml version="1.0" encoding="UTF-8"?>

<top-poster nick="Alex Fedotov">

<name>Alex Fedotov</name>

<location from="" workin="San Francisco"/>

<rating total-rating="31"/>

</top-poster>

<top-poster nick="Vi2">

<name>Шарахов Виктор</name>

<location from="Ижевск" workin="Ижевск"/>

<rating total-rating="26"/>

</top-poster>

<top-poster nick="Sinclair">

<name>Антон Злыгостев</name>

<location from="Новосибирск" workin="Новосибирск"/>

<rating total-rating="39"/>

</top-poster>

<top-poster nick="Merle">

<name>Ivan D. Bodiaguine</name>

<location from="Москва" workin="Москва"/>

<rating total-rating="23"/>

</top-poster>

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

(: для каждого города :)

for $cities in distinct-values(doc("users.xml")/rsdn/users/user/location/@workin)

(: найдем пользователей, в нем проживающих :)

let $users-in-the-same-city :=

doc("users.xml")/rsdn/users/user[location/@workin = $cities]

(: отсортируем выходную последовательность в порядке,

обратном сумме балов пользователей данного города :)

order by sum($users-in-the-same-city/@rate) descending

(: сконструируем результат :)

return <city name="{$cities}" total-rate="{sum($users-in-the-same-city/@rate)}" />

Вот результат этого запроса:

<?xml version="1.0" encoding="UTF-8"?>

<city name="Новосибирск" total-rate="15814"/>

<city name="Москва" total-rate="10753"/>

<city name="San Francisco" total-rate="6885"/>

<city name="Ижевск" total-rate="5922"/>

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