2

I need to extract all attributes from a long list of various elements. Therefore, I'm seeking to build a loop going through all of my elements and return their attributes.

Through several posts I have been able to write the code below. However, I have more than a 1000 elements why I would extremely glad if it somehow is possible to build a loop around the latter part rather than copying it for all elements.

with cte as
(
select cast(
'<schema fwRel="2"> 
<taxFormId isPrimeKey="true" fkRef="C1-TXFRM" mapField="TAX_FORM_ID" dataType="string"/>  
<formType fkRef="C1-FRMTY" mapField="FORM_TYPE_CD" dataType="string"/>  
<bo suppress="true" required="true" fkRef="F1-BUSOB" mapField="BUS_OBJ_CD" dataType="string"/>
<transferReason mdField="C1_TXF_TFRRSN_FLG" dataType="lookup" mapXML="BO_DATA_AREA" lookup="C1_TXF_TFRRSN_FLG"/>  
</schema>' as xml) xml_col
)

select cte.xml_col.value('(/schema/taxFormId/@fkRef)[1]', 'varchar(100)') as Dummy1
cte.xml_col.value('(/schema/taxFormId/@mapField)[1]', 'varchar(100)') as Dummy2
cte.xml_col.value('(/schema/taxFormId/@dataType)[1]', 'varchar(100)') as Dummy3
cte.xml_col.value('(/schema/taxFormId/@mapXML)[1]', 'varchar(100)') as Dummy4

from cte

I hope I have provided enough information

Shnugo
  • 66,100
  • 9
  • 53
  • 114
D. Daugs
  • 35
  • 6

2 Answers2

1

An easy way to get attributes listed:

with cte as
(
select cast(
'<schema fwRel="2">
  <taxFormId isPrimeKey="true" fkRef="C1-TXFRM" mapField="TAX_FORM_ID" dataType="string" />
  <formType fkRef="C1-FRMTY" mapField="FORM_TYPE_CD" dataType="string" />
  <bo suppress="true" required="true" fkRef="F1-BUSOB" mapField="BUS_OBJ_CD" dataType="string" />
  <transferReason mdField="C1_TXF_TFRRSN_FLG" dataType="lookup" mapXML="BO_DATA_AREA" lookup="C1_TXF_TFRRSN_FLG" />
</schema>' as xml) xml_col
)

--The query uses .nodes(N'/schema/*') to list all nodes below <schema> and .nodes(N'@*') to list all attributes within this node:

select nd.value(N'local-name(.)',N'nvarchar(max)') AS NodeName
      ,attr.value(N'local-name(.)',N'nvarchar(max)') AS AttrName
      ,attr.value(N'.',N'nvarchar(max)') AS AttrValue
from cte
OUTER APPLY xml_col.nodes(N'/schema/*') AS A(nd)
OUTER APPLY A.nd.nodes(N'@*') AS B(attr)

The result:

taxFormId       isPrimeKey  true
taxFormId       fkRef       C1-TXFRM
taxFormId       mapField    TAX_FORM_ID
taxFormId       dataType    string
formType        fkRef       C1-FRMTY
formType        mapField    FORM_TYPE_CD
formType        dataType    string
bo              suppress    true
bo              required    true
bo              fkRef       F1-BUSOB
bo              mapField    BUS_OBJ_CD
bo              dataType    string
transferReason  mdField     C1_TXF_TFRRSN_FLG
transferReason  dataType    lookup
transferReason  mapXML      BO_DATA_AREA
transferReason  lookup      C1_TXF_TFRRSN_FLG

If you need a statement like the one in your example one could create it dynamically (as string) and use EXEC to execute this (dynamic SQL).

Shnugo
  • 66,100
  • 9
  • 53
  • 114
0

It may be a little over-kill, but I often use a TVF to parse and hash large XML files. The function will return the data in a parent/child hierarchy structure with range keys R1/R2.

For example:

Declare @XML xml='
<schema fwRel="2"> 
    <taxFormId isPrimeKey="true" fkRef="C1-TXFRM" mapField="TAX_FORM_ID" dataType="string"/>  
    <formType fkRef="C1-FRMTY" mapField="FORM_TYPE_CD" dataType="string"/>  
    <bo suppress="true" required="true" fkRef="F1-BUSOB" mapField="BUS_OBJ_CD" dataType="string"/>
    <transferReason mdField="C1_TXF_TFRRSN_FLG" dataType="lookup" mapXML="BO_DATA_AREA" lookup="C1_TXF_TFRRSN_FLG"/>
</schema>'

Select * from [dbo].[udf-XML-Hier](@XML) Order by R1

Returns

enter image description here

You'll notice there are columns for Element Name, Attribute Name, XPath, Title (optional), and Value. The R1,R2,Lvl,ID and PT are all derived.

Being a TVF, you could apply any WHERE or ORDER desired


The UDF (with original source) if interested

CREATE FUNCTION [dbo].[udf-XML-Hier](@XML xml)

Returns Table 
As Return

with  cte0 as ( 
                  Select Lvl       = 1
                        ,ID        = Cast(1 as int) 
                        ,Pt        = Cast(NULL as int)
                        ,Element   = x.value('local-name(.)','varchar(150)')
                        ,Attribute = cast('' as varchar(150))
                        ,Value     = x.value('text()[1]','varchar(max)')
                        ,XPath     = cast(concat(x.value('local-name(.)','varchar(max)'),'[' ,cast(Row_Number() Over(Order By (Select 1)) as int),']') as varchar(max))
                        ,Seq       = cast(10000001 as varchar(max))
                        ,AttData   = x.query('.') 
                        ,XMLData   = x.query('*') 
                  From   @XML.nodes('/*') a(x) 
                  Union  All
                  Select Lvl       = p.Lvl + 1 
                        ,ID        = Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10
                        ,Pt        = p.ID
                        ,Element   = c.value('local-name(.)','varchar(150)')
                        ,Attribute = cast('' as varchar(150))
                        ,Value     = cast( c.value('text()[1]','varchar(max)') as varchar(max) ) 
                        ,XPath     = cast(concat(p.XPath,'/',c.value('local-name(.)','varchar(max)'),'[',cast(Row_Number() Over(PARTITION BY c.value('local-name(.)','varchar(max)') Order By (Select 1)) as int),']') as varchar(max) )
                        ,Seq       = cast(concat(p.Seq,' ',10000000+Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10) as varchar(max))
                        ,AttData   = c.query('.') 
                        ,XMLData   = c.query('*') 
                  From   cte0 p 
                  Cross  Apply p.XMLData.nodes('*') b(c) 
              )
    , cte1 as (   
                  Select R1 = Row_Number() over (Order By Seq),A.*
                  From  (
                          Select  Lvl,ID,Pt,Element,Attribute,Value,XPath,Seq From cte0
                          Union All
                          Select Lvl       = p.Lvl+1
                                ,ID        = p.ID + Row_Number() over (Order By (Select NULL)) 
                                ,Pt        = p.ID
                                ,Element   = p.Element
                                ,Attribute = x.value('local-name(.)','varchar(150)')
                                ,Value     = x.value('.','varchar(max)')
                                ,XPath     = p.XPath + '/@' + x.value('local-name(.)','varchar(max)')
                                ,Seq       = cast(concat(p.Seq,' ',10000000+p.ID + Row_Number() over (Order By (Select NULL)) ) as varchar(max))
                          From   cte0 p 
                          Cross  Apply AttData.nodes('/*/@*') a(x) 
                        ) A 
               )

Select A.R1
      ,R2  = IsNull((Select max(R1) From cte1 Where Seq Like A.Seq+'%'),A.R1)
      ,A.Lvl
      ,A.ID
      ,A.Pt
      ,A.Element
      ,A.Attribute
      ,A.XPath
      ,Title = Replicate('|---',Lvl-1)+Element+IIF(Attribute='','','@'+Attribute)
      ,A.Value
 From  cte1 A

/*
Source: http://beyondrelational.com/modules/2/blogs/28/posts/10495/xquery-lab-58-select-from-xml.aspx

Declare @XML xml='<person><firstname preferred="Annie" nickname="BeBe">Annabelle</firstname><lastname>Smith</lastname></person>'
Select * from [dbo].[udf-XML-Hier](@XML) Order by R1
*/
John Cappelletti
  • 79,615
  • 7
  • 44
  • 66
  • Thanks! :) I managed to get this working as well. However, as you stated yourself it is a minor overkill. – D. Daugs Jan 11 '17 at 08:48