Joining Xml nodes in SQL Server T-SQL

ekqde3dh  于 2023-08-02  发布在  SQL Server
关注(0)|答案(3)|浏览(112)

I have a SQL Server table with a VarChar column called "Reference" and an Xml column called XmlData. I need to be able to join two separate nodes within XmlData, but I'm really not sure how to go about it. This is an example of the data:

Declare     @MyTable    Table
                      ( Reference   VarChar(20)
                      , XmlData     Xml
                      )

Insert      @MyTable
Values    ( 'Reference1'
          , '<MainStuff>
                <FirstGroups>
                    <FirstGroup>
                        <PartOne>ABC</PartOne>
                        <PartTwo>123</PartTwo>
                        <OutputOne>Get this value ABC-123 First</OutputOne>
                    </FirstGroup>
                    <FirstGroup>
                        <PartOne>ABC</PartOne>
                        <PartTwo>456</PartTwo>
                        <OutputOne>Get this value ABC-456 First</OutputOne>
                    </FirstGroup>
                    <FirstGroup>
                        <PartOne>DEF</PartOne>
                        <PartTwo>123</PartTwo>
                        <OutputOne>Get this value DEF-123 First</OutputOne>
                    </FirstGroup>
                </FirstGroups>
                <SecondGroups>
                    <SecondGroup>
                        <Title>ABC-456</Title>
                        <OutputTwo>Get this value ABC-456 Second</OutputTwo>
                    </SecondGroup>
                    <SecondGroup>
                        <Title>ABC-123</Title>
                        <OutputTwo>Get this value ABC-123 Second</OutputTwo>
                    </SecondGroup>
                    <SecondGroup>
                        <Title>DEF-123</Title>
                        <OutputTwo>Get this value DEF-123 Second</OutputTwo>
                    </SecondGroup>
                </SecondGroups>
            </MainStuff>
          '
          )

I want to be able to join FirstGroup to SecondGroup, using a combination of nodes PartOne and PartTwo from FirstGroup to link to Title from SecondGroup. The output would therefore look like this:

ReferenceTitleOutputOneOutputTwo
Reference1ABC-123Get this value ABC-123 FirstGet this value ABC-123 Second
Reference1ABC-456Get this value ABC-456 FirstGet this value ABC-456 Second
Reference1DEF-123Get this value ABC-123 FirstGet this value ABC-123 Second

I can get the first three columns easily enough:

Select      Reference
          , FG.value('(PartOne)[1]', 'VarChar(100)' ) + '-' + FG.value('(PartTwo)[1]', 'VarChar(100)' )     As  Title
          , FG.value('(OutputOne)[1]', 'VarChar(100)' )                                                     As  OutputOne
From        @MyTable                MT
Outer Apply XmlData.nodes('//*/FirstGroup')     As  FirstGroup(FG)

But I'd appreciate some guidance on how I might be able to join the nodes to get the final column.

sirbozc5

sirbozc51#

Add another outer apply for second part and correlate elements

Select      Reference
          , FG.value('(PartOne)[1]', 'VarChar(100)' ) + '-' + FG.value('(PartTwo)[1]', 'VarChar(100)' )     As  Title
          , FG.value('(OutputOne)[1]', 'VarChar(100)' )                                                     As  OutputOne
          , SG.value('(OutputTwo)[1]', 'VarChar(100)' )                                                     As  OutputOne
From        @MyTable                MT
Outer Apply XmlData.nodes('//*/FirstGroup')     As  FirstGroup(FG)
Outer Apply XmlData.nodes('//*/SecondGroup')     As  SecondGroup(SG)
WHERE SG.value('(Title)[1]', 'VarChar(100)' ) = FG.value('(PartOne)[1]', 'VarChar(100)' ) + '-' + FG.value('(PartTwo)[1]', 'VarChar(100)' )

Result

Reference   Title    OutputOne                     OutputOne                    
----------  -------  ----------------------------  -----------------------------
Reference1  ABC-123  Get this value ABC-123 First  Get this value ABC-123 Second
Reference1  ABC-456  Get this value ABC-456 First  Get this value ABC-456 Second
Reference1  DEF-123  Get this value DEF-123 First  Get this value DEF-123 Second
h5qlskok

h5qlskok2#

You can do an intermediate calculation of the concatted value inside a CROSS APPLY (VALUES virtual table, then use that to search the XML again, using the sql:column function.

SELECT
  MT.Reference,
  v1.Title,
  FirstGroup.FG.value('(OutputOne/text())[1]', 'VarChar(100)' ) AS OutputOne,
  SecondGroup.SG.value('(OutputTwo/text())[1]', 'VarChar(100)' ) AS OutputTwo
FROM @MyTable MT
OUTER APPLY XmlData.nodes('/MainStuff/FirstGroups/FirstGroup') AS FirstGroup(FG)
CROSS APPLY (VALUES (
    FirstGroup.FG.value('(PartOne/text())[1]', 'nvarchar(100)' ) + '-' + FG.value('(PartTwo/text())[1]', 'nvarchar(100)')
)) AS v1(Title)
OUTER APPLY XmlData.nodes('/MainStuff/SecondGroups/SecondGroup[Title/text()=sql:column("v1.Title")]') AS SecondGroup(SG);

db<>fiddle

Note the use of /text() and the full /MainStuff/SecondGroups/SecondGroup path (rather than //* ), both for performance reasons.

mcdcgff0

mcdcgff03#

Here is how to do joins via XQuery directly.

An intermediate XML, in the t1(x), is being composed as follows:

<r>
  <Title>ABC-123</Title>
  <OutputOne>Get this value ABC-123 First</OutputOne>
  <OutputTwo>Get this value ABC-123 Second</OutputTwo>
</r>
<r>
  <Title>ABC-456</Title>
  <OutputOne>Get this value ABC-456 First</OutputOne>
  <OutputTwo>Get this value ABC-456 Second</OutputTwo>
</r>
<r>
  <Title>DEF-123</Title>
  <OutputOne>Get this value DEF-123 First</OutputOne>
  <OutputTwo>Get this value DEF-123 Second</OutputTwo>
</r>

SQL

DECLARE @MyTable TABLE (Reference VARCHAR(20), XmlData XML);
INSERT @MyTable (Reference, XmlData) VALUES
('Reference1',
N'<MainStuff>
    <FirstGroups>
        <FirstGroup>
            <PartOne>ABC</PartOne>
            <PartTwo>123</PartTwo>
            <OutputOne>Get this value ABC-123 First</OutputOne>
        </FirstGroup>
        <FirstGroup>
            <PartOne>ABC</PartOne>
            <PartTwo>456</PartTwo>
            <OutputOne>Get this value ABC-456 First</OutputOne>
        </FirstGroup>
        <FirstGroup>
            <PartOne>DEF</PartOne>
            <PartTwo>123</PartTwo>
            <OutputOne>Get this value DEF-123 First</OutputOne>
        </FirstGroup>
    </FirstGroups>
    <SecondGroups>
        <SecondGroup>
            <Title>ABC-456</Title>
            <OutputTwo>Get this value ABC-456 Second</OutputTwo>
        </SecondGroup>
        <SecondGroup>
            <Title>ABC-123</Title>
            <OutputTwo>Get this value ABC-123 Second</OutputTwo>
        </SecondGroup>
        <SecondGroup>
            <Title>DEF-123</Title>
            <OutputTwo>Get this value DEF-123 Second</OutputTwo>
        </SecondGroup>
    </SecondGroups>
</MainStuff>');

SELECT t.Reference
    , c.value('(Title/text())[1]', 'VARCHAR(100)') AS Title
    , c.value('(OutputOne/text())[1]', 'VARCHAR(100)') AS OutputOne
    , c.value('(OutputTwo/text())[1]', 'VARCHAR(100)') AS OutputTwo
FROM @MyTable AS t
OUTER APPLY (SELECT XmlData.query('
    for $FirstGroup in /MainStuff/FirstGroups/FirstGroup,
        $SecondGroup in /MainStuff/SecondGroups/SecondGroup[Title/text() = 
            concat(($FirstGroup/PartOne/text())[1],"-",($FirstGroup/PartTwo/text())[1])]
    return <r>
            {$SecondGroup/Title}{$FirstGroup/OutputOne}{$SecondGroup/OutputTwo}
        </r>
')) AS t1(x)
CROSS APPLY x.nodes('/r') AS t2(c);

Output

ReferenceTitleOutputOneOutputTwo
Reference1ABC-123Get this value ABC-123 FirstGet this value ABC-123 Second
Reference1ABC-456Get this value ABC-456 FirstGet this value ABC-456 Second
Reference1DEF-123Get this value DEF-123 FirstGet this value DEF-123 Second

相关问题