【问题标题】:SQL Server split SELECT XML column as arbitrary individual columnsSQL Server 将 SELECT XML 列拆分为任意单个列
【发布时间】:2016-09-06 21:52:34
【问题描述】:

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

例如我有员工表,其中 FN、LN、电子邮件作为预定义列,自定义字段作为 XML 列来保存用户定义的字段。

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

例如第 1 行 -> John,Smith,jsmith@example.com,

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

然后是第 2 行 -> 史密斯,约翰,sjohn@example.com,

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

并且可以为不同的员工记录定义任意数量的此类自定义字段。格式总是一样的

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

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

【问题讨论】:

  • 假设我没看错,我认为您需要动态 SQL 来生成列名/值。例如DECLARE @cols VARCHAR(4000) = ''; SELECT @cols += ', ''' + a.b.value('(.)[1]', 'varchar(255)') + ''' AS ' + QUOTENAME(a.b.value('local-name(.)', 'varchar(255)')) FROM myTable CROSS APPLY myXMLColumn.nodes('root/*') a(b) WHERE Email = 'jsmith@example.com'; 然后您将创建一个动态 SQL 语句,例如 @SQL = 'SELECT fn, ln, email' + @cols + ' from myTable WHERE email = ''jsmith@example.com'';'; EXEC(@SQL);

标签: sql-server xml tsql dynamic-sql


【解决方案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

  • 假设您不知道列,但您需要正确命名列
  • 注意:请注意,在诸如 VIEWinline TVF 之类的 ad-hoc-SQL 中永远不会允许这种方法,这可能是一个很大的倒退...李>

这需要动态创建一个语句。我将创建选项 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);

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

清理

DROP TABLE #tbl;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-19
    相关资源
    最近更新 更多