Практическое руководство по работе с полями XML в MS SQL
Для работы с XML полями используются методы SQL Server.
Все методы описаны в официальном руководстве Microsoft.
Так же есть хорошее описание на сайте sql-tutorial.ru.
ВАЖНО!
В статье используется база данных, в которой таблицы ОТВЯЗАНЫ от XML схем. При привязанных XML схемах некоторые запросы не будут выполнены.
Чтобы посмотреть привязку таблицы к XML схеме, можно выполнить следующий запрос, который выведет привязанные схемы
Запрос
select *
from sys.xml_schema_collections XSC
join sys.xml_schema_namespaces XSN on (XSC.xml_collection_id = XSN.xml_collection_id)
where XSC.name = 'position';
Результат

Основные методы для работы с XML
Нумерация узлов в XML документах начинается с 1
В методах для поиска узла используются языки запросов XPath или XQuery.
Для работы с XML документом в T-SQL используются следующие методы:
- query
- value
- exist
- nodes
- modify
Метод query
Метод query используется для извлечения XML узлов. Метод возвращает экземпляр нетипизированного XML.
Метод value
Метод value извлекает значение узла с указанием типа данных. Данный метод возвращает скалярное значение. При невозможности преобразовать данные в нужный тип — сервер выкидывает ошибку. Нужно явно указывать номер узла, за исключением использования оператора точка '.' - выбрать все содержимое.
Метод exist
Метод exist проверяет существование указанного выражения XPath. Возвращает значение типа bit, которое представляет одно из следующих состояний:
- 1 - означающее
True, если выражение на языкеXQueryпри запросе возвращает непустой результат, то есть возвращается как минимум один узелXML. - 0 - означающее
Falseпри возвращении пустого результата. - NULL - если экземпляр типа
xml, к которому был выполнен запрос, содержит значениеNULL.
Метод nodes
Метод nodes разбивает XML документ в соответствии с указанным выражением XPath, создавая при этом собственную таблицу из найденных XML узлов.
Пример. У нас есть XML документ со следующей структурой:
<root>
<label>Макароны</label>
<value>200 гр</value>
<label>Фрукты</label>
<value>400 гр</value>
<label>Орехи</label>
<value>100 гр</value>
</root>
Использование метода nodes('root/value') AS Table1(field) приведет к созданию таблицы Table1 и столбцом field, где каждая строка — это найденный узел по выражению Xpath

Метод используется в сочетании с другими методами или с оператором CROSS APPLY. Использование с оператором CROSS APPLY создаст эффект LEFT JOIN исходной таблицы с таблицей, созданной nodes.
Метод modify
Метод modify изменяет XML документ. Используется преимущественно в выражении UPDATE и может за один раз изменить только одно значение.
Для метода существует 3 ключевых оператора:
Каждый оператор говорит сам за себя: вставка, удаление, замена значения.
insert
Оператор insert позволяет добавлять XML элементы. Имеет следующие вспомогательные операторы для уточнения места добавления:
{ as first || as last } into- добавляет данные в узел как дочерние.as lastдобавляет последним потомком, является поведением по умолчанию.as first intoдобавит данные первым потомком.after- добавляет данные после указанного узла.before- добавляет данные перед указанным узлом.
delete
Оператор delete удаляет часть XML документа найденного по выражению XPath.
replace value of
Оператор replace value of изменяет содержимое узла.
Сборник задач SQL
Получаем первый XML узел по его имени
Найдем первый узел с именем lastname
Запрос
Результат
В результате получим первый узел lastname со всем его содержимым. Номер узла для получения задается [1]
Получаем все XML узлы по имени
Найдем все узлы с именем name. В нашем документе всего три узла с таким именем и все они вложены в разные элементы custom_elem
Запрос
select data.query('(/collaborator/custom_elems/custom_elem/name)')
from collaborator
where id = 6148914691236517121
Результат
В результате получим все найденные узлы по указанному пути, так как мы не указали номер узла, который нам нужен
Получаем узел со всем его содержимым
Найдем узлы кастомных полей с его родителем. Кастомные поля хранятся в узлах с именем custom_elem, корневой узел для которых custom_elems
Запрос
Результат
В результате получим узел custom_elems со всеми вложенными узлами
Получаем XML узел по номеру родителя
Найдем узел name из второго узла custom_elem. Для этого используем явное указание номера элемента
Запрос
select data.query('(collaborator/custom_elems/custom_elem[2]/name)')
from collaborator
where id = 6148914691236517121
Результат
В результате получим элемент name из второго по счету элемента custom_elem
Получаем XML узел по значению его вложенного узла
Найдем узел custom_elem в котором содержится узел name со значением fld_city
Запрос
select data.query('(/collaborator/custom_elems/custom_elem[name="fld_city"])[1]')
from collaborator
where id = 6148914691236517121
Результат
Конструкция ...custom_elem[name="fld_city"]... ищет узел custom_elem в котором находится узел name со значением fld_city и возвращает подительский узел custom_elem
Получаем XML узел по значению соседнего XML узла
Найдем элемент value, который находится в элементе custom_elem, где вложенный элемент name имеет значение fld_city.
Запрос
select data.query('(/collaborator/custom_elems/custom_elem[name="fld_city"]/value)[1]')
from collaborator
where id = 6148914691236517121
Результат
Получаем значение XML узла
Найдем значения узлов с основными данными сотрудника (ИД, фамилию, дату рождения и флаг уволен сотрудник или нет)
Запрос
select data.value('(/collaborator/id)[1]', 'bigint') as id
,data.value('(/collaborator/lastname)[1]', 'varchar(40)') as lastname
,data.value('(/collaborator/birth_date)[1]', 'date') as birth_date
,data.value('(/collaborator/is_dismiss)[1]', 'bit') as is_dismiss
from collaborator
where id = 6148914691236517121
Результат

Обратите внимание
Если попытаться привести поле lastname к дате, то получим ошибку

Получаем значение соседнего XML узла
Найдем значения кастомных полей с именами fld_city, fld_sex и fld_birth_date
Запрос
select data.value('(/collaborator/custom_elems/custom_elem[name="fld_city"]/value)[1]', 'varchar(50)') as fld_city
,data.value('(/collaborator/custom_elems/custom_elem[name="fld_sex"])[1]/value[1]', 'varchar') as fld_sex
,data.value('(/collaborator/custom_elems/custom_elem[name="fld_birth_date"]/value)[1]', 'datetime') as fld_birth_date
from collaborator
where id = 6148914691236517121
Результат

Конструкция ...custom_elem[name="fld_city"]/value... ищет узел с именем custom_elem, в который вложен узел name со значением fld_city. Дальше мы добавляем /value - то есть в найденном узле custom_elem с вложенным узлом <name>fld_city</name> обращаемся к узлу <value>...</value> и получаем его значение. На самом деле name и value это всего лишь имена вложенных узлов
Получаем значения нескольких соседних узлов XML
Найдем из списка состояний сотрудника history_states первое состояние history_state, поле state_id которого имеет значение vacation, и выведем значения узлов start_date и finish_date в формате даты.
Запрос:
select data.value('(/collaborator/history_states/history_state[state_id="vacation"]/start_date)[1]', 'date') as start_date
,data.value('(/collaborator/history_states/history_state[state_id="vacation"]/finish_date)[1]', 'date') as finish_date
from collaborator
where id = 6148914691236517121
Результат

Получаем последний дочерний узел XML
Когда количество узлов неизвестно, можно получить последний узел с помощью функции last(), предпоследний last()-1 и т.д.
Запрос
select data.value('(/collaborator/custom_elems/custom_elem[last()]/value)[1]', 'varchar(50)') as fld_birth_date
,data.value('(/collaborator/custom_elems/custom_elem[last()-1]/value)[1]', 'varchar(50)') as fld_sex
,data.value('(/collaborator/custom_elems/custom_elem[last()-2]/value)[1]', 'varchar(50)') as fld_city
,data.value('(/collaborator/custom_elems/custom_elem[last()-3]/value)[1]', 'varchar(50)') as not_fount
,data.value('(/collaborator/custom_elems/custom_elem[last()-4]/value)[1]', 'varchar(50)') as not_fount
from collaborator
where id = 6148914691236517121
Результат

Если узла не существует - возвращает NULL
Использование агрегации для значений XML узлов
У сотрудника есть 2 отпуска. В комментариях к состоянию хранится количество дней отпуска. Нам нужно получить количество отпусков и их сумму дней. Для этого применим 2 функции count() и sum()
Запрос
select data.value('count(/collaborator/history_states/history_state/comment)','int') as count_days
,data.value('sum(/collaborator/history_states/history_state/comment)','int') as sum_days
from collaborator
where id = 6148914691236517121
ВАЖНО
Конструкция 'sum(/collaborator/history_states/history_state/comment)' сработает только в том случае, если таблица отвязана от XML схемы, иначе будет ошибка типов

Результат

Проверяем XML узел
Проверим существование XML узла в документах - password
Запрос
select data.value('(/collaborator/login)[1]','varchar(50)') as lastname
,data.value('(/collaborator/password)[1]','varchar(50)') as password
,data.exist('(/collaborator/password)') as exist_password
from collaborator
Результат

Обратите внимание
Наличие значения в узле password не влияет на результат

Проверяем равенство значения XML узла с нашим значением
Найдем все строки с узлом lastname и его значение Иванов. Регистр важен
Запрос
select data.value('(/collaborator/lastname)[1]','varchar(50)') as lastname
,data.exist('(/collaborator/lastname[text()="Иванов"])') as exist_lastname
from collaborator
order by data.exist('(/collaborator/lastname[text()="Иванов"])') desc
Результат

Проверяем, содержит ли значение XML узла подстроку
Запрос
select data.value('(/collaborator/lastname)[1]','varchar(50)') as lastname
,data.exist('/collaborator/lastname/text()[contains(., "ван")]') as exist_lastname
from collaborator
order by data.exist('/collaborator/lastname/text()[contains(., "ван")]') desc
Результат

В примере используется дополнительная конструкция [contains(., "ван")]. Функция contains(str, substr) проверяет, содержит ли строка str подстроку substr. Оператор . - возвращает все найденное содержимое предшествующему запросу XPath - значение узла lastname. Регистр важен
Проверяем значение кастомного поля в WebTutor
Найдем все записи, где custom_elem содержит узел name со значением fld_city и узел value со значением Киров
Запрос
select data.value('(/collaborator/lastname)[1]','varchar(50)') as lastname
,data.value('(/collaborator/custom_elems/custom_elem[name="fld_city"]/value)[1]','varchar(50)') as message_sent
,data.exist('/collaborator/custom_elems/custom_elem[name="fld_city"]/value[text() = "Киров"]') as exist_result
from collaborator
order by data.exist('/collaborator/custom_elems/custom_elem[name="fld_city"]/value[text() = "Киров"]') desc
Результат

Разделяем по разным строкам каждый найденный XML узел
Для каждого имеющегося узла custom_elem выведем значения его дочерних узлов
Запрос
select data.value('(/collaborator/id)[1]','bigint') as id
,Table1.field.query('.') as nodes_element
from collaborator
cross apply collaborator.data.nodes('(/collaborator/custom_elems/custom_elem)') as Table1(field)
where id = 6148914691236517121
Результат
Получим строки с каждым найденным custom_elem, оператор . в методе query выведет все содержимое найденного узла

Доработаем запрос и добавим вывод значений дочерних узлов внутри custom_elem
Запрос
select data.value('(/collaborator/id)[1]','bigint') as id
,Table1.field.query('.') as nodes_element
,Table1.field.query('name') as nodes_name
,Table1.field.query('./value') as nodes_value
from collaborator
cross apply collaborator.data.nodes('(/collaborator/custom_elems/custom_elem)') as Table1(field)
where id = 6148914691236517121
Результат
Благодаря тому, что метод nodes возвращает свой собственный XML документ, мы можем обращаться к его содержимому относительного этого документа

Запрос
Доработаем запрос и для найденных элементов получим их значения
select data.value('(/collaborator/id)[1]','bigint') as id
,Table1.field.query('.') as nodes_element
,Table1.field.query('name').value('.', 'varchar(100)') as nodes_name
,Table1.field.value('(./value)[1]', 'varchar(100)') as nodes_value
from collaborator
cross apply collaborator.data.nodes('(/collaborator/custom_elems/custom_elem)') as Table1(field)
where id = 6148914691236517121
Результат
Мы получили желаемый результат, у нас есть название кастомного поля и его значение

Получаем порядковые номера XML узлов на одном уровне
Для получения порядковых номеров мы будем использовать метод nodes и его особенность, при которой узлы возвращаются в порядке их следования в XML документе. Получим порядковые номера всех кастомных полей custom_elem.
Запрос
select id
,Table1.field.query('.') as query
,Table1.field.value('count( for $s in (/collaborator/custom_elems/custom_elem) where $s << . return(.) ) + 1', 'int') as node_number
,row_number() over (partition by id order by id) as node_number_light
from collaborator
cross apply collaborator.data.nodes('(/collaborator/custom_elems/custom_elem)') as Table1(field)
where id = 6148914691236517121
Результат

В методе value используется запрос XQuery и побитовая операция <<. Говоря простым языком, конструкция count( for $s in (/collaborator/custom_elems/custom_elem) where $s << . return(.) ) возвращает количество всех предшествующих узлов относительно текущего, возвращенного методом nodes
То есть, для первого custom_elem нет предшествующих узлов, поэтому количество будет равно 0. Следующим метод nodes возвращает второй custom_elem, количество предшествующих узлов становится 1 и так далее. Обратите внимание, путь до узла в методе nodes полностью соответствует пути в запросе XQuery. Так как конструкция считает количество предшествующих элементов, то отсчет начинается с 0. Так как функция count возвращает число, мы можем производить над результатом математические операции
Существует более простой способ получить порядковые номера узлов, можно использовать оконную функцию ROW_NUMBER, которая легче в понимании и даёт аналогичный результат
Добавляем XML узел в документ
Добавим новый узел history_state в родительский history_states
Запрос
update collaborator
set data.modify('insert
<history_state>
<id>hpb91b</id>
<state_id>vacation</state_id>
<start_date>2025-01-01T00:00:00+00:00</start_date>
<finish_date>2025-09-09T00:00:00+00:00</finish_date>
<comment>9</comment>
</history_state>
into
(collaborator/history_states)[1]
')
where id = 6148914691236517121
Результат
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>3</comment>
</history_state>
<history_state>
<id>hpb91a</id>
<state_id>vacation</state_id>
<start_date>2024-09-16T00:00:00+00:00</start_date>
<finish_date>2024-09-23T00:00:00+00:00</finish_date>
<comment>7</comment>
</history_state>
</history_states>
...
</collaborator>
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>3</comment>
</history_state>
<history_state>
<id>hpb91a</id>
<state_id>vacation</state_id>
<start_date>2024-09-16T00:00:00+00:00</start_date>
<finish_date>2024-09-23T00:00:00+00:00</finish_date>
<comment>7</comment>
</history_state>
<history_state>
<id>hpb91b</id>
<state_id>vacation</state_id>
<start_date>2025-01-01T00:00:00+00:00</start_date>
<finish_date>2025-09-09T00:00:00+00:00</finish_date>
<comment>9</comment>
</history_state>
</history_states>
...
</collaborator>

Оператор into добавляет узел последним потомком. Для точного указания места вставки используются ключевые слова as first и as last
Запрос
update collaborator
set data.modify('insert
<history_state>
<id>hpb91c</id>
<state_id>vacation</state_id>
<start_date>2026-01-01T00:00:00+00:00</start_date>
<finish_date>2026-09-09T00:00:00+00:00</finish_date>
<comment>9</comment>
</history_state>
as first into
(collaborator/history_states)[1]
')
where id = 6148914691236517121
Результат
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>3</comment>
</history_state>
<history_state>
<id>hpb91a</id>
<state_id>vacation</state_id>
<start_date>2024-09-16T00:00:00+00:00</start_date>
<finish_date>2024-09-23T00:00:00+00:00</finish_date>
<comment>7</comment>
</history_state>
<history_state>
<id>hpb91b</id>
<state_id>vacation</state_id>
<start_date>2025-01-01T00:00:00+00:00</start_date>
<finish_date>2025-09-09T00:00:00+00:00</finish_date>
<comment>9</comment>
</history_state>
</history_states>
...
</collaborator>
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>hpb91c</id>
<state_id>vacation</state_id>
<start_date>2026-01-01T00:00:00+00:00</start_date>
<finish_date>2026-09-09T00:00:00+00:00</finish_date>
<comment>9</comment>
</history_state>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>3</comment>
</history_state>
<history_state>
<id>hpb91a</id>
<state_id>vacation</state_id>
<start_date>2024-09-16T00:00:00+00:00</start_date>
<finish_date>2024-09-23T00:00:00+00:00</finish_date>
<comment>7</comment>
</history_state>
<history_state>
<id>hpb91b</id>
<state_id>vacation</state_id>
<start_date>2025-01-01T00:00:00+00:00</start_date>
<finish_date>2025-09-09T00:00:00+00:00</finish_date>
<comment>9</comment>
</history_state>
</history_states>
...
</collaborator>

Добавляем XML узел на указанную позицию
Запрос
declare @comment varchar(100) = 'Вставили коммент';
update collaborator
set data.modify('insert
<comment>{ sql:variable("@comment") }</comment>
after
(collaborator/history_states/history_state[1]/finish_date)[1]
')
where id = 6148914691236517121
Результат
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
</history_state>
<history_state>
<id>hpb91a</id>
<state_id>vacation</state_id>
<start_date>2024-09-16T00:00:00+00:00</start_date>
<finish_date>2024-09-23T00:00:00+00:00</finish_date>
<comment>7</comment>
</history_state>
</history_states>
...
</collaborator>
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>Вставили коммент</comment>
</history_state>
<history_state>
<id>hpb91a</id>
<state_id>vacation</state_id>
<start_date>2024-09-16T00:00:00+00:00</start_date>
<finish_date>2024-09-23T00:00:00+00:00</finish_date>
<comment>7</comment>
</history_state>
</history_states>
...
</collaborator>

Удаляем XML узел с его содержимым
Запрос
update collaborator
set data.modify('delete
(collaborator/history_states/history_state[last()])
')
where id = 6148914691236517121
Результат
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>Вставили коммент</comment>
</history_state>
<history_state>
<id>hpb91a</id>
<state_id>vacation</state_id>
<start_date>2024-09-16T00:00:00+00:00</start_date>
<finish_date>2024-09-23T00:00:00+00:00</finish_date>
<comment>7</comment>
</history_state>
</history_states>
...
</collaborator>
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>Вставили коммент</comment>
</history_state>
</history_states>
...
</collaborator>

Изменить значение узла
Запрос
update collaborator
set data.modify('replace value of
(collaborator/history_states/history_state[1]/comment/text())[1]
with
"Обновили комментарий"
')
where id = 6148914691236517121
Результат
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>Вставили коммент</comment>
</history_state>
</history_states>
...
</collaborator>
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>Обновили комментарий</comment>
</history_state>
</history_states>
...
</collaborator>

Заменяем значения узла значением из переменной
Запрос:
declare @comment varchar(100) = 'котомёт'
update collaborator
set data.modify('replace value of
(collaborator/history_states/history_state[1]/comment/text())[1]
with
sql:variable("@comment")
')
where id = 6148914691236517121
Результат
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>Обновили комментарий</comment>
</history_state>
</history_states>
...
</collaborator>
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>котомёт</comment>
</history_state>
</history_states>
...
</collaborator>
Заменяем значения узла значением из другого поля
Запрос:
update collaborator
set data.modify('replace value of
(collaborator/history_states/history_state[1]/comment/text())[1]
with
sql:column("id")
')
where id = 6148914691236517121
Результат
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>Обновили комментарий</comment>
</history_state>
</history_states>
...
</collaborator>
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<history_states>
<history_state>
<id>92d6c6</id>
<state_id>vacation</state_id>
<start_date>2024-09-01T00:00:00+00:00</start_date>
<finish_date>2024-09-03T00:00:00+00:00</finish_date>
<comment>6148914691236517121</comment>
</history_state>
</history_states>
...
</collaborator>
Собираем значения нескольких полей в одном поле по ключу
Исходные данные
Таблица с мобильными номерами

Запрос:
select login
,rtrim(ltrim(substring(phone_number, 2, 50))) as phone_number
from (
select login
,(
select '; ' + cast(phone_number as varchar)
from phone_numbers
where A.login = login
order by login, phone_number
for xml path('')
) as phone_number
from phone_numbers A
) A
group by login, phone_number
Результат:

Получение полной иерархии подразделений
Исходные данные
Структура подразделений

Запрос
with tree ( id
,name
,pathstr
) as
(
select id
,name
,CAST(name as varchar(max))
from subdivisions
where parent_object_id is null
union all
select S.id
,S.name
,t.pathstr + '/'+ S.name
from subdivisions S inner join tree T on T.id = S.parent_object_id
)
select id
,pathstr
,CAST('<sub>' + REPLACE(pathstr, '/', '</sub><sub>') + '</sub>' as xml) as pathstrXML
from tree
Результат

На рисунке мы получили полный путь до подразделения, составленный из наименований родительских подразделений. Дополнительный столбец pathstrXML - это искусственно созданный столбец для удобства последующих работ
ВАЖНО
Обратите внимание на разделитель, в нашем случае это /, в некоторых случаях его понадобится заменить на собственные, если в названиях подразделений символ / является частью названия
ВАЖНО
Так же запрос выполнится с ошибкой, если в названиях подразделений присутствуют символы >, <, &. Ошибка проявляется при попытке собрать XML из названий подразделений, чтобы обойти эту ошибку, эти символы следует заменить на HTML-представление (& -> &)
Получение иерархии подразделений вниз, начиная с конкретного подразделения
Исходные данные
Структура подразделений, нам нужно получить "Отдел обучения и развития" и все дочерние

Запрос
declare @sub_id bigint = 6327975429225669223;
with tree (id, name) as (
select id, name
from [subdivisions]
where id = @sub_id
union all
select V.id, v.name
from [subdivisions] V inner join tree t on t.id = V.parent_object_id
)
select A.id, A.name
from tree A
Результат

Получаем список подразделений по иерархии вверх, включая само подразделение
Исходные данные
Структура подразделений, нам нужно получить штатное распиание для "Отдел дистанционного обучения"

Запрос
with tree (id, name, parent_object_id, level) as (
select A.id
,A.name
,A.parent_object_id
,1
from subdivisions A
where A.id = 6327975429225669224
union all
select A.id
,A.name
,A.parent_object_id
,B.level + 1
from subdivisions A inner join tree B on A.id = B.parent_object_id
)
select A.*
,(select max(level) from tree) - A.level + (select min(level) from tree) as reverse_level
from tree A
Результат

Получаем значения XML узлов и собираем в строку через разделитель
Соберем категории сотрудника в одну строку через разделитель
Исходные данные
Запрос
select id
,data.query('for $elem in collaborator/category_id return <s>{concat($elem, ";")}</s>').value('.', 'varchar(max)')
from collaborator
where id = 6148914691236517121
Результат

Удаление узлов без дочерних элементов
Проблема - пустые узлы custom_elem, нужно удалить их из XML
Исходные данные
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<custom_elems>
<custom_elem>
<name>fld_city</name>
<value>Киров</value>
</custom_elem>
<custom_elem>
<name>fld_sex</name>
<value>М</value>
</custom_elem>
<custom_elem>
<name>fld_birth_date</name>
<value>1988-06-02T00:00:00+00:00</value>
</custom_elem>
<custom_elem />
<custom_elem />
<custom_elem />
<custom_elem />
<custom_elem />
<custom_elem />
</custom_elems>
...
</collaborator>
Запрос
Для удаления пустых узлов в XML можно использовать функцию node(), которая возвращает все дочерние узлы
update collaborator
set data.modify('
delete
(collaborator/custom_elems/custom_elem[count(node())=0])
')
where id = 6148914691236517121
Результат
<collaborator SPXML-FORM="x-local://wtv/wtv_collaborator.xmd">
<id>6148914691236517121</id>
<lastname>Иванов</lastname>
...
<custom_elems>
<custom_elem>
<name>fld_city</name>
<value>Киров</value>
</custom_elem>
<custom_elem>
<name>fld_sex</name>
<value>М</value>
</custom_elem>
<custom_elem>
<name>fld_birth_date</name>
<value>1988-06-02T00:00:00+00:00</value>
</custom_elem>
</custom_elems>
...
</collaborator>






