Search code examples
sqlxml

SQL - extract XML with multiple nodes


I'm trying to extract data from XML in my SQL column that has multiple lists of data in them that are stored in two different ways.

I'm only familiar with using cross apply to get a single list but how do I do it for multiple nodes, especially if they are in different structures?

DECLARE @XML XML = 
'
<root>
<Bundle name="x">
<Profiles>
    <Profile>
        <ApplicationRef>
            <Reference class="a" name="application1"/>
        </ApplicationRef>
        <Constraints>
            <Filter property="e" value="ent1"/>
            <Filter property="e" value="ent2"/>
        </Constraints>
    </Profile>
    <Profile>
        <ApplicationRef>
            <Reference class="a" name="application2"/>
        </ApplicationRef>
        <Constraints>
            <Filter property="e" value="entA"/>
            <Filter property="e" value="entB"/>
        </Constraints>
    </Profile>
</Profiles>
</Bundle>
<Bundle name="y">
<Profiles>
    <Profile>
        <ApplicationRef>
            <Reference class="a" name="application1"/>
        </ApplicationRef>
        <Constraints>
            <Filter property="e" value="ent3"/>
            <Filter property="e" value="ent4"/>
        </Constraints>
    </Profile>
    <Profile>
        <ApplicationRef>
            <Reference class="a" name="application3"/>
        </ApplicationRef>
        <Constraints>
            <Filter property="m" value="entA">
                <Value>
                    <List>
                        <String>this</String>
                        <String>that</String>
                        <String>thus</String>
                    </List>
                </Value>
            </Filter>
        </Constraints>
    </Profile>
</Profiles>
</Bundle>
</root>
'

SELECT
    Bun = XC.value('@name', 'VARCHAR(200)'),
    App =  XC2.value('@name', 'VARCHAR(200)'),
    Ent =  XC3.value('.', 'VARCHAR(200)')
FROM 
    @Xml.nodes('//Bundle') AS XT(XC)
CROSS APPLY
    xc.nodes('//Bundle/Profiles/Profile/ApplicationRef/Reference') AS XT2(XC2)
CROSS APPLY
    xc.nodes('//Bundle/Profiles/Profile/Constraints/Filter/Value/List/String') AS XT3(XC3)

I don't mind if the ent data is in a single column, but the Bun and App Columns need to different

Bun App Ent
x application1 ent1
x application1 ent2
x application2 entA
x application2 entB
y application1 ent3
y application1 ent4
y application3 this
y application3 that
y application3 thus

Solution

  • Hopefully, here is what you are looking for.

    SQL

    DECLARE @xml XML = 
    N'<root>
        <Bundle name="x">
            <Profiles>
                <Profile>
                    <ApplicationRef>
                        <Reference class="a" name="application1"/>
                    </ApplicationRef>
                    <Constraints>
                        <Filter property="e" value="ent1"/>
                        <Filter property="e" value="ent2"/>
                    </Constraints>
                </Profile>
                <Profile>
                    <ApplicationRef>
                        <Reference class="a" name="application2"/>
                    </ApplicationRef>
                    <Constraints>
                        <Filter property="e" value="entA"/>
                        <Filter property="e" value="entB"/>
                    </Constraints>
                </Profile>
            </Profiles>
        </Bundle>
        <Bundle name="y">
            <Profiles>
                <Profile>
                    <ApplicationRef>
                        <Reference class="a" name="application1"/>
                    </ApplicationRef>
                    <Constraints>
                        <Filter property="e" value="ent3"/>
                        <Filter property="e" value="ent4"/>
                    </Constraints>
                </Profile>
                <Profile>
                    <ApplicationRef>
                        <Reference class="a" name="application3"/>
                    </ApplicationRef>
                    <Constraints>
                        <Filter property="m" value="entA">
                            <Value>
                                <List>
                                    <String>this</String>
                                    <String>that</String>
                                    <String>thus</String>
                                </List>
                            </Value>
                        </Filter>
                    </Constraints>
                </Profile>
            </Profiles>
        </Bundle>
    </root>';
    
    SELECT  p.value('@name', 'VARCHAR(200)') AS bundle_name
        , c.value('(ApplicationRef/Reference/@name)[1]', 'VARCHAR(200)') AS app_name
        , d.value('(.)[1]', 'VARCHAR(200)') AS constraints_filter
    FROM @Xml.nodes('/root/Bundle') t1(p)
    CROSS APPLY p.nodes('Profiles/Profile') AS t2(c)
    CROSS APPLY c.nodes('Constraints/Filter/@value') AS t3(d)
    UNION ALL
    SELECT  p.value('@name', 'VARCHAR(200)') AS bundle_name
        , c.value('(ApplicationRef/Reference/@name)[1]', 'VARCHAR(200)') AS app_name
        , e.value('(text())[1]', 'VARCHAR(200)') AS constraints_filter_list
    FROM @Xml.nodes('/root/Bundle') t1(p)
    CROSS APPLY p.nodes('Profiles/Profile') AS t2(c)
    CROSS APPLY c.nodes('Constraints/Filter/Value/List/String') AS t4(e);
    

    Output

    bundle_name app_name constraints_filter
    x application1 ent1
    x application1 ent2
    x application2 entA
    x application2 entB
    y application1 ent3
    y application1 ent4
    y application3 entA
    y application3 this
    y application3 that
    y application3 thus