Преобразование одного столбца XML в несколько столбцов в T- SQL SQL -SERVER - PullRequest
0 голосов
/ 10 марта 2020

Я видел подобные вопросы с моим, но формат XML всегда был другим, так как формат XML, который у меня есть, не соответствует "стандартному" strcuture. Моя таблица выглядит следующим образом (один столбец с XML скобкой в ​​качестве значений строк):

|VAL|
|<person name="bob" age="22" city="new york" occupation="student"></person>|
|<person name="bob" age="22" city="new york" occupation="student"></person>|

И результат, который я ищу:

|Name|age|city    |occupation|
|bob |22 |new york|student   |
|bob |22 |new york|student   |

Я могу создать жестко закодированный сценарий с этими именами столбцов, но проблема в том, что у меня есть более 20 таблиц, для которых затем потребуется собственный сценарий. Я думаю, что есть способ, которым я могу динамически, принимая во внимание таблицу назначения и исходную таблицу (xml), у меня может быть процедура, где генерируются эти данные.

1 Ответ

2 голосов
/ 10 марта 2020

Ваш вопрос не совсем понятен ...

Насколько я понимаю, у вас есть множество различных XML-файлов, и вы хотите прочитать их в общем. Если это так, я бы посоветовал для вашего следующего вопроса отразить это в ваших образцах данных.

Одно общее утверждение: нет никакого способа обойти динамически создаваемые операторы в тех случаях, когда вы хотите задать описательные элементы набора результатов (в данном случае: имена столбцов) динамически. T- SQL опирается на некоторые вещи, которые вы должны знать заранее .

Попробуйте:

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

DECLARE @tbl TABLE(ID INT IDENTITY, Descr VARCHAR(100), VAL XML);
INSERT INTO @tbl VALUES
 ('One person',N'|<person name="bob" age="22" city="new york" occupation="student"></person>')
,('One more person','<person name="bob" age="22" city="new york" occupation="student"></person>')
,('One country','<country name="Germany" capital="Berlin" continent="Europe"></country>');

- этот запрос опирается на все возможные заранее известные атрибуты.
- общие атрибуты, такие как name , возвращаются для человека и для страны
- отличающиеся атрибуты возвращаются как NULL. - Одним из преимуществ может быть то, что вы можете использовать указанный тип данных c, если это необходимо.

SELECT t.ID 
      ,t.Descr
      ,t.VAL.value('(/*[1]/@name)[1]','nvarchar(max)') AS [name] 
      ,t.VAL.value('(/*[1]/@age)[1]','nvarchar(max)') AS [age] 
      ,t.VAL.value('(/*[1]/@city)[1]','nvarchar(max)') AS [city] 
      ,t.VAL.value('(/*[1]/@occupation)[1]','nvarchar(max)') AS [occupation] 
      ,t.VAL.value('(/*[1]/@city)[1]','nvarchar(max)') AS [city] 
      ,t.VAL.value('(/*[1]/@capital)[1]','nvarchar(max)') AS [capital] 
      ,t.VAL.value('(/*[1]/@continent)[1]','nvarchar(max)') AS [continent] 
FROM @tbl t;

- Этот запрос возвращается как классический список EAV (entity-attribute-value)
- В результате вы получаете каждый атрибут в отдельной строке

SELECT t.ID 
      ,t.Descr
      ,A.attrs.value('local-name(.)','nvarchar(max)') AS AttrName
      ,A.attrs.value('.','nvarchar(max)') AS AttrValue
FROM @tbl t
CROSS APPLY t.VAL.nodes('/*[1]/@*') A(attrs);

Оба подхода могут быть сгенерированы как оператор на уровне строки и затем выполнены с помощью EXEC() или sp_executesql.

Подсказка: один из подходов может состоять в том, чтобы вставить список EAV в допустимую промежуточную таблицу и перейти оттуда к условной агрегации , PIVOT или жестко закодированным представлениям.

Dynami c подхода

Чтобы прочитать элементы <person>, нам понадобится следующее:

SELECT t.ID 
      ,t.Descr
      ,t.VAL.value('(/*[1]/@name)[1]','nvarchar(max)') AS [name] 
      ,t.VAL.value('(/*[1]/@age)[1]','nvarchar(max)') AS [age] 
      ,t.VAL.value('(/*[1]/@city)[1]','nvarchar(max)') AS [city] 
      ,t.VAL.value('(/*[1]/@occupation)[1]','nvarchar(max)') AS [occupation] 
FROM @tbl t
WHERE VAL.value('local-name(/*[1])','varchar(100)')='person';

Все, что нам нужно сделать, это создать изменяющуюся часть :

Попробуйте:

Новый макет с реальной таблицей

CREATE TABLE SimulateYourTable(ID INT IDENTITY, Descr VARCHAR(100), VAL XML);
INSERT INTO SimulateYourTable VALUES
 ('One person',N'|<person name="bob" age="22" city="new york" occupation="student"></person>')
,('One more person','<person name="bob" age="22" city="new york" occupation="student"></person>')
,('One country','<country name="Germany" capital="Berlin" continent="Europe"></country>');

- Фильтр для <person> сущностей

DECLARE @entityName NVARCHAR(100)='person';

- это строка, представляющая команду

DECLARE @cmd NVARCHAR(MAX)=
'SELECT t.ID 
      ,t.Descr
      ***columns here***
FROM SimulateYourTable t
WHERE VAL.value(''local-name(/*[1])'',''varchar(100)'')=''***name here***''';

- с помощью этого мы можем создать все столбцы
--Hin t: С SQL Server 2017+ STRING_AGG() - намного проще!

DECLARE @columns NVARCHAR(MAX)=
(
    SELECT CONCAT(',t.VAL.value(''(/*[1]/@',Attrib.[name],')[1]'',''nvarchar(max)'') AS ',QUOTENAME(Attrib.[name]))
    FROM SimulateYourTable t
    CROSS APPLY t.VAL.nodes('//@*') AllAttrs(a)
    CROSS APPLY (SELECT a.value('local-name(.)','varchar(max)')) Attrib([name])
    WHERE VAL.value('local-name(/*[1])','varchar(100)')=@entityName
    GROUP BY Attrib.[name]
    FOR XML PATH(''),TYPE
).value('.','nvarchar(max)'); 

- Теперь мы добавим это в нашу команду

SET @cmd=REPLACE(@cmd,'***columns here***',@columns);
SET @cmd=REPLACE(@cmd,'***name here***',@entityName);

- Это команда.
- Подсказка: вы можете использовать это для создания физических VIEW без необходимости набирать их в ...

PRINT @cmd;

Вы можете использовать EXEC(@cmd), чтобы выполнить эту динамику c SQL и проверьте результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...