Практическое руководство по работе с полями 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>






