Я расскажу, как обращаться к данным, которые содержатся в XML-строках, передаваемых хранимым процедурам, и как выводить XML-данные. На рис. 1 показана высокоуровневая архитектура этих операций. Я познакомлю вас со следующими синтаксическими конструкциями T-SQL, связанными с XML:
--- sp_xml_preparedocument; --- OPENXML; --- sp_xml_removedocument; --- FOR XML EXPLICIT.
Рис. 1. Применение на практике некоторых новшеств в синтаксисе T-SQL
Простой пример: пакетная проверка пользовательского ввода В SQL Server 2000 нет встроенного типа для хранения XML-данных. Для хранения XML-строк в переменных или полях таблиц применяют тип (n)(var)char или (n)text. Почти во всех случаях, встречающихся при разработке, такие переменные оказываются входными параметрами хранимых процедур; поэтому так будет и в моем примере. (Следует заметить, что передача кода T-SQL между уровнями всегда рискованна с точки зрения безопасности и, как правило, оказывается не лучшим вариантом с точки зрения производительности; это достаточно веские причины, чтобы никогда так не поступать.)
Рассмотрим хранимую процедуру sptxValidateLookupData, разработанную моей группой несколько лет назад. Она предназначена для проверки на допустимость данных, вводимых пользователем в рабочий процесс определенной системы. Чтобы свести к минимуму количество обменов с базой данных, разработчики системы решили накапливать весь пользовательский ввод и передавать информацию, которую требуется проверять на допустимость, в базу данных в одном XML-документе. Хранимая процедура выполняет проверку на допустимость и возвращает вызывающему процессу результаты, также помещая их в один XML-документ. Процедура sptxValidateLookupData обслуживает несколько разных рабочих процессов, поэтому в одном и том же пакете могут запрашиваться проверки "существует" или "не существует" для любого значения (datum) или области (domain). В следующем фрагменте кода показаны типичные входные XML-данные этой хранимой процедуры:
В корневом узле содержатся подузлы , описывающие, какую проверку нужно выполнить. Type задает область, для которой выполняется проверка, Name - проверяемый атрибут, а Test - тип проверки (существует ли значение Value в поле, указанном атрибутом Name, в области Type). Заметьте: этот фрагмент XML-данных описывает четыре операции проверки на допустимость для трех областей, но версия хранимой процедуры, которая используется в настоящее время, поддерживает 17 областей и любое число проверок. Таким образом, у нас есть компонент проверки на допустимость, обеспечивающий многократное использование кода, отличную расширяемость и высокую производительность, удобный в сопровождении, а главное - простой до неприличия!
Результаты также возвращаются в XML-формате. Если все проверки для данного потока возвратили TRUE (т. е. если истинны утверждения, задаваемые в узлах Test), возвращается пустой тэг . Однако, если какие-либо проверки потерпели неудачу, возвращается список ошибок:
<Errors> <Error ErrorMessage="JA exists in Countries" FieldName="CountryCode"/> <Error ErrorMessage="BO1 does not exist in Countries" FieldName="CountryCode"/> <Error ErrorMessage="EN does not exist in ContactPreferences" FieldName="ContactPreferenceCode"/> </Errors>
Работа с XML-данными В SQL Server 2000 XML-данные передаются процессу T-SQL как простые строки. Чтобы с ними можно было работать как с реляционными или иерархическими данными, необходимо дать SQL Server "понять", что это XML-данные, - "подготовить" их. Для этого вызывается системная хранимая процедура sp_xml_preparedocument. Давайте посмотрим начало хранимой процедуры sptxValidateLookupData (листинг 1). Системная хранимая процедура sp_xml_preparedocument считывает XML-текст, передаваемый как входной параметр (параметр @XMLString в вызове в листинге 1), затем передает текст анализатору MSXML и формирует проанализированный документ, готовый к обработке функцией OPENXML, возвращающей набор записей.
Этот документ является иерархическим представлением различных узлов XML-документа (элементов, атрибутов, текста, комментариев и т.д.). Оно хранится в кэше сервера, максимальный размер которого равен одной восьмой от общего объема памяти сервера. Поэтому необходимо аккуратно работать с этим внутренним представлением в интенсивно используемых системах или в средах с ограниченным объемом памяти.
-- Готовим входные XML-данные к выборке с помощью OPENXML exec sp_xml_preparedocument @idoc OUTPUT, @XMLString
Процедура sp_xml_preparedocument возвращает описатель (значение @idoc в вызове в листинге 1), через который можно обращаться к созданному внутреннему представлению XML-документа. Это значение используется в качестве параметра функции OPENXML, возвращающей набор записей для оператора SELECT. OPENXML - "мост" к подготовленному XML-документу, и ее можно указывать в операторе SELECT аналогично таблице или представлению (листинг 2).
Я создал и заполнил табличную переменную, чтобы не выполнять многократные вызовы OPENXML, которые были бы менее эффективны, чем многократное обращение к табличной переменной. Лучше копировать содержимое XML-документов в табличные переменные, чтобы избавиться от многократного неэффективного доступа к XML-данным (хотя, если вы собираетесь обратиться к XML-данным только один раз, просто выполните запрос с OPENXML без предварительного создания табличной переменной). Второй оператор на рис. 3 копирует входные XML-данные в табличную переменную @tempValidateLookupData.
Листинг 2. Создание табличной переменной
-- Создаем табличную переменную для хранения -- данных о проверках declare @tempValidateLookupData table ( [Type] nvarchar(30), [Name] nvarchar(30), [Value] nvarchar(300), [Test] nvarchar(30), [TestResult] int )
-- Заполняем табличную переменную данными о проверках, -- которые требуется выполнить insert @tempValidateLookupData select [Type], [Name], [Value], [Test], NULL from OPENXML (@idoc, '/ValidateData/Validate') with ([Type] nvarchar(30), [Name] nvarchar(30), [Value] nvarchar(300), [Test] nvarchar(30))
Знакомимся с OPENXML Теперь повнимательнее рассмотрим синтаксис OPENXML:
OPENXML(idoc int [in],rowpattern nvarchar[in],[flags byte[in]]) [WITH (SchemaDeclaration | TableName)]
Параметр idoc - описатель документа, созданный для внутреннего представления XML-документа. Другими словами, это значение, ранее возвращенное процедурой sp_xml_preparedocument. Заметьте: в одной хранимой процедуре можно манипулировать несколькими XML-строками. В таком случае необходимо вызвать sp_xml_preparedocument для каждой XML-строки и объявить по отдельной переменной для хранения каждого возвращенного описателя.
Параметр rowpattern идентифицирует узлы XML-документа, связанного с описателем idoc, которые должны обрабатываться как записи. Он указывает XML-анализатору, где в XML-документе находятся интересующие вас данные.
Параметр flags задает, какое сопоставление используется при запросе в первую очередь - ориентированное на атрибуты (attribute-centric) или на элементы (element-centric). Если этот параметр опущен, SQL Server 2000 по умолчанию применяет сопоставление, ориентированное на атрибуты, что вполне подходит для нашего случая. Подробнее об этом параметре см. описание функции OPENXML в SQL Server Books Online.
Раздел WITH функции OPENXML указывает SQL Server 2000, какие типы данных SQL сопоставляются содержимому XML-документа. Вы можете либо явно задать поля, либо сослаться на таблицу базы данных с подходящей структурой. Каждое поле XML-документа, используемое в операторе SELECT, должно присутствовать в разделе WITH (описываться явно или в таблице). В рассматриваемом примере:
-- Заполняем табличную переменную данными о проверках, -- которые требуется выполнить insert @tempValidateLookupData select [Type], [Name], [Value], [Test], NULL from OPENXML (@idoc, '/ValidateData/Validate') with ([Type] nvarchar(30), [Name] nvarchar(30), [Value] nvarchar(300), [Test] nvarchar(30))
имена полей в операторе SELECT и в разделе WITH должны соответствовать именам атрибутов во входном XML-документе. Также заметьте, что, поскольку XML-данные рассматриваются как набор записей, программа проверки на допустимость будет правильно работать при любом количестве значений (хоть при одном, хоть при десяти): вызывающий процесс просто передает столько узлов XML-документа, сколько нужно проверить на допустимость.
Освобождение памяти Внутреннее представление XML-документа остается в памяти, пока соединение процесса с сервером не будет закрыто или сброшено или пока память не освободят явно. Следует вызывать системную процедуру sp_xml_removedocument как можно раньше, поскольку лучше поскорее освободить эту память, чтобы на сервере было доступно больше ресурсов (для освобождения памяти требуется указать описатель):
-- Освобождаем память сервера, которая используется -- образом входных XML-данных, созданным OPENXML exec sp_xml_removedocument @idoc
Это особенно важно в средах, где используется пул соединений. В таких средах соединения могут существовать в течение нескольких дней, и многократное создание образов XML-документов в памяти без последующего освобождения может вызвать снижение производительности, поскольку количество свободной памяти сократится. Пул соединений сбрасывает соединение, если оно повторно используется кем-то другим, при этом освобождаются и описатели XML-данных. Но лучше явно освобождать описатели, а не полагаться, на то, что при сбросе соединений это сделают за вас.
После разбора XML-документа процедура sptxValidateLookupData выполняет большой оператор IF, чтобы выбрать требуемую проверку и занести в переменную @TestResult значение, соответствующее результатам проверки. Затем результаты проверки используются при обновлении табличной переменной (листинг 3).
Листинг 3. Выбор требуемой проверки
-- Заносим результаты проверок в табличную переменную, -- перебирая записи, у которых поле TestResult содержит NULL while exists (select TestResult from @tempValidateLookupData where TestResult is null) begin
-- Извлекаем данные из записи, проверяемой на допустимость Select top 1 @Type=[Type], @Name=[Name], @Value=Value, @Test=Test from @tempValidateLookupData where TestResult is null
-- И выполняем соответствующую проверку...
-- Проверка для области Countries (взята в качестве примера) if @Type = 'Countries' begin if exists (select CountryCode from dbo.Country where CountryCode = convert(nvarchar(4), @Value)) select @TestResult = CASE when @Test = 'Exists' then 1 else 0 end else select @TestResult = CASE when @Test = 'Not Exists' then 1 else 0 end end
-- (16 других проверок на допустимость опущены для краткости)
-- Обновляем соответствующую запись: заносим в нее результат -- проверки update @tempValidateLookupData set TestResult = @TestResult where Name = @Name and Value = @Value and Test = @Test end
SQL-синтаксис для возврата XML-данных Итак, проверки на допустимость выполнены, теперь нужно возвратить результаты вызывающему процессу. SQL Server 2000 поддерживает несколько механизмов вывода XML-данных с помощью директивы FOR XML оператора SELECT. Прежде чем продолжить рассказ о sptxValidateLookupData, я кратко рассмотрю еще кое-какие новшества в синтаксисе языка SQL.
В SQL Server 2000 три типа разделов FOR XML. FOR XML RAW и FOR XML AUTO позволяют сформировать простейший XML-вывод с минимумом усилий и соответственно с отсутствием контроля над форматом вывода. Большинство уважающих себя программистов для SQL Server 2000 используют FOR XML EXPLICIT. При применении режима EXPLICIT программист полностью контролирует вид XML-документа, возвращаемого запросом, и должен обеспечить синтаксическую корректность и допустимость XML-документа.
Как выглядит содержимое раздела EXPLICIT? Существует ряд жестких синтаксических требований к формированию запросов, использующих режим EXPLICIT. Каждый запрос с режимом EXPLICIT должен содержать два поля метаданных. У первого поля, указываемого в операторе SELECT, должно быть имя Tag и тип int. Это номер тэга текущего элемента, т. е. фактически номер типа поддерева. У второго поля должно быть имя Parent и тоже тип int. Оно содержит номер тэга элемента, который является родителем текущего элемента. Эти поля описывают иерархию XML-дерева. Если поле Parent записи имеет значение 0 или NULL, его данные располагаются на вершине XML-иерархии. Кроме этого единственного исключения, все значения поля Parent должны соответствовать ранее объявленным значениям тэгов. Заметьте: набор результатов должен содержать ровно одну запись, у которой поле Parent содержит 0 или NULL, причем она должна быть первой в наборе результатов (если имеется более одной записи, у которой номер тэга родителя равен 0 или NULL, генерируется XML-фрагмент).
Имена остальных полей запроса должны соответствовать специальному формату, который определяет, как имена элементов связаны с номерами тэгов, и задает имена атрибутов для генерируемых XML-данных. Имя поля имеет формат:
[ElementName!TagNumber!AttributeName!Directive]
где ElementName - имя элемента (если в качестве ElementName указано "Countries", результатом будет ), а TagNumber - номер тэга элемента. TagNumber вместе с полями метаданных Tag и Parent описывает иерархию XML-дерева. Каждый TagNumber соответствует одному ElementName, а AttributeName является именем XML-атрибута (если оно задано).
Рассмотрение использования Directive и работы в нескольких ситуациях, в которых AttributeName может быть NULL, выходит за рамки данной статьи. Раздел "Using Explicit Mode" в SQL Server 2000 Books Online - превосходный источник дополнительной информации.
Кроме того, для формирования корректных XML-данных набор результатов должен быть упорядочен так, чтобы каждый родитель шел непосредственно перед своими потомками.
Вернемся к sptxValidateLookupData. Как вы помните, в описании процедуры говорилось о двух форматах возвращаемого набора результатов. Если все проверки данного пакета возвратили TRUE, я возвращаю пустой тэг . Но если какая-то проверка потерпела неудачу, я возвращаю XML-документ со списком ошибок.
В листинге 4 приведен код процедуры sptxValidateLookupData, который генерирует наборы результатов с помощью FOR XML EXPLICIT. Синтаксис режима EXPLICIT довольно многословен, поэтому рассмотрим его по частям. Первая ветвь оператора IF обрабатывает простую ситуацию, когда все мои проверки возвратили TRUE:
-- Если все проверки на допустимость пройдены, -- возвращаем пустой набор XML-данных... if not exists (select [TestResult] from @tempValidateLookupData where TestResult = 0) select 1 as TAG, 0 as parent, NULL as [Results!1!] for xml explicit
Листинг 4. Применение FOR XML EXPLICIT
-- Если все проверки на допустимость пройдены, -- возвращаем пустой набор XML-данных... if not exists (select [TestResult] from @tempValidateLookupData where TestResult = 0) select 1 as TAG, 0 as parent, NULL as [Results!1!] for xml explicit
-- ...иначе возвращаем XML-данные о проверках, -- потерпевших неудачу else select 1 as TAG, 0 as parent, NULL as [Errors!1!], NULL as [Error!2!], NULL as [Error!2!ErrorMessage], NULL as [Error!2!FieldName]
union all
select 2 as TAG, 1 as parent, NULL, NULL, ltrim(rtrim(value)) + ' does not exist in ' + type, [name]
from @tempValidateLookupData where [TestResult] = 0 and test = 'Exists'
union all
select 2 as TAG, 1 as parent, NULL, NULL, ltrim(rtrim(value)) + ' already exists in ' + type, [name]
from @tempValidateLookupData where [TestResult] = 0 and test = 'Not Exists'
for xml explicit
Поскольку AttributeName не задано, этот оператор создаст единственный XML-элемент Results без атрибутов и потомков:
Ветвь ELSE гораздо интереснее: в ней я формирую XML-данные о проверках, потерпевших неудачу, объединяя операторы SELECT с помощью UNION. Как показано в листинге 5, каждый запрос формирует узлы выходных XML-данных определенного типа (который описывается в комментариях после каждого запроса).
Листинг 5. Формирование узлов
-- ...иначе возвращаем XML-данные о проверках, -- потерпевших неудачу else select 1 as TAG, 0 as parent, NULL as [Errors!1!], NULL as [Error!2!], NULL as [Error!2!ErrorMessage], NULL as [Error!2!FieldName]
-- (приведенный выше запрос формирует элемент <Errors>)
union all
select 2 as TAG, 1 as parent, NULL, NULL, ltrim(rtrim(value)) + ' does not exist in ' + type, [name]
from @tempValidateLookupData where [TestResult] = 0 and test = 'Exists'
-- (приведенный выше запрос формирует узлы вида -- <Error ErrorMessage="BO1 does not exist in Countries" -- FieldName="CountryCode"/>)
union all
select 2 as TAG, 1 as parent, NULL, NULL, ltrim(rtrim(value)) + ' exists in ' + type, [name]
from @tempValidateLookupData where [TestResult] = 0 and test = 'Not Exists'
-- (приведенный выше запрос формирует узлы вида -- <Error ErrorMessage="JA exists in Countries" -- FieldName="CountryCode"/>)
for xml explicit
Заметьте: запросы, объединенные UNION, упорядочены так, чтобы каждый потомок шел сразу за своим родителем, хотя узлы могут быть потомками нескольких подзапросов (у запросов "exists" и "not exists" поля tag и parent имеют одинаковые значения). Когда запросы, объединенные с помощью UNION, обрабатываются в режиме FOR XML EXPLICIT, сериализатор FOR XML вычисляет имена полей
[ElementName!TagNumber!AttributeName!Directive]
и значения полей метаданных Tag и Parent, а затем выводит XML-иерархию, заданную программистом:
<Errors> <Error ErrorMessage="JA exists in Countries" FieldName="CountryCode"/> <Error ErrorMessage="BO1 does not exist in Countries" FieldName="CountryCode"/> <Error ErrorMessage="EN does not exist in ContactPreferences" FieldName="ContactPreferenceCode"/> </Errors>
Миссия выполнена Имея в распоряжении описанные выше средства, вы можете запрашивать и генерировать XML-данные в среде SQL Server 2000. Но какими бы мощными ни были эти средства, SQL Server 2005 предоставит еще больше программных функций всем специалистам, обрабатывающим XML-данные с помощью T-SQL, и позволит добиться еще большей производительности.
XML-программирование в SQL Server 2005 При разработке SQL Server 2005 Microsoft значительно усовершенствовала поддержку XML. Последние два года я создавал новую систему на основе внутренних версий этого продукта (рай для SQL-разработчика!) и рад сообщить, что программистов, использующих XML, ждет масса хороших новостей.
Весь синтаксис поддержки XML, введенный в SQL Server 2000, не изменился, но в SQL Server 2005 в него внесена уйма дополнений. Я рассмотрю некоторые из них и покажу, как изменится хранимая процедура sptxValidateLookupData, рассмотренная в статье. Я также расскажу о следующих новшествах SQL Server 2005: о типах данных XML, FOR XML PATH, TYPE, синтаксисе nodes().
Все методики работы с XML, применяемые в SQL Server 2000 (передача XML-строк хранимым процедурам в параметрах типа ntext, манипулирование ими с помощью sp_xml_preparedocument, sp_xml_removedocument, FOR XML EXPLICIT и OPENXML), доступны и в SQL Server 2005, но в новой версии появились средства, позволяющие использовать другие подходы. Как только вы поработаете с новым синтаксисом, вам уже не захочется возвращаться к старому.
В SQL Server 2005 внесены революционные изменения в сам механизм хранения данных, в частности введены три новых типа данных: nvarchar(max), varbinary(max) и xml. В T-SQL возникали сложности с обработкой значений типа ntext, поэтому, если вы собираетесь по-прежнему использовать те же конструкции работы с XML, что и в SQL Server 2000, имеет смысл отказаться от ntext и перейти на nvarchar(max) и varbinary(max). Однако наиболее интересен тип данных XML, также добавленный в ядро сервера. Он предоставляет программистам ряд совершенно новых возможностей.
Переработанный пример Как вы помните, хранимая процедура sptxValidateLookupData, рассмотренная в статье, поддерживает единый процесс пакетной проверки пользовательского ввода в Web-страницы, позволяющий выполнить несколько разных проверок на допустимость для значений, получаемых из гетерогенных источников. Директивы, описывающие проверки, передаются хранимой процедуре в одном XML-документе. Хранимая процедура выполняет проверки и возвращает результаты вызывающему процессу, тоже в одном XML-документе.
Новая версия процедуры Как и в процедуре для SQL Server 2000, я помещаю содержимое входного XML-документа в табличную переменную. Но я больше не вызываю sp_xml_preparedocument и не применяю OPENXML. Как показано в листинге 6, я изменил тип данных параметра на xml (с ntext) и воспользовался новым синтаксисом nodes().
Листинг 6. Использование типа данных XML
create procedure sptxValidateLookupData @XMLString xml as
-- Создаем табличную переменную для хранения -- данных о проверках declare @tempValidateLookupData table ( [Type] nvarchar(30), [Name] nvarchar(30), [Value] nvarchar(300), [Test] nvarchar(30), [TestResult] int )
-- Заполняем табличную переменную данными о проверках, -- которые требуется выполнить insert @tempValidateLookupData select ref.value ('@Type', '[nvarchar](30)'), ref.value ('@Name', '[nvarchar](30)'), ref.value ('@Value', '[nvarchar](300)'), ref.value ('@Test', '[nvarchar](30)'), NULL from @XMLString.nodes('/ValidateData/Validate') as node(ref)
Давайте повнимательнее рассмотрим метод nodes. Он позволяет получить ссылку для каждой записи, которая соответствует элементу Validate, находящемуся внутри элемента ValidateData переменной @XMLString. Эта переменная описывается инструкцией AS NODE(ref) как набор записей, представляющий узлы (node rowset). В каждом определении поля в операторе SELECT вызывается метод value, извлекающий значение заданного атрибута. Он выполняется для каждой записи, поэтому для каждого элемента Validate с параметрами проверки генерируется по одной записи.
В каждом вызове метода value указывается имя атрибута элемента, извлекаемого из XML-узла. Перед именем атрибута ставится символ @, и оно заключается в одинарные кавычки, затем (также в кавычках) указывается тип данных SQL, сопоставляемый атрибуту.
Логика выполнения проверок на допустимость в новой версии sptxValidateLookupData осталась прежней.
Возврат результатов Как вы помните, в исходной спецификации процедуры описывались два формата возвращаемого набора записей. Если все проверки данного пакета возвратили TRUE, набор должен содержать пустой тэг . Однако, если какие-то проверки потерпели неудачу, нужно вернуть список XML-данных об ошибках.
В листинге 7 приведен новый код процедуры sptxValidateLookupData, генерирующий наборы результатов с помощью FOR XML PATH, TYPE. Обратите внимание, насколько этот синтаксис компактнее старого синтаксиса FOR XML EXPLICIT.
Листинг 7. Применение FOR XML PATH
-- Если все проверки на допустимость пройдены, -- возвращаем пустой набор XML-данных... if not exists (select [TestResult] from @tempValidateLookupData where TestResult = 0) select null for xml path ('Results'), type
-- ...иначе возвращаем XML-данные о проверках, -- потерпевших неудачу else select null, ( select ltrim(rtrim(value)) + ' does not exist in ' + type AS '@ErrorMessage', [name] AS '@FieldName'
from @tempValidateLookupData where [TestResult] = 0 and test = 'Exists'
for xml path ('Error'), type ), ( select ltrim(rtrim(value)) + ' already exists in ' + type AS '@ErrorMessage', [name] AS '@FieldName'
from @tempValidateLookupData where [TestResult] = 0 and test = 'Not Exists'
for xml path ('Error'), type ) for xml path ('Errors'), type
Как и прежде, первая ветвь оператора IF обрабатывает простой случай, когда все проверки возвратили TRUE:
-- Если все проверки на допустимость пройдены, -- возвращаем пустой набор XML-данных... if not exists (select [TestResult] from @tempValidateLookupData where TestResult = 0) select null for xml path ('Results'), type
Ветвь ELSE, как и раньше, формирует XML-данные для проверок, потерпевших неудачу, но на сей раз с помощью нескольких вложенных операторов SELECT FOR XML PATH, TYPE. Явно заданные имена полей указывают SQL Server, что их нужно использовать в качестве имен XML-атрибутов (перед ними должен идти символ @, их надо заключить в одинарные кавычки). Директивы FOR XML PATH ('Error') указывают серверу, что требуется обернуть XML-данные, создаваемые внутренними операторами SELECT, элементом Error, а директива FOR XML PATH ('Errors') - что внешний SELECT формирует корневой элемент 'Errors'. Таким образом, этот SQL-код генерирует тот же набор результатов, что и прежде, но с помощью гораздо более лаконичного запроса, чем запрос с FOR XML EXPLICIT.
Может, на первый взгляд это и не очевидно, но отказ от запросов SELECT, объединенных операторами UNION, выполняемых при использовании FOR XML EXPLICIT, делает код гетерогенных запросов гораздо компактнее и удобнее в сопровождении. Например, недавно моя группа переписала одну пользовательскую функцию SQL Server 2000 длиной в 5000 строк (большую их часть составлял 43-уровневый запрос с FOR XML EXPLICIT). Новый синтаксис позволил уложиться в 497 строк.
Если вы хотите по-прежнему придерживаться модели с UNION (что сомнительно), то можете написать второй запрос SELECT так, как показано в листинге 8. Этот подход не сработал бы, если бы ваши внутренние XML-узлы содержали разные количества атрибутов. Одно из основных преимуществ нового вложенного синтаксиса в том, что в отличие от операторов SELECT с UNION количество полей в подзапросах не обязательно должно быть одинаковым. Тем не менее, пример в листинге 8 демонстрирует еще одну новую конструкцию - FOR XML ROOT. Она позволяет объявить корневой элемент возвращаемых XML-данных, не создавая явный запрос SELECT. Дополнительные сведения по этому вопросу см. в статье "XML Options in Microsoft SQL Server 2005" (EN).
Листинг 8. Применение FOX XML ROOT
select ltrim(rtrim(value)) + ' does not exist in ' + type AS '@ErrorMessage', [name] AS '@FieldName'
from @tempValidateLookupData where [TestResult] = 0 and test = 'Exists'
union all
select ltrim(rtrim(value)) + ' already exists in ' + type AS '@ErrorMessage', [name] AS '@FieldName'
from @tempValidateLookupData where [TestResult] = 0 and test = 'Not Exists'
for xml path ('Error'), root ('Errors'), type
С помощью этих новых средств SQL Server 2005 вы можете запрашивать и генерировать XML-данные еще эффективнее, чем раньше. Я рассмотрел лишь небольшую часть возможностей нового синтаксиса. SQL Server 2005 позволяет делать с XML-данными почти все, что угодно. Если вас интересуют ресурсы с самой свежей информацией о SQL Server 2005, посетите сайт "Introducing SQL Server 2005" (EN).