1

As title says.. I have XML like so:

<logs>
  <Event>
    <DriverId>51</DriverId>
    <EventID>b31ade0f-1053-4df4-a9dd-ffc76060f3c5</EventID>
    <Records>
      <Record>
        <RecordID>b31ade0f-1053-4df4-a9dd-ffc76060f3c5</RecordID>
      </Record>
    </Records>
  </Event>
  <Event>
    <DriverId>45</DriverId>
    <EventID>3b454377-74c7-4ea2-909e-3ea239b969b3</EventID>
    <Records>
      <Record>
        <RecordID>3b454377-74c7-4ea2-909e-3ea239b969b3</RecordID>
      </Record>
      <Record>
        <RecordID>3b454377-74c7-4ea2-909e-3ea239b969b4</RecordID>
      </Record>
      <Record>
        <RecordID>3b454377-74c7-4ea2-909e-3ea239b969b5</RecordID>
      </Record>
    </Records>
  </Event>
</logs>

And I have SQL like this:

SELECT
e.col.value('./DriverId[1]', 'NVarChar(25)') DriverId,
e.col.value('./EventID[1]', 'UniqueIdentifier') EventId,
-- /Records/Record data:
e.col.value('./Records[1]/Record[1]/RecordID[1]', 'UniqueIdentifier') RecordID
FROM @XML.nodes('//Event') e(col)

It returns 2 rows, but I need second event to be returned as 3 separate rows so I can see set with 4 rows with all different Record IDs

How do I read XML like this with T-SQL?

katit
  • 17,375
  • 35
  • 128
  • 256

2 Answers2

3

The correct way to query nested nodes is a cascade of .nodes() called by APPLY:

Your sample XML:

DECLARE @XML XML=
N'<logs>
  <Event>
    <DriverId>51</DriverId>
    <EventID>b31ade0f-1053-4df4-a9dd-ffc76060f3c5</EventID>
    <Records>
      <Record>
        <RecordID>b31ade0f-1053-4df4-a9dd-ffc76060f3c5</RecordID>
      </Record>
    </Records>
  </Event>
  <Event>
    <DriverId>45</DriverId>
    <EventID>3b454377-74c7-4ea2-909e-3ea239b969b3</EventID>
    <Records>
      <Record>
        <RecordID>3b454377-74c7-4ea2-909e-3ea239b969b3</RecordID>
      </Record>
      <Record>
        <RecordID>3b454377-74c7-4ea2-909e-3ea239b969b4</RecordID>
      </Record>
      <Record>
        <RecordID>3b454377-74c7-4ea2-909e-3ea239b969b5</RecordID>
      </Record>
    </Records>
  </Event>
</logs>';

--The query

SELECT A.evnt.value('(DriverId/text())[1]','int') AS Event_DriverId
      ,A.evnt.value('(EventID/text())[1]','uniqueidentifier') AS Event_EventId 
      ,B.rec.value('(RecordID/text())[1]','uniqueidentifier') AS Record_RecordId 
FROM @XML.nodes('/logs/Event') A(evnt)
OUTER APPLY A.evnt.nodes('Records/Record') B(rec);

The result

Event_DriverId  Event_EventId                           Record_RecordId
51              B31ADE0F-1053-4DF4-A9DD-FFC76060F3C5    B31ADE0F-1053-4DF4-A9DD-FFC76060F3C5
45              3B454377-74C7-4EA2-909E-3EA239B969B3    3B454377-74C7-4EA2-909E-3EA239B969B3
45              3B454377-74C7-4EA2-909E-3EA239B969B3    3B454377-74C7-4EA2-909E-3EA239B969B4
45              3B454377-74C7-4EA2-909E-3EA239B969B3    3B454377-74C7-4EA2-909E-3EA239B969B5

The idea in short:

We use .nodes() to get each <Event> as a separate row.
Now we use .nodes() again, but we do this with the XML-fragment returned by the first call and we use a relative path (no slash at the beginning).
The second .nodes() returns each <Record> within each <Event> as a separate row.

Just if interested: This answer shows, why we should never use backward-navigation (using ../ in the XPath).

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

Start from your innermost element and use the .. to go up in the hierarchy:

SELECT
e.col.value('../../../DriverId[1]', 'NVarChar(25)') DriverId,
e.col.value('../../../EventID[1]', 'UniqueIdentifier') EventId,
e.col.value('.', 'UniqueIdentifier') RecordID
FROM @XML.nodes('//RecordID') e(col)
Rubens Farias
  • 57,174
  • 8
  • 131
  • 162
  • 1
    This might work, but it will perform awfully. [This answer](https://stackoverflow.com/a/24199428/5089204) shows, why we should never use backward-navigation (using `../` in the `XPath`). The correct approach is a cascade of `.nodes()`. You might read my answer if interested. – Shnugo Jul 29 '19 at 16:42