Как развернуть атрибуты столбца XML в T-SQL

Мне нужно выполнить сводку для столбца XML в таблице, где XML содержит несколько элементов с рядом атрибутов. Атрибуты в каждом элементе всегда одинаковы, однако количество элементов будет различным. Позвольте мне привести пример...

FormEntryId |               FormXML                                    | DateCreated
====================================================================================
1           |<Root>                                                    | 10/15/2009
            |  <Form>                                                  |
            |    <FormData FieldName="Username" FieldValue="stevem" /> |
            |    <FormData FieldName="FirstName" FieldValue="Steve" /> |
            |    <FormData FieldName="LastName" FieldValue="Mesa" />   |
            |  </Form>                                                 |
            |</Root>                                                   |
            |                                                          |
------------------------------------------------------------------------------------
2           |<Root>                                                    | 10/16/2009
            |  <Form>                                                  |
            |    <FormData FieldName="Username" FieldValue="bobs" />   |
            |    <FormData FieldName="FirstName" FieldValue="Bob" />   |
            |    <FormData FieldName="LastName" FieldValue="Suggs" />  |
            |    <FormData FieldName="NewField" FieldValue="test" />   |
            |  </Form>                                                 |
            |</Root>                                                   |

Мне нужно получить результирующий набор для каждого отдельного значения атрибута FieldName (в этом примере, Username, FirstName, LastName и NewField) с соответствующими им атрибутами FieldValue в качестве значения. Результаты для примера, который я привел выше, будут выглядеть так:

FormEntryId | Username | FirstName | LastName | NewField | DateCreated
======================================================================
1           | stevem   | Steve     | Mesa     | NULL     | 10/15/2009
----------------------------------------------------------------------
2           | bobs     | Bob       | Suggs    | test     | 10/16/2009

Я нашел способ сделать это с помощью статических столбцов

SELECT
    FormEntryId,
    FormXML.value('/Root[1]/Form[1]/FormData[@FieldName="Username"][1]/@FieldValue','varchar(max)') AS Username,
    FormXML.value('/Root[1]/Form[1]/FormData[@FieldName="FirstName"][1]/@FieldValue','varchar(max)') AS FirstName,
    FormXML.value('/Root[1]/Form[1]/FormData[@FieldName="LastName"][1]/@FieldValue','varchar(max)') AS LastName,
    FormXML.value('/Root[1]/Form[1]/FormData[@FieldName="NewField"][1]/@FieldValue','varchar(max)') AS NewField,
    DateCreated
FROM FormEntry

Однако я хотел бы посмотреть, есть ли способ, чтобы столбцы были динамическими, основанными на различном наборе значений атрибута "FieldName".

2 ответа

Решение

Взгляните на этот динамический стержень, а в последнее время - на то, что вам нужно SELECT DISTINCT FieldName использовать эту технику для динамического построения вашего запроса.

Вот полный ответ для вашей конкретной проблемы (обратите внимание, что существует слабость порядка столбцов при генерации списка из отдельных атрибутов, зная, в каком порядке должны появляться столбцы):

DECLARE @template AS varchar(MAX)
SET @template = 'SELECT 
    FormEntryId
    ,{@col_list}
    ,DateCreated 
FROM FormEntry'

DECLARE @col_template AS varchar(MAX)
SET @col_template = 'FormXML.value(''/Root[1]/Form[1]/FormData[@FieldName="{FieldName}"][1]/@FieldValue'',''varchar(max)'') AS {FieldName}'

DECLARE @col_list AS varchar(MAX)

;WITH FieldNames AS (
    SELECT DISTINCT FieldName
    FROM FormEntry
    CROSS APPLY (
        SELECT X.FieldName.value('@FieldName', 'varchar(255)')
        FROM FormXML.nodes('/Root[1]/Form[1]/FormData') AS X(FieldName)
    ) AS Y (FieldName)
)
SELECT @col_list = COALESCE(@col_list + ',', '') + REPLACE(@col_template, '{FieldName}', FieldName)
FROM FieldNames

DECLARE @sql AS varchar(MAX)
SET @sql = REPLACE(@template, '{@col_list}', @col_list)

EXEC (@sql)

Динамический свод не зря встроен в язык. Было бы необходимо просканировать всю таблицу, содержащую потенциальные имена столбцов, прежде чем структура результата была бы известна. В результате структура таблицы динамического сводного оператора будет неизвестна до времени выполнения. Это создает много проблем, касающихся разбора и интерпретации языка.

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

Другие вопросы по тегам