SQL Server将SELECT XML列拆分为任意单独的列

时间:2016-09-06 21:52:34

标签: sql-server xml tsql dynamic-sql

在我的应用程序中,我没有为对象预定义字段,用户可以定义自定义字段。我使用XML数据类型以名称值格式存储自定义字段。

e.g。我有Employees表,其中包含FN,LN,Email作为预定义列,CustomFields作为XML列来保存用户定义的字段。

不同的行可以包含不同的自定义字段。

e.g。第1行 - > John,Smith,jsmith @ example.com,

<root>
    <phone>123-123-1234</phone>
    <country>USA</country>
</root>

然后第2行 - &gt; Smith,John,sjohn @ example.com,

<root>
    <age>50</age>
    <sex>Male</sex>
</root>

可以为不同的员工记录定义任意数量的此类自定义字段。格式将始终相同

<root><field>value</field></root>

如何在选择Row1时将Phone和Country作为列返回,并在选择Row2时将Age和Sex作为列返回?

1 个答案:

答案 0 :(得分:1)

获取所有示例的临时表

CREATE TABLE #tbl (ID INT IDENTITY, FirstName VARCHAR(100),LastName VARCHAR(100),eMail VARCHAR(100),CustomFields XML);
INSERT INTO #tbl VALUES
 ('John','Smith','john.smith@test.com'
 ,'<root>
    <phone>123-123-1234</phone>
    <country>USA</country>
   </root>')
, ('Jane','Miller','jane.miller@test.com'
 ,'<root>
    <age>50</age>
    <sex>Male</sex>
   </root>');

选项1

  • 假设已修复一组已知的自定义字段。
  • 这允许类型安全阅读(年龄为INT)
  • 返回所有可能的列,未使用的是NULL

试试此代码

SELECT tbl.ID
      ,tbl.FirstName
      ,tbl.LastName
      ,tbl.eMail
      ,tbl.CustomFields.value('(/root/phone)[1]','nvarchar(max)') AS phone
      ,tbl.CustomFields.value('(/root/country)[1]','nvarchar(max)') AS country
      ,tbl.CustomFields.value('(/root/age)[1]','int') AS age
      ,tbl.CustomFields.value('(/root/sex)[1]','nvarchar(max)') AS sex
FROM #tbl AS tbl

这是结果

+----+-----------+----------+----------------------+--------------+---------+------+------+
| ID | FirstName | LastName | eMail                | phone        | country | age  | sex  |
+----+-----------+----------+----------------------+--------------+---------+------+------+
| 1  | John      | Smith    | john.smith@test.com  | 123-123-1234 | USA     | NULL | NULL |
+----+-----------+----------+----------------------+--------------+---------+------+------+
| 2  | Jane      | Miller   | jane.miller@test.com | NULL         | NULL    | 50   | Male |
+----+-----------+----------+----------------------+--------------+---------+------+------+
*/

选项2

  • 假设您事先不知道字段名称,则无法直接命名输出列
  • 但您可以使用通用名称,按行读取数据并执行PIVOT

试试这个:

SELECT p.*
FROM
(
    SELECT tbl.FirstName
          ,tbl.LastName
          ,tbl.eMail
          ,N'Col_' + CAST(ROW_NUMBER() OVER(PARTITION BY tbl.ID ORDER BY (SELECT NULL)) AS NVARCHAR(max)) AS ColumnName
          ,A.cf.value('local-name(.)','nvarchar(max)') + ':' +  A.cf.value('.','nvarchar(max)') AS cf
    FROM #tbl AS tbl
    CROSS APPLY tbl.CustomFields.nodes('/root/*') AS A(cf)  
) AS x
PIVOT
(
    MAX(cf) FOR ColumnName IN(Col_1,Col_2,Col_3,Col_4 /*add as many as you need*/)
) AS p

这是结果

+-----------+----------+----------------------+--------------------+-------------+-------+-------+
| FirstName | LastName | eMail                | Col_1              | Col_2       | Col_3 | Col_4 |
+-----------+----------+----------------------+--------------------+-------------+-------+-------+
| Jane      | Miller   | jane.miller@test.com | age:50             | sex:Male    | NULL  | NULL  |
+-----------+----------+----------------------+--------------------+-------------+-------+-------+
| John      | Smith    | john.smith@test.com  | phone:123-123-1234 | country:USA | NULL  | NULL  |
+-----------+----------+----------------------+--------------------+-------------+-------+-------+

选项3

  • 假设您不知道列,但需要正确命名的列
  • 关注:请注意这样一个事实,即{-1}}或VIEW这样的ad-hoc-SQL永远不会允许这样的方法后退...

这需要动态创建语句。我将创建选项1的语句,但用动态创建的列表替换修复列表:

inline TVF

结果与选项1中的结果相同,但采用完全动态的方法。

清理

DECLARE @DynamicColumns NVARCHAR(MAX)=
(
    SELECT ',tbl.CustomFields.value(''(/root/' + A.cf.value('local-name(.)','nvarchar(max)') + ')[1]'',''nvarchar(max)'') AS ' +  A.cf.value('local-name(.)','nvarchar(max)')
    FROM #tbl AS tbl
    CROSS APPLY tbl.CustomFields.nodes('/root/*') AS A(cf)  
    FOR XML PATH('')
);

DECLARE @DynamicSQL NVARCHAR(MAX)=
'   SELECT tbl.ID
      ,tbl.FirstName
      ,tbl.LastName
      ,tbl.eMail'
+ @DynamicColumns +
' FROM #tbl AS tbl;'

EXEC(@DynamicSQL);