3

I'm having trouble shredding this xml in sql server due to the different levels of nodes in the xml. How can I acheieve this with 1 query. Example of the xml below.

<Report>
<P>
    <Data>
             <Cust custID = "A" custName = "B" ></Cust>
 </Data>
</P>
<H>
    <Data1>
        <Seats>
            <Seat id = "abc" value = "123" ></Seat>
            <Date depart = "abc1" arrive = "1231" ></Date>
            <Records>
                <Record  recID = "C" col2 = "D" ></Record>
            </Records>
        </Seats>
        <Seats>
            <Seat id = "xyz" value = "756" ></Seat>
            <Date depart = "asd" arrive = "6781" ></Date>
            <Records>
                <Record  recID = "1" col2 = "6" ></Record>
                <Record  recID = "2" col2 = "7" ></Record>
            </Records>
        </Seats>
     </Data1>
    <Data2>
        <S id = "1" value = "eco" ></S>
        <S id = "2" value = "bus" ></S>
    </Data2>
    <Data3>
        <Guest id = "100" value = "aaa" recID="C"></Guest>
        <Guest id = "101" value = "bbb" recID="1"></Guest>
        <Guest id = "102" value = "ccc" recID="2"></Guest>
    </Data3>
 </H>
 </Report>
3
  • the end tag </Report> was cut off for some reason Commented Apr 6, 2018 at 8:25
  • You'll have to be more specific about how you want to shred the nodes. What's your desired result? A rowset is not hierarchical, so some sort of choice has to be made about how to map elements and attributes to columns. Commented Apr 6, 2018 at 9:32
  • @DC07 was cut off for some reason: It was not indented. XML will not be displayed unless it is marked as code (backticks or indentation) Commented Apr 6, 2018 at 10:05

1 Answer 1

4

The following code provides some templates to read values out of any location into a flat table. Add missing columns yourself. That should be easy.

DECLARE @xml XML=
N'<Report>
 <P>
    <Data>
      <Cust custID="A" custName="B" />
    </Data>
  </P>
  <H>
    <Data1>
      <Seats>
        <Seat id="abc" value="123" />
        <Date depart="abc1" arrive="1231" />
        <Records>
          <Record recID="C" col2="D" />
        </Records>
      </Seats>
      <Seats>
        <Seat id="xyz" value="756" />
        <Date depart="asd" arrive="6781" />
        <Records>
          <Record recID="1" col2="6" />
          <Record recID="2" col2="7" />
        </Records>
      </Seats>
    </Data1>
    <Data2>
      <S id="1" value="eco" />
      <S id="2" value="bus" />
    </Data2>
    <Data3>
      <Guest id="100" value="aaa" recID="C" />
      <Guest id="101" value="bbb" recID="1" />
      <Guest id="102" value="ccc" recID="2" />
    </Data3>
  </H>
</Report>';

--the query reads meta data out of the @xml directly and nested data out of derived tables via .nodes()

 select @xml.value('(/Report/P/Data/Cust/@custID)[1]','nvarchar(max)') AS CustomerID
       ,@xml.value('(/Report/P/Data/Cust/@custName)[1]','nvarchar(max)') AS CustomerName
       ,B.Seat.value('(Seat/@id)[1]','nvarchar(max)') AS SeatId
       ,B1.Record.value('@recID','nvarchar(max)') AS SeatRecordId
       ,C.S.value('@id','int') AS S_Id
       ,D.Guest.value('@id','int') AS Guest_Id
 INTO #FlatTable
 FROM @xml.nodes('/Report/H') AS A(DataNode)
 OUTER APPLY A.DataNode.nodes('Data1/Seats') AS B(Seat)
 OUTER APPLY B.Seat.nodes('Records/Record') AS B1(Record) 
 OUTER APPLY A.DataNode.nodes('Data2/S') AS C(S) 
 OUTER APPLY A.DataNode.nodes('Data3/Guest') AS D(Guest);

 SELECT * FROM #FlatTable;

Once you've got the data in this flat table, you can use any kind of SELECT...GROUP BY to get this prepared for inserts into specific detail tables.

If this does not help you out, you must provide more information about your target structure.

UPDATE

According to your comment you want to place the guest besides the Seat, linked via @recID. This can be done by reading this value into a normal result set column and then use this value within an XQuery predicate using sql:column():

 select @xml.value('(/Report/P/Data/Cust/@custID)[1]','nvarchar(max)') AS CustomerID
       ,@xml.value('(/Report/P/Data/Cust/@custName)[1]','nvarchar(max)') AS CustomerName
       ,B.Seat.value('(Seat/@id)[1]','nvarchar(max)') AS SeatId
       ,B2.RecordId AS SeatRecordId
       ,B3.Guest.value('@id','int') AS Guest_Id
       ,C.S.value('@id','int') AS S_Id
 INTO #FlatTable
 FROM @xml.nodes('/Report/H') AS A(DataNode)
 OUTER APPLY A.DataNode.nodes('Data1/Seats') AS B(Seat)
 OUTER APPLY B.Seat.nodes('Records/Record') AS B1(Record) 
 OUTER APPLY (SELECT B1.Record.value('@recID','nvarchar(max)')) AS B2(RecordId)
 OUTER APPLY A.DataNode.nodes('Data3/Guest[@recID=sql:column("B2.RecordId")]') AS B3(Guest)
 OUTER APPLY A.DataNode.nodes('Data2/S') AS C(S);

The result for this (don't know how you want to handle <S> values)

+------------+--------------+--------+--------------+----------+------+
| CustomerID | CustomerName | SeatId | SeatRecordId | Guest_Id | S_Id |
+------------+--------------+--------+--------------+----------+------+
| A          | B            | abc    | C            | 100      | 1    |
+------------+--------------+--------+--------------+----------+------+
| A          | B            | abc    | C            | 100      | 2    |
+------------+--------------+--------+--------------+----------+------+
| A          | B            | xyz    | 1            | 101      | 1    |
+------------+--------------+--------+--------------+----------+------+
| A          | B            | xyz    | 1            | 101      | 2    |
+------------+--------------+--------+--------------+----------+------+
| A          | B            | xyz    | 2            | 102      | 1    |
+------------+--------------+--------+--------------+----------+------+
| A          | B            | xyz    | 2            | 102      | 2    |
+------------+--------------+--------+--------------+----------+------+
Sign up to request clarification or add additional context in comments.

2 Comments

hi Shnugo, yes this is what I'm after more or less. the rest can be worked out with SQL I suppose. The problem is, that for example Seat id "abc" in reality only applies to Guest id "100". Join via the record id (i added this to the xml..as it was missing). In this result everything is cross applied with each other
@DC07 Okay, this will get a little more complicated, but you find a solution in my update

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.