double arrow

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 xmlset @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 xmlset @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, и даже, я уверен, значительно превосходит его по гибкости. Единственной проблемой остается производительность подобных запросов, но об этом поговорим в конце статьи.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



Сейчас читают про: