Reputation: 312
I have the following XML record.
<row>
<c1>BUSINESS.HOME</c1>
<c1 m="2">PAYMENTS.HOME</c1>
<c1 m="3">DEPARTMENT.PAGE</c1>
<c1 m="4">SECTION.HOME</c1>
<c1 m="5">ABOUT.HOME</c1>
<c2>Our Business</c2>
<c2 m="1" s="2">Businesul nostru</c2>
<c2 m="2">Payment Services</c2>
<c2 m="2" s="2">Plati</c2>
<c2 m="3">Department Operations</c2>
<c2 m="3" s="2">Departamente</c2>
<c2 m="4">Section Operations</c2>
<c2 m="4" s="2">Sectiuni</c2>
<c2 m="5">ABOUT</c2>
<c2 m="5" s="2">Despre</c2>
<c6>2</c6>
<c10>GB0010001</c10>
<c11>1</c11>
</row>
C2 are lables for C1 in two languages differentiated by the S attribute so the count of S2 is always double C1.
I would like to select a query with the following output.
NAME LABEL
--------------------------------------
BUSINESS.HOME Our Business
PAYMENTS.HOME Payment Services
DEPARTMENT.PAGE Department Operations
SECTION.HOME Section Operations
ABOUT.HOME ABOUT
Every value from C1 and the corresponding first value from C2.
I tried cross apply like this:
select t.p.query('.').value('data(./c2)[1]', 'varchar(max)') c2
from table_name
cross apply XMLRECORD.nodes('(/row/c2)') t(p)
where ID = 'HOME.PAGE'
But do not know how to proceed further in order to exclude the foreign language and include the C1 tag.
Upvotes: 1
Views: 99
Reputation: 67291
You've got a working approach already. That's fine.
I just want to add an alternative, which should be better in performance.
DECLARE @XML xml = '
<row>
<c1>BUSINESS.HOME</c1>
<c1 m="2">PAYMENTS.HOME</c1>
<c1 m="3">DEPARTMENT.PAGE</c1>
<c1 m="4">SECTION.HOME</c1>
<c1 m="5">ABOUT.HOME</c1>
<c2>Our Business</c2>
<c2 m="1" s="2">Businesul nostru</c2>
<c2 m="2">Payment Services</c2>
<c2 m="2" s="2">Plati</c2>
<c2 m="3">Department Operations</c2>
<c2 m="3" s="2">Departamente</c2>
<c2 m="4">Section Operations</c2>
<c2 m="4" s="2">Sectiuni</c2>
<c2 m="5">ABOUT</c2>
<c2 m="5" s="2">Despre</c2>
<c6>2</c6>
<c10>GB0010001</c10>
<c11>1</c11>
</row>';
-- The query
WITH Tally(Nmbr) AS(SELECT TOP(@Xml.value('count(/row/c1)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values )
SELECT @Xml.value('(/row/c1/text())[sql:column("Nmbr")][1]','nvarchar(250)') AS [NAME]
,@xml.value('(/row/c2/text())[sql:column("Nmbr")*2-1][1]','nvarchar(250)') AS [LABEL]
FROM Tally;
The idea in short:
<c1>
elements (if you have a numbers table, use this instead)Nmbr
column, and introduce it into the XQuery
predicate with sql:column()
.
.value()
calls the <c1>
value by its position.This is another approach, filtering for <c2>
elements without the s
attribute. In this case we do not need the computation with position x2 -1:
WITH Tally(Nmbr) AS(SELECT TOP(@Xml.value('count(/row/c1)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values )
SELECT @Xml.value('(/row/c1/text())[sql:column("Nmbr")][1]','nvarchar(250)') AS [NAME]
,@xml.value('(/row/c2[empty(@s)]/text())[sql:column("Nmbr")][1]','nvarchar(250)') AS [LABEL]
FROM Tally
Upvotes: 0
Reputation: 95554
This is a bit "ugly", due to having to match NULL
on NULL
, however, this is how I would achieve what you're after:
DECLARE @XML xml = '
<row>
<c1>BUSINESS.HOME</c1>
<c1 m="2">PAYMENTS.HOME</c1>
<c1 m="3">DEPARTMENT.PAGE</c1>
<c1 m="4">SECTION.HOME</c1>
<c1 m="5">ABOUT.HOME</c1>
<c2>Our Business</c2>
<c2 m="1" s="2">Businesul nostru</c2>
<c2 m="2">Payment Services</c2>
<c2 m="2" s="2">Plati</c2>
<c2 m="3">Department Operations</c2>
<c2 m="3" s="2">Departamente</c2>
<c2 m="4">Section Operations</c2>
<c2 m="4" s="2">Sectiuni</c2>
<c2 m="5">ABOUT</c2>
<c2 m="5" s="2">Despre</c2>
<c6>2</c6>
<c10>GB0010001</c10>
<c11>1</c11>
</row>';
SELECT c1.value('(./text())[1]','varchar(15)') AS [NAME],
c2.value('(./text())[1]','varchar(15)') AS LABEL
FROM @XML.nodes('row/c1') c1(c1)
JOIN @XML.nodes('row/c2') c2(c2) ON (c1.value('@m','int') = c2.value('@m','int') AND c2.value('@s','int') IS NULL)
OR (c1.value('@m','int') IS NULL AND c2.value('@m','int') IS NULL);
Upvotes: 2