How do I PIVOT on an XML column with XQuery?

JF Landry 41 Reputation points
2021-05-25T22:03:38.897+00:00

Hello,
I would like to pivot the attributes and values of an SQL XML column and display it in an ordered columns and row format.

To get started, this is what the XML column looks like in SQL:

<Model xmlns:dsc="ModelDescriptive" xmlns:draw="ModelDraw" xmlns:display="Display" xmlns:thd="Model3D">
<dsc:Model name="15DE">
<dsc:Defs>
<dsc:Materials>
<dsc:Material ref=E-HD-AnoAno" baseRef="E-HD" type="rod" productionGroup="OPTION" />
<dsc:Material ref="E-3L44A-CLEAR" baseRef="E-3L44A" type="piece" productionGroup="OPTION" />
<dsc:Material ref="R-TH01" baseRef="R-TH-TH" type="rod" productionGroup="ASSEMBLY" />
<dsc:Material ref="M-TH05" baseRef="M-TH" type="meter" productionGroup="MOLDING" />
</dsc:Materials>
</dsc:Defs>
</dsc:Model>

…and here is my query :

SELECT  
  x.y.value('local-name(..)', 'VARCHAR(MAX)') ParentElementName  
 ,x.y.value('local-name(.)', 'VARCHAR(MAX)') Attribut  
       ,x.y.value('.', 'VARCHAR(MAX)') Value  
 ,Row_number() Over(Partition by x.y.value('local-name(..)', 'VARCHAR(MAX)') order by x.y.value('local-name(..)', 'VARCHAR(MAX)') ASC)  as rn  
FROM @xmlDescriptive.nodes('//*[text()], //@*') AS x(y)  
WHERE x.y.value('local-name(..)', 'VARCHAR(MAX)')= 'Material'  
ORDER BY x.y.value('local-name(..)', 'VARCHAR(MAX)')  

I get an interesting result using the Nodes method of XQuery and filtering only on the material from the bill of material (BOM).
However, I would like to present the data in a standardized fashion.

The result I get looks like this:

99603-image.png

Then I transform this result to rotate it using the Pivot method but I find my result in are unordered:

Here is my method of trying to pivot my information:

SELECT [ref], [baseRef], [type], [productionGroup]  
FROM   
(  
 SELECT  
  x.y.value('local-name(..)', 'VARCHAR(MAX)') ParentElementName  
 ,x.y.value('local-name(.)', 'VARCHAR(MAX)') Attribut  
 ,x.y.value('.', 'VARCHAR(MAX)') Valeur  
 ,CAST(Row_number() Over(Partition by x.y.value('local-name(.)', 'VARCHAR(MAX)') order by x.y.value('local-name(..)', 'VARCHAR(MAX)') ASC) as varchar(10)) as rn  
 FROM @xmlDescriptive.nodes('//@*') AS x(y)  
 WHERE x.y.value('local-name(..)', 'VARCHAR(MAX)')= 'Material'  

) d  
pivot  
(  
  max(Valeur)  
  FOR Attribut in ([ref], [baseRef], [type], [productionGroup])  
) piv   

I am a little confused. I would like to have my result formatted this way but ordered. What is wrong with my reasoning?

99942-image.png

I would like my result to be presented like this:

99876-image.png

Developer technologies | Transact-SQL
{count} votes

Accepted answer
  1. Yitzhak Khabinsky 26,586 Reputation points
    2021-05-26T00:14:28.807+00:00

    Hi @JF Landry ,

    I took a liberty and fixed your XML based on my limited understanding of your data.
    Check it out.

    SQL

    -- DDL and sample data population, start  
    DECLARE @xml XML =  
    N'<Model xmlns:dsc="ModelDescriptive" xmlns:draw="ModelDraw"  
           xmlns:display="Display" xmlns:thd="Model3D">  
    	<dsc:Model name="15DE">  
    		<dsc:Defs>  
    			<dsc:Materials>  
    				<dsc:Material ref="E-HD-AnoAno" baseRef="E-HD" type="rod"  
    				              productionGroup="OPTION"/>  
    				<dsc:Material ref="E-3L44A-CLEAR" baseRef="E-3L44A" type="piece"  
    				              productionGroup="OPTION"/>  
    				<dsc:Material ref="R-TH01" baseRef="R-TH-TH" type="rod"  
    				              productionGroup="ASSEMBLY"/>  
    				<dsc:Material ref="M-TH05" baseRef="M-TH" type="meter"  
    				              productionGroup="MOLDING"/>  
    			</dsc:Materials>  
    		</dsc:Defs>  
    	</dsc:Model>  
    </Model>';  
      
    WITH XMLNAMESPACES ('ModelDescriptive' AS dsc)  
    SELECT c.value('@ref', 'VARCHAR(100)') AS [ref]  
    	, c.value('@baseRef', 'VARCHAR(100)') AS [baseRef]  
    	, c.value('@type', 'VARCHAR(100)') AS [type]  
    	, c.value('@productionGroup', 'VARCHAR(100)') AS [productionGroup]  
    FROM @xml.nodes('/Model/dsc:Model/dsc:Defs/dsc:Materials/dsc:Material') AS t(c);  
    

    Output

    +---------------+---------+-------+-----------------+  
    |      ref      | baseRef | type  | productionGroup |  
    +---------------+---------+-------+-----------------+  
    | E-HD-AnoAno   | E-HD    | rod   | OPTION          |  
    | E-3L44A-CLEAR | E-3L44A | piece | OPTION          |  
    | R-TH01        | R-TH-TH | rod   | ASSEMBLY        |  
    | M-TH05        | M-TH    | meter | MOLDING         |  
    +---------------+---------+-------+-----------------+  
    
    1 person found this answer helpful.
    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. JF Landry 41 Reputation points
    2021-05-26T14:30:43.5+00:00

    Hi @Yitzhak Khabinsky ,
    As you may have seen, I am not an expert with XML and XQuery. I believed that by providing a small sample of the XML file, I could get some insight into the problem. I had failed to give any attributes so by taking a closer look at your answer, I found my solution.

    Using your sample, I found the answer. It would look more like this:

    DECLARE @xml XML =  
     N'<Model xmlns:dsc="ModelDescriptive" xmlns:draw="ModelDraw" xmlns:display="Display" xmlns:thd="Model3D">  
     <dsc:Model name="15DE">  
         <dsc:Defs>  
             <dsc:Materials>  
     <dsc:Material ref="E-HD-AnoAno" baseRef="E-HD" type="rod"  
                               productionGroup="OPTION"/>  
     <dsc:Material ref="E-3L44A-CLEAR" baseRef="E-3L44A" type="piece"  
                               productionGroup="OPTION"/>  
     <dsc:Material ref="R-TH01" baseRef="R-TH-TH" type="rod"  
                               productionGroup="ASSEMBLY"/>  
      <dsc:Material ref="M-TH05" baseRef="M-TH" type="meter"  
                               productionGroup="MOLDING"/>  
      </dsc:Materials>  
     </dsc:Defs>  
     </dsc:Model>  
     </Model>';  
      
     -- DDL and sample data population, end  
      ;WITH XMLNAMESPACES ('ModelDescriptive' as dsc, 'ModelDraw' as draw, 'Display' as display)  
      SELECT c.value('@ref', 'VARCHAR(100)') AS [ref]  
         , c.value('@baseRef', 'VARCHAR(100)') AS [baseRef]  
         , c.value('@type', 'VARCHAR(100)') AS [type]  
         , c.value('@productionGroup', 'VARCHAR(100)') AS [productionGroup]  
      FROM @xml.nodes('descendant::dsc:Model/dsc:Defs/dsc:Materials/dsc:Material') AS t(c);  
    

    While looking on the web, I also found that a part was missing : descendant::dsc:Model/dsc:Defs/dsc:Materials/dsc:Material

    The end result is now ordered.

    99917-image.png

    Many thanks for your help!


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.