【问题标题】:TSQL XML - Node attributes as columns or rows in same queryTSQL XML - 节点属性作为同一查询中的列或行
【发布时间】:2022-01-10 07:37:55
【问题描述】:

我正在处理一个带有类似于此节点的 XML 文件:

<Process>    
    <Step No="1" Types="D" Temp="25" Secs="6" Macro="2">Enable Mixers</Step>
    <Step No="11" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>
    <Step No="2" Types="D2" Temp="22" Secs="62" Macro="23">Enable Mixers3</Step>
</Process>

DDL:

DROP TABLE IF EXISTS MyXML2
GO
CREATE TABLE MyXML2(ID INT IDENTITY(1,1), c XML)
INSERT MyXML2(c) VALUES 
    ('<Process><Step No="1" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>
     <Step No="11" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>
     <Step No="2" Types="D2" Temp="22" Secs="62" Macro="23">Enable Mixers3</Step></Process>')
GO

我需要得到一个这样的数据库结构(上面仅给出了 1 个“步骤”的示例):

StepNumber ColumnName ColumnValue
1 Types D
1 Temp 25
1 Secs 6
1 Macro 2

到目前为止我的工作:我已经能够将每个属性映射到一行。(编辑:根据上面的 DDL 更新为工作示例)

SELECT 
    col.value('local-name(.)', 'VARCHAR(50)') AS ColumnName,
    col.value('.[1]', 'VARCHAR(MAX)') AS ColumnValue
FROM [MyXML2]
CROSS APPLY [c].nodes('/Process/Step/@*') doc(col)

输出如下:

但我需要将“否”属性作为一列。有没有办法在一个查询中完成所有这些操作?

小提琴:http://sqlfiddle.com/#!18/e56828/9

【问题讨论】:

  • 您的查询和演示 XML 不适合您的结果集。请提供完整的演示,其中包括创建表的查询(而不是描述)和为示例插入几行的查询以及您在该表上的当前解决方案
  • 我已经用可重现的例子更新了这个问题。

标签: tsql xquery xquery-sql


【解决方案1】:

由于最初您没有提供 DDL+DML,因此我提供了两个示例。一个用于具有标识列(我的示例中为 ID)的表,一个没有(这意味着我需要使用 ROW_NUMBER 动态添加一个)

演示一:当我们有识别栏时

-- DDL+DML : this is something that the OP should provide!!!
DROP TABLE IF EXISTS MyXML2
GO
CREATE TABLE MyXML2(ID INT IDENTITY(1,1), c XML)
INSERT MyXML2(c) VALUES 
    ('<Step No="1" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>'),
    ('<Step No="11" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>'),
    ('<Step No="2" Types="D2" Temp="22" Secs="62" Macro="23">Enable Mixers3</Step>')
GO

-- Solution
;With MyCTE as (
    SELECT 
        MyXML2.ID,
        doc.Col.value('local-name(.[1])','VARCHAR(100)') ColumnName,
        doc.Col.value('.[1]','VARCHAR(100)') ColumnValue
    FROM MyXML2
    CROSS APPLY MyXML2.c.nodes('/Step/@*') doc(Col)
)
select
    StepNumber = (SELECT MyIn.ColumnValue from MyCTE as MyIn where MyIn.ColumnName = 'No' and MyIn.ID = MyCTE.ID)
    ,ColumnName,ColumnValue
from MyCTE
WHERE not ColumnName = 'No'
GO

演示二:当我们没有标识列时

-- DDL+DML : this is something that the OP should provide!!!
DROP TABLE IF EXISTS MyXML
GO
CREATE TABLE MyXML(c XML)
INSERT MyXML(c) VALUES 
    ('<Step No="1" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>'),
    ('<Step No="11" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>'),
    ('<Step No="2" Types="D2" Temp="22" Secs="62" Macro="23">Enable Mixers3</Step>')
GO

-- Solution
;With MyCTE1 AS (SELECT RN = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), c FROM MyXML)
, MyCTE2 as (
    SELECT 
        MyCTE1.RN,
        doc.Col.value('local-name(.[1])','VARCHAR(100)') ColumnName,
        doc.Col.value('.[1]','VARCHAR(100)') ColumnValue
    FROM MyCTE1
    CROSS APPLY MyCTE1.c.nodes('/Step/@*') doc(Col)
)
select
    StepNumber = (SELECT MyIn.ColumnValue from MyCTE2 as MyIn where MyIn.ColumnName = 'No' and MyIn.RN = MyCTE2.RN)
    ,ColumnName,ColumnValue
from MyCTE2
WHERE not ColumnName = 'No'
GO

结果如预期:


更新:2021-12-06

根据我们获得的新信息,这里有一些新的解决方案和解释。以上内容应该对未来有类似问题的读者有用。

因此,在上述解决方案中,我重点关注了表中每一行中都有单个 Step 节点的情况。根据新信息,我们可能在同一值中有多个Step 节点。此外,Step 节点被包裹在另一个节点名称 Process

例如,特定的 XML 值可以是:&lt;Process&gt;&lt;Step No="1" Types="D1" Temp="1" Secs="61" Macro="21"&gt;Enable Mixers1&lt;/Step&gt; &lt;Step No="11" Types="D11" Temp="11" Secs="611" Macro="21"&gt;Enable Mixers2&lt;/Step&gt; &lt;Step No="111" Types="D111" Temp="111" Secs="6111" Macro="23"&gt;Enable Mixers3&lt;/Step&gt;&lt;/Process&gt;

演示三:使用变量,Step节点结构未知,多个Step节点

在这个演示中,我将反对基于与解决方案一相同的方法的解决方案

declare @xml XML = '<Process><Step No="1" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>
     <Step No="11" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers2</Step>
     <Step No="2" Types="D2" Temp="22" Secs="62" Macro="23">Enable Mixers3</Step></Process>'

-->>> HIGHLY recommended to un-comment below lines and check what I am using as input for the CTE in this solution
--SELECT
--  t.c.value('./@No', 'VARCHAR(128)') as StepNumber,
--  t.c.query ('.') as Types
--from @xml.nodes('Process/.[1]/*')as t(c)
    
;With MyCTE01 as (
    SELECT
        t.c.value('./@No', 'INT') as StepNumber,
        t.c.query ('.') as MyXML
    from @xml.nodes('Process/.[1]/*')as t(c)
)
SELECT 
    MyCTE01.StepNumber,
    doc.Col.value('local-name(.[1])','VARCHAR(100)') ColumnName,
    doc.Col.value('.[1]','VARCHAR(100)') ColumnValue
FROM MyCTE01
CROSS APPLY MyCTE01.MyXML.nodes('/Step/@*') doc(Col)
WHERE not doc.Col.value('local-name(.[1])','VARCHAR(100)') = 'No'
GO

此解决方案对您有用,但如果 Step 节点的结构始终相同 - 这意味着您具有与讨论期间的所有示例相同的属性,那么我们可以获得更好的解决方案...

演示四:使用变量,Step节点有一个已知结构,多个Step节点

既然我们知道我们拥有哪个属性,那么我们就可以使用这些名称进行硬编码。在这种情况下,我们没有这部分意味着找到所有属性CROSS APPLY MyCTE01.MyXML.nodes('/Step/@*')

我们可以使用完全不同的方法,直接获取已知属性的值并使用 UNPIVOT。此解决方案提供了更好的性能,但不如解决方案三灵活。

declare @xml XML = '<Process><Step No="1" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>
     <Step No="11" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers2</Step>
     <Step No="2" Types="D2" Temp="22" Secs="62" Macro="23">Enable Mixers3</Step></Process>'

--select 
--  t.c.value('./@No', 'VARCHAR(128)') as id,
--  t.c.value('./@Types', 'VARCHAR(128)') as Types,
--  t.c.value('./@Temp', 'VARCHAR(128)') as Temp,
--  t.c.value('./@Secs', 'VARCHAR(128)') as Secs,
--  t.c.value('./@Macro', 'VARCHAR(128)') as Macro,
--  t.c.value('./@Macro', 'VARCHAR(128)') as Macro
--from @xml.nodes('Process/.[1]/*')as t(c)

SELECT StepNumber, Column_Name, Column_Value
FROM(
    select 
        t.c.value('./@No', 'VARCHAR(128)')    as StepNumber,
        t.c.value('./@Types', 'VARCHAR(128)') as Types,
        t.c.value('./@Temp', 'VARCHAR(128)')  as Temp,
        t.c.value('./@Secs', 'VARCHAR(128)')  as Secs,
        t.c.value('./@Macro', 'VARCHAR(128)') as Macro
    from @xml.nodes('Process/.[1]/*')as t(c)
   ) p  
UNPIVOT  
   (Column_Value FOR Column_Name IN (Types, Temp, Secs, Macro)  )AS unpvt;  
GO

注意!如果您使用动态查询并首先在 XML 中找到属性,您也可以将这种方法用于未知结构。

演示五:使用变量,Step节点有已知结构,多个Step节点

此解决方案与解决方案四(已知结构)具有相同的限制,但此外,它仅适用于我们处理像变量这样的单个值时。因此,如果我们想在表上实现它,那么我们可能需要循环所有行,这可能会显着降低性能。 但当此解决方案满足需求时,它应该提供最佳性能!

/***BEST SOLUTION - if fits the needs***/
-- XML to Tabular using OPENXML
DECLARE @idoc INT, @xml XML = '<Process><Step No="1" Types="D1" Temp="1" Secs="61" Macro="21">Enable Mixers1</Step>
     <Step No="11" Types="D11" Temp="11" Secs="611" Macro="21">Enable Mixers2</Step>
     <Step No="111" Types="D111" Temp="111" Secs="6111" Macro="23">Enable Mixers3</Step></Process>'
--Create an internal representation of the XML document.  
-- Reads the XML text -> parses the text by using the MSXML parser -> and provides the parsed document in a state ready for consumption. 
EXEC sp_xml_preparedocument @idoc OUTPUT, @xml;

--SELECT 
--  No    as StepNumber,
--  Types as Types,
--  Temp  as Temp,
--  Secs  as Secs,
--  Macro as Macro,
--  NoteValue
--FROM OPENXML (@idoc, '/Process/Step')  
--  WITH (
--      -- When OPENXML does not have input of third parameter then we can choose if this will atribute or node
--      -- usig '@No' will bring the value of atribute and using 'No' will bring the value of node
--      No        INT          '@No'   ,
--      Types     VARCHAR(128) '@Types',  
--      Temp      VARCHAR(128) '@Temp' ,  
--      Secs      VARCHAR(128) '@Secs' ,  
--      Macro     VARCHAR(128) '@Macro', 
--      NoteValue VARCHAR(128) '.'
--  ) 

SELECT StepNumber, Column_Name, Column_Value
FROM(
    SELECT 
    No    as StepNumber,
    Types as Types,
    Temp  as Temp,
    Secs  as Secs,
    Macro as Macro
    FROM OPENXML (@idoc, '/Process/Step',1)  
        WITH (
            No    INT,
            Types VARCHAR(128),  
            Temp  VARCHAR(128),  
            Secs  VARCHAR(128),  
            Macro VARCHAR(128)
        ) 
   ) p  
UNPIVOT  
   (Column_Value FOR Column_Name IN (Types, Temp, Secs, Macro)  )AS unpvt;  
--sp_xml_removedocument free's up the memory.  
EXEC sp_xml_removedocument @idoc   
GO

所以...我们有多种方法适合不同的情况...但我们仍然需要考虑表格...

演示六:使用table,Step节点结构未知,多个Step节点

如果适合(已知结构或使用动态查询),您可以实现演示四,但对于最后一个演示,我将在一个表中有多行的情况下实现演示三方法,每行包含具有多个Step 节点

DROP TABLE IF EXISTS MyXML_Tbl
GO
CREATE TABLE MyXML_Tbl(ID INT IDENTITY(1,1), MyXML XML)
GO
INSERT MyXML_Tbl(MyXML) VALUES 
    ('<Process><Step No="1" Types="D1" Temp="1" Secs="61" Macro="21">Enable Mixers1</Step>
     <Step No="11" Types="D11" Temp="11" Secs="611" Macro="21">Enable Mixers1</Step>
     <Step No="111" Types="D111" Temp="111" Secs="6111" Macro="23">Enable Mixers3</Step></Process>')
INSERT MyXML_Tbl(MyXML) VALUES 
    ('<Process><Step No="2" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>
     <Step No="22" Types="D1" Temp="11" Secs="61" Macro="21">Enable Mixers1</Step>
     <Step No="222" Types="D2" Temp="22" Secs="62" Macro="23">Enable Mixers3</Step></Process>')
GO

--SELECT * FROM MyXML_Tbl
--GO

--SELECT
--  tb.ID,
--  tx.c.value('./@No', 'VARCHAR(128)') as StepNumber,
--  tx.c.query ('.') as Types
--from MyXML_Tbl tb
--CROSS APPLY tb.MyXML.nodes('Process/.[1]/*')as tx(c)

;With MyCTE01 as (
    SELECT
        tb.ID,
        tx.c.value('./@No', 'VARCHAR(128)') as StepNumber,
        tx.c.query ('.') as MyXML
    from MyXML_Tbl tb
    CROSS APPLY tb.MyXML.nodes('Process/.[1]/*')as tx(c)
)
SELECT 
    MyCTE01.id,
    MyCTE01.StepNumber,
    doc.Col.value('local-name(.[1])','VARCHAR(100)') ColumnName,
    doc.Col.value('.[1]','VARCHAR(100)') ColumnValue
FROM MyCTE01
CROSS APPLY MyCTE01.MyXML.nodes('/Step/@*') doc(Col)
WHERE not doc.Col.value('local-name(.[1])','VARCHAR(100)') = 'No'
GO

我希望这是有用的。它应该涵盖讨论中提到的所有案例

【讨论】:

  • 谢谢你。您的答案适用于您的查询,但我所有的 都在同一个 XML 中。完全是我的错,因为没有提供足够的细节!!如果您可以再看一下,我已经更新了我的问题以提供更清晰和可重复的示例。
  • 嗨@GaiusAugustus,stackoverflow 在讨论方面非常有问题。它没有提供讨论的好地方,而只关注问题的最终版本和接受的单一答案(即使在现实生活中有多个答案)。因此,人们编辑原件可能会导致不相关的答案/评论并破坏讨论渠道。我讨厌这个,但它就是这样。因此,我将通过添加更多信息而不删除现有选项来编辑答案
  • 感谢您的所有时间和工作!我已经能够修改 Demo 6 以获得我所需要的。我真的很感激。
  • 不客气@GaiusAugustus :-) 我很高兴能帮上忙。祝你有美好的一天。
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-13
  • 1970-01-01
  • 2018-10-18
  • 1970-01-01
相关资源
最近更新 更多