How to query XML with namespace in T-SQL? - sql-server

I'm having difficulty querying this XML with a namespace. I can query the xml without the namespace fine.
Below is my attempt. It results in 0 records.
;WITH XMLNAMESPACES ('http://www.google.com/kml/ext/2.2' as gx)
,CTE AS
( SELECT CONVERT(XML,'<?xml version=''1.0'' encoding=''UTF-8''?>
<kml xmlns=''http://www.opengis.net/kml/2.2'' xmlns:gx=''http://www.google.com/kml/ext/2.2''>
<Document>
<Placemark>
<open>1</open>
<gx:Track>
<altitudeMode>clampToGround</altitudeMode>
<when>2017-10-26T11:42:05Z</when>
<gx:coord>Lat Long Altitude</gx:coord>
<when>2017-10-26T11:41:40Z</when>
<gx:coord>Lat Long Altitude</gx:coord>
</gx:Track>
</Placemark>
</Document>
</kml>'
) AS BulkColumnXML
)
SELECT altitudeModetext.node.value('.','NVARCHAR(255)') AS altitudeMode,
gdcoordtext.node.value('.','NVARCHAR(255)') AS gdcoord,
whentext.node.value('.','NVARCHAR(255)') AS [when]
FROM CTE
CROSS APPLY BulkColumnXML.nodes('/kml/Document/Placemark/gx:Track') as kmlDocumentPlacemarkopengxtrack(node)
CROSS APPLY kmlDocumentPlacemarkopengxtrack.node.nodes('altitudeMode/text()') as altitudeModetext(node)
CROSS APPLY kmlDocumentPlacemarkopengxtrack.node.nodes('gx:coord/text()') as gdcoordtext(node)
CROSS APPLY kmlDocumentPlacemarkopengxtrack.node.nodes('when/text()') as whentext(node)
Corrected Code by adding default namespace into with namespaces clause:
;WITH XMLNAMESPACES ('http://www.google.com/kml/ext/2.2' as gx,
DEFAULT 'http://www.opengis.net/kml/2.2')
,CTE AS
( SELECT CONVERT(XML,'<?xml version=''1.0'' encoding=''UTF-8''?>
<kml xmlns=''http://www.opengis.net/kml/2.2'' xmlns:gx=''http://www.google.com/kml/ext/2.2''>
<Document>
<Placemark>
<open>1</open>
<gx:Track>
<altitudeMode>clampToGround</altitudeMode>
<when>2017-10-26T11:42:05Z</when>
<gx:coord>Lat Long Altitude</gx:coord>
<when>2017-10-26T11:41:40Z</when>
<gx:coord>Lat Long Altitude</gx:coord>
</gx:Track>
</Placemark>
</Document>
</kml>'
) AS BulkColumnXML
)
SELECT altitudeModetext.node.value('.','NVARCHAR(255)') AS altitudeMode,
gdcoordtext.node.value('.','NVARCHAR(255)') AS gdcoord,
whentext.node.value('.','NVARCHAR(255)') AS [when]
FROM CTE
CROSS APPLY BulkColumnXML.nodes('/kml/Document/Placemark/gx:Track') as kmlDocumentPlacemarkopengxtrack(node)
CROSS APPLY kmlDocumentPlacemarkopengxtrack.node.nodes('altitudeMode/text()') as altitudeModetext(node)
CROSS APPLY kmlDocumentPlacemarkopengxtrack.node.nodes('gx:coord/text()') as gdcoordtext(node)
CROSS APPLY kmlDocumentPlacemarkopengxtrack.node.nodes('when/text()') as whentext(node)

My magic crystal ball tells me, that you might be looking for something like this:
;WITH XMLNAMESPACES ('http://www.google.com/kml/ext/2.2' as gx,
DEFAULT 'http://www.opengis.net/kml/2.2')
,CTE AS
( SELECT CONVERT(XML,'<?xml version=''1.0'' encoding=''UTF-8''?>
<kml xmlns=''http://www.opengis.net/kml/2.2'' xmlns:gx=''http://www.google.com/kml/ext/2.2''>
<Document>
<Placemark>
<open>1</open>
<gx:Track>
<altitudeMode>clampToGround</altitudeMode>
<when>2017-10-26T11:42:05Z</when>
<gx:coord>Lat Long Altitude</gx:coord>
<when>2017-10-26T11:41:40Z</when>
<gx:coord>Lat Long Altitude</gx:coord>
</gx:Track>
</Placemark>
</Document>
</kml>'
) AS BulkColumnXML
)
,intermediateCTE AS
(
SELECT CTE.BulkColumnXML.value('(/kml/Document/Placemark/open/text())[1]','NVARCHAR(255)') AS placemark_open,
CTE.BulkColumnXML.value('(/kml/Document/Placemark/gx:Track/altitudeMode/text())[1]','nvarchar(255)') AS AltitudeMode,
CTE.BulkColumnXML.query('/kml/Document/Placemark/gx:Track/*[local-name()!="altitudeMode"]') AS SubTree
FROM CTE
)
,AllWhens AS
(
SELECT intermediateCTE.placemark_open
,intermediateCTE.AltitudeMode
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS WhenIndex
,whn.value('text()[1]','datetime') AS WhenValue
FROM intermediateCTE
CROSS APPLY SubTree.nodes('/*:when') AS A(whn)
)
,AllCoords AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS CoordIndex
,crd.value('text()[1]','varchar(255)') AS CoordValue
FROM intermediateCTE
CROSS APPLY SubTree.nodes('/*:coord') AS A(crd)
)
SELECT AllWhens.*
,AllCoords.CoordValue
FROM AllWhens
INNER JOIN AllCoords ON WhenIndex=CoordIndex
The result
AltitudeMode Inx WhenValue CoordValue
----------------------------------------------------------------
1 clampToGround 2 2017-10-26 11:41:40.000 Lat Long Altitude
1 clampToGround 1 2017-10-26 11:42:05.000 Lat Long Altitude

Related

How do I query repeating XML child nodes inside parent repeating nodes?

I have the following XML stored in an nText column (not my design, older database). I need to pull the PolicyNumber and the CvgCode that is a property of Coverage child node.
<efs:Request
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:efs="http://www.slsot.org/efs"
xsi:schemaLocation="http://www.slsot.org/efs
http://efs.slsot.org/efs/xsd/SlsotEfsSchema2.xsd">
<EfsVersion>2.0</EfsVersion>
<Batch BatchType="N" AgLicNo="12345" ItemCnt="69">
<EFSPolicy>
<PolicyNumber>POL12345</PolicyNumber>
<Binder>0086592YZ</Binder>
<TransType>N</TransType>
<Insured>Dummy Co LLC</Insured>
<ZipCode>75225</ZipCode>
<ClassCd>99930</ClassCd>
<PolicyFee>35.00</PolicyFee>
<TotalTax>36.62</TotalTax>
<TotalStampFee>1.13</TotalStampFee>
<TotalGross>792.75</TotalGross>
<EffectiveDate>09/17/2018</EffectiveDate>
<ExpirationDate>09/17/2019</ExpirationDate>
<IssueDate>09/20/2018</IssueDate>
<ContUntilCancl>N</ContUntilCancl>
<FedCrUnion>N</FedCrUnion>
<AORFlag>N</AORFlag>
<CustomID>043684</CustomID>
<WindStormExclusion>N</WindStormExclusion>
<CorrectionReEntry>N</CorrectionReEntry>
<Coverages>
<Coverage CvgCode="9325">720.00</Coverage>
</Coverages>
<Securities>
<Company CoNumber="80101168">100.00</Company>
</Securities>
</EFSPolicy>
<EFSPolicy>
...
</EFSPolicy>
</Batch>
</efs:Request>
And here is the SQL code I am using to extract the PolicyNumber (so far).
with cte_table(BatchID, xmlData)
AS
(SELECT BatchID, CAST(CAST(xmlData AS VARCHAR(MAX)) AS XML) from
Batches)
select
s.BatchID
,t.c.value('PolicyNumber[1]', 'varchar(max)') as PolicyNumber
from cte_table as s
cross apply s.xmlData.nodes('/*:Request/Batch/EFSPolicy') as t(c)
where BatchID in (select batchID from Batches where CreateDate between '1/1/19' and getdate())
I have tried a second CROSS APPLY on the Coverages node, but that was giving me all the Coverage values (not CvgCode property) for every batch, so my result set was 100+ times too many rows. I assume that was due to the 2nd CROSS APPLY, is there a INNER JOIN type CROSS APPLY?
You need to declare your namespaces to retrieve the data:
DECLARE #XML xml = '<efs:Request
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:efs="http://www.slsot.org/efs"
xsi:schemaLocation="http://www.slsot.org/efs
http://efs.slsot.org/efs/xsd/SlsotEfsSchema2.xsd">
<EfsVersion>2.0</EfsVersion>
<Batch BatchType="N" AgLicNo="12345" ItemCnt="69">
<EFSPolicy>
<PolicyNumber>POL12345</PolicyNumber>
<Binder>0086592YZ</Binder>
<TransType>N</TransType>
<Insured>Dummy Co LLC</Insured>
<ZipCode>75225</ZipCode>
<ClassCd>99930</ClassCd>
<PolicyFee>35.00</PolicyFee>
<TotalTax>36.62</TotalTax>
<TotalStampFee>1.13</TotalStampFee>
<TotalGross>792.75</TotalGross>
<EffectiveDate>09/17/2018</EffectiveDate>
<ExpirationDate>09/17/2019</ExpirationDate>
<IssueDate>09/20/2018</IssueDate>
<ContUntilCancl>N</ContUntilCancl>
<FedCrUnion>N</FedCrUnion>
<AORFlag>N</AORFlag>
<CustomID>043684</CustomID>
<WindStormExclusion>N</WindStormExclusion>
<CorrectionReEntry>N</CorrectionReEntry>
<Coverages>
<Coverage CvgCode="9325">720.00</Coverage>
</Coverages>
<Securities>
<Company CoNumber="80101168">100.00</Company>
</Securities>
</EFSPolicy>
<EFSPolicy>
</EFSPolicy>
</Batch>
</efs:Request>';
WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' as xsi,
'http://www.slsot.org/efs' AS efs)
SELECT EFS.[Policy].value('(./PolicyNumber/text())[1]','varchar(25)') AS PolicyNumber,
EFS.[Policy].value('(./Coverages/Coverage/#CvgCode)[1]','int') AS CvgCode --Assumes only 1 CvgCode per policy
FROM (VALUES(#XML)) V(X)
CROSS APPLY V.X.nodes('efs:Request/Batch/EFSPolicy') EFS([Policy]);

SQL statement is nested too deeply when adding more subqueries to projection

Reproduction project available on github, using AdventureWorks2016 database. GitHub Reproduction
We have custom filtering mechanism to accomodate for our needs. What it does is - it takes input data, builds expression tree with full query and passes it to EntityFramework to execute. We have two parts of the query - getting basic entity and getting some extra data values, represented as sub queries inside final projection.
Problem:
When getting more than ~20 subqueries, SqlServer throws an error:
Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries.
Upon closer investigation it turns out queries similar to this:
var products = db.Products.Where(p => productIds.Contains(p.ProductID))
.Select(p => new
{
Entity = p,
Extras = new
{
TotalTransactions = p.TransactionHistories.Count(),
TotalCostChanges = p.ProductCostHistories.Count(),
AverageTransactionCost = p.TransactionHistories.Average(t => t.Quantity * t.ActualCost),
MaxQuantity = (int?)p.TransactionHistories.Max(t => t.Quantity)
}
});
Are resulting in SQL query generated like this:
SELECT
[Project3].[ProductID] AS [ProductID],
[Project3].[Name] AS [Name],
[Project3].[C1] AS [C1],
[Project3].[C2] AS [C2],
[Project3].[C3] AS [C3],
(SELECT
MAX([Extent5].[Quantity]) AS [A1]
FROM [Production].[TransactionHistory] AS [Extent5]
WHERE [Project3].[ProductID] = [Extent5].[ProductID]) AS [C4]
FROM ( SELECT
[Project2].[ProductID] AS [ProductID],
[Project2].[Name] AS [Name],
[Project2].[C1] AS [C1],
[Project2].[C2] AS [C2],
(SELECT
AVG([Filter4].[A1]) AS [A1]
FROM ( SELECT
CAST( [Extent4].[Quantity] AS decimal(19,0)) * [Extent4].[ActualCost] AS [A1]
FROM [Production].[TransactionHistory] AS [Extent4]
WHERE [Project2].[ProductID] = [Extent4].[ProductID]
) AS [Filter4]) AS [C3]
FROM ( SELECT
[Project1].[ProductID] AS [ProductID],
[Project1].[Name] AS [Name],
[Project1].[C1] AS [C1],
(SELECT
COUNT(1) AS [A1]
FROM [Production].[ProductCostHistory] AS [Extent3]
WHERE [Project1].[ProductID] = [Extent3].[ProductID]) AS [C2]
FROM ( SELECT
[Extent1].[ProductID] AS [ProductID],
[Extent1].[Name] AS [Name],
(SELECT
COUNT(1) AS [A1]
FROM [Production].[TransactionHistory] AS [Extent2]
WHERE [Extent1].[ProductID] = [Extent2].[ProductID]) AS [C1]
FROM [Production].[Product] AS [Extent1]
WHERE [Extent1].[ProductID] IN (707, 708, 709, 711)
) AS [Project1]
) AS [Project2]
) AS [Project3]
With each property added to Extras, one more nested query is created.
Is there any way in which EntityFramework will generate better query (see: all those nested queries for values C1, C2... can be represented as simple sub-queries in main select) or should this kind of query be created in some completely different way?

Xquery Get Position()

This is a part of our xml file.
<point distanceTotal="162" seqNo="189">
<lineSection id="395" track="1" direction="1">
<outInfos>
<comment commentTypeId="4" priority="1"oneLiner="BOT">
<layerVPK seasonValue="S0"/>
<vectors>
<vector dateFrom="2016-12-11"/>
</vectors>
<frenchText>1x3 MH</frenchText>
</comment>
<comment commentTypeId="4" priority="1" oneLiner="bot">
<layerVPK seasonValue="S0"/>
<frenchText>Réception voie occupée</frenchText>
<dutchText>Test</dutchText>
</comment>
</outInfos>
</point>
We are uploading this to a SqlServer column and with XQuery we are fetching the values.
But, I can't find a way to get the position() coded, and basically T-SQL ROW_NUMBER or dense rank can't be used as not always all data exists.
As example the dutchText only exists on the second comment and there is no field that identifies the 2 comments....
This is the SQL Code
SELECT fi.file_uid,
fi.file_date,
T1.ref.value('#id', 'varchar(100)') AS gTV_id,
T2.ref.value('#id', 'varchar(100)') AS gTrn_id,
T4.ref.value('#seqNo', 'varchar(100)') AS gTrnTPp_seqNo,
T7.ref.value('text()[1]', 'varchar(1000)') AS gTrnTPpOiCDT_Text,
T6.ref.query('/globalTrainVariant/trains/globalTrainVariant/train/timetablePoints/point/outInfos/comment[position()]') AS Test
FROM ods.filesin fi
CROSS APPLY fi.file_xml.nodes('declare namespace cern="http://...";
(/cern:trains/globalTrainVariant)') T1(ref)
CROSS APPLY T1.ref.nodes('declare namespace cern="http://...";
(train)') T2(ref)
CROSS APPLY T2.ref.nodes('declare namespace cern="http://...";
(timetablePoints)') T3(ref)
CROSS APPLY T3.ref.nodes('declare namespace cern="http://...";
(point)') T4(ref)
CROSS APPLY T4.ref.nodes('declare namespace cern="http://...";
(outInfos)') T5(ref)
CROSS APPLY T5.ref.nodes('declare namespace cern="http://...";
(comment)') T6(ref)
CROSS APPLY T6.ref.nodes('declare namespace cern="http://...";
(dutchText)') T7(ref)
WHERE fi.file_type = 'trains'
The code gives no errors, but the Test field is always blank.
Any suggestions ?
If you would look up the documentation, you would see that, as of now, you can't return the result of the position() function directly:
In SQL Server, fn:position() can only be used in the context of a
context-dependent predicate. Specifically, it can only be used inside
brackets ([ ]).
However, there is a neat trick you can employ to get it. Namely, you can compare the position of the element with a known sequence and then return the matched value from that sequence. An example below illustrates that.
declare #x xml = N'<point distanceTotal="162" seqNo="189">
<outInfos>
<comment commentTypeId="4" priority="1" oneLiner="BOT">
<layerVPK seasonValue="S0" />
<vectors>
<vector dateFrom="2016-12-11" />
</vectors>
<frenchText>1x3 MH</frenchText>
</comment>
<comment commentTypeId="4" priority="1" oneLiner="bot">
<layerVPK seasonValue="S0" />
<frenchText>Réception voie occupée</frenchText>
<dutchText>Test</dutchText>
</comment>
</outInfos>
</point>';
with cte as (
select top (1000) row_number() over(order by ac.object_id) as [RN]
from sys.all_columns ac
)
select t.c.query('.') as [OutInfos], sq.RN as [TextPosition], x.c.query('.') as [DutchComment]
from #x.nodes('/point/outInfos') t(c)
cross join cte sq
cross apply t.c.nodes('./comment[position() = sql:column("sq.RN")]/dutchText') x(c);
In it, the CTE produces an ordered set of integers (I usually keep a special table around, but you can always construct one as you go), and match condition is specified in the XQuery expression that defines the x(c) output.
I agree with Roger that the position() fuction cannot be called directly and should be inside []. However, there is a solution which doesn't require any additional tables and supports any number of rows by using recursion:
declare #Xml xml = N'<?xml version="1.0" encoding="utf-16"?>
<root>
<n>1</n>
<n>10</n>
<n>5</n>
<n>3</n>
<n>11</n>
</root>';
with cte as
(
select t.c.value(N'n[1]', N'int') n, 1 RowNum
from #Xml.nodes(N'root[1]') t(c)
where t.c.exist(N'n[1]') = 1
union all
select t.c.value(N'n[position() = sql:column("cte.RowNum") + 1][1]', N'int') n, cte.RowNum + 1
from #Xml.nodes(N'root[1]') t(c)
cross join cte
where t.c.exist(N'n[position() = sql:column("cte.RowNum") + 1]') = 1
)
select *
from cte;
It might be simpler and better performing to use Node Order Comparison Operators in order to count the preceding //comment nodes in the XML tree.
I didn't test on huge XML documents, but it's definitely less I/O intensive, and was less CPU intensive on my contrived tests.
declare #x xml = N'<point distanceTotal="162" seqNo="189">
<outInfos>
<comment commentTypeId="4" priority="1" oneLiner="BOT">
<layerVPK seasonValue="S0" />
<vectors>
<vector dateFrom="2016-12-11" />
</vectors>
<frenchText>1x3 MH</frenchText>
</comment>
<comment commentTypeId="4" priority="1" oneLiner="bot">
<layerVPK seasonValue="S0" />
<frenchText>Réception voie occupée</frenchText>
<dutchText>Test</dutchText>
</comment>
</outInfos>
</point>';
select
[OutInfos] = t.c.query('../..'),
[TextPosition] = t.c.value('let $dutchText := . return count(../../comment[. << $dutchText])', 'int'),
[DutchComment] = t.c.query('.')
from #x.nodes('/point/outInfos/comment/dutchText') t(c)

sql tvp vs xml speed in stored procedure are the same??? Is my testing or logic flawed?

I'm an accidental DBA charged with speeding up all our sql servers. I've got a highly used query with a horrible average worker time. I noticed it uses XML to pass data to a stored procedure. Query plan tells me it spends most of its time converting XML. Everything I've read says XML is about 33% slower than TVP. I rewrote the SP using TVP and compared times using the method:
SELECT #StartTime=GETDATE()
exec GetTVPData3 #tvp --or XML method
SELECT #EndTime=GETDATE()
SELECT DATEDIFF(ms,#StartTime,#EndTime) AS [Duration in millisecs]
After many runs and averaging out the times.... TVP vs XML has XML winning by 5ms. ????? (550ms vs 545ms) Is my testing or logic flawed?
Both XML and TVP are populated before I get StartTime. I've run this on 2 different SQL test servers with similar results.
The particular code is used in a cross apply. The only difference in the SPs are:
**TVP**
CROSS APPLY (SELECT id AS ProductID, sortorder AS SortOrder FROM #Insert_tvp) Items
**XML**
CROSS APPLY (
SELECT f.id.value('#id', 'int') AS ProductID, f.id.value('#sortorder', 'int') AS SortOrder
FROM #ProductIDs.nodes('list/p')
AS f(id)
) Items
Everything in my head tells me we need to switch to using TVP and get rid of XML. But I can't convince coders without better results.
EDIT: Adding the whole XML SP:
ALTER PROCEDURE [dbo].[ExtendedDataXML]
#HostedSiteID INT,
#ProductIDs XML = NULL,
#ImageType VARCHAR(20) = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT
Products.ID AS ItemID,
0 AS ItemType,
Products.SKU,
Products.Title,
HSP.Slug,
Products.Rank,
Products.Rank AS SalesRank,
Products.Status,
Products.LaunchDate,
Products.IsOnline,
Products.IsAutoOffline,
Products.IsSalableOnline,
Products.IsMarketableOnline,
Products.LeadIn, Products.LeadOut,
COALESCE(Products.CaseQuantity, 1) AS CaseQuantity,
COALESCE(Products.MinimumOrderQuantity, 1) AS MinimumOrderQuantity,
Products.QuantityOnHand,
Image.Filename, Image.Width, Image.Height, Image.Alt, Image.Title,
Pricing.Price, Pricing.SalePrice,
Products.TruckShipment,
HSP.NDescription
FROM Products
JOIN HostedSites_Products HSP ON Products.ID = HSP.ProductID
CROSS APPLY (
SELECT f.id.value('#id', 'int') AS ProductID, f.id.value('#sortorder', 'int') AS SortOrder
FROM #ProductIDs.nodes('list/p')
AS f(id)
) Items
OUTER APPLY (
SELECT TOP(1) Filename, Width, Height, Alt, Title
FROM Items_Images
JOIN Images ON Items_Images.ImageID = Images.ID
WHERE Items_Images.ItemID = Products.ID
AND Items_Images.ItemType = 0
AND Images.Type = COALESCE(#ImageType, '.4b')
) Image
OUTER APPLY (
SELECT TOP(1) Price, SalePrice, CurrentPrice
FROM ProductPrices
WHERE ProductPrices.ProductID = Products.ID
ORDER BY LoRange ASC
) Pricing
WHERE Products.ID = Items.ProductID
AND HSP.HostedSiteID = #HostedSiteID
AND HSP.Validated = 1
AND Products.IsMarketableOnline = 1
ORDER BY Items.SortOrder
END
CROSS APPLY means row wise execution! You are parsing your XML over and over...
Your ID-List is - as far as I understand - meant as a filter
Besides the fact, that this was much better done within an inlined TVF (syntax without BEGIN...END you might try this like this:
ALTER PROCEDURE [dbo].[ExtendedDataXML]
#HostedSiteID INT,
#ProductIDs XML = NULL,
#ImageType VARCHAR(20) = NULL
AS
BEGIN
SET NOCOUNT ON;
WITH IDList AS
(
SELECT f.id.value('#id', 'int') AS ProductID, f.id.value('#sortorder', 'int') AS SortOrder
FROM #ProductIDs.nodes('list/p') AS f(id)
)
SELECT
Products.ID AS ItemID,
0 AS ItemType,
Products.SKU,
Products.Title,
HSP.Slug,
Products.Rank,
Products.Rank AS SalesRank,
Products.Status,
Products.LaunchDate,
Products.IsOnline,
Products.IsAutoOffline,
Products.IsSalableOnline,
Products.IsMarketableOnline,
Products.LeadIn, Products.LeadOut,
COALESCE(Products.CaseQuantity, 1) AS CaseQuantity,
COALESCE(Products.MinimumOrderQuantity, 1) AS MinimumOrderQuantity,
Products.QuantityOnHand,
Image.Filename, Image.Width, Image.Height, Image.Alt, Image.Title,
Pricing.Price, Pricing.SalePrice,
Products.TruckShipment,
HSP.NDescription
FROM Products
JOIN HostedSites_Products HSP ON HSP.HostedSiteID = #HostedSiteID AND HSP.Validated = 1 AND Products.ID = HSP.ProductID
INNER JOIN IDList AS Items ON Items.ProductID=Products.ProductID
OUTER APPLY (
SELECT TOP(1) Filename, Width, Height, Alt, Title
FROM Items_Images
JOIN Images ON Items_Images.ImageID = Images.ID
WHERE Items_Images.ItemID = Products.ID
AND Items_Images.ItemType = 0
AND Images.Type = COALESCE(#ImageType, '.4b')
) Image
OUTER APPLY (
SELECT TOP(1) Price, SalePrice, CurrentPrice
FROM ProductPrices
WHERE ProductPrices.ProductID = Products.ID
ORDER BY LoRange ASC
) Pricing
WHERE Products.IsMarketableOnline = 1
ORDER BY Items.SortOrder
END

SQL Server 2012: Select xml with repeated and ungrouped set of elements

For the XML below:
<Document>
<ID>01</ID>
<RaitingDate>2006-05-04T18:13:51.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner1</MinimumRatingPartner>
<RaitingDate>2006-05-04T18:13:52.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner2</MinimumRatingPartner>
<RaitingDate>2006-05-04T18:13:53.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner3</MinimumRatingPartner>
</Document>
I would like to generate the following table:
RatingDate MRP
----------------------- ---------------------
2006-05-04 18:13:51.000 MinimumRatingPartner1
2006-05-04 18:13:52.000 MinimumRatingPartner2
2006-05-04 18:13:53.000 MinimumRatingPartner3
Now I am getting:
RatingDate MRP
----------------------- ---------------------
2006-05-04 18:13:51.000 MinimumRatingPartner1
2006-05-04 18:13:52.000 MinimumRatingPartner1
2006-05-04 18:13:53.000 MinimumRatingPartner1
2006-05-04 18:13:51.000 MinimumRatingPartner2
2006-05-04 18:13:52.000 MinimumRatingPartner2
2006-05-04 18:13:53.000 MinimumRatingPartner2
2006-05-04 18:13:51.000 MinimumRatingPartner3
2006-05-04 18:13:52.000 MinimumRatingPartner3
2006-05-04 18:13:53.000 MinimumRatingPartner3
Using this query:
DECLARE #XML XML =
'<Document>
<ID>01</ID>
<RaitingDate>2006-05-04T18:13:51.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner1</MinimumRatingPartner>
<RaitingDate>2006-05-04T18:13:52.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner2</MinimumRatingPartner>
<RaitingDate>2006-05-04T18:13:53.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner3</MinimumRatingPartner>
</Document>'
SELECT
RatingDate = s.value('text()[1]', 'datetime')
,MRP =r.value('text()[1]', 'nvarchar(50)')
FROM
#XML.nodes('Document') as D(V)
cross apply
D.V.nodes('./RaitingDate') as Q(S)
cross apply
D.V.nodes('./MinimumRatingPartner') as M(R)
order by MRP, RatingDate
I have tried couple other queries, but without success.
Please note: XML structure cannot be changed.
Your XML appears to depend on ordered pairs (first partner goes with first date, second partner goes with second date etc etc). Not only that, but both columns are listed within the same parent node. So you'll have to do something like this. Lucky for you xml is order sensitive.
DECLARE #XML XML =
'<Document>
<ID>01</ID>
<RaitingDate>2006-05-04T18:13:51.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner1</MinimumRatingPartner>
<RaitingDate>2006-05-04T18:13:52.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner2</MinimumRatingPartner>
<RaitingDate>2006-05-04T18:13:53.0Z</RaitingDate>
<MinimumRatingPartner>MinimumRatingPartner3</MinimumRatingPartner>
</Document>'
SELECT MinimumRatingPartner, RatingDate FROM
(SELECT
D.V.value('text()[1]', 'datetime') AS RatingDate,
ROW_NUMBER() over (order by ##rowcount) AS RowNum
FROM #XML.nodes('Document/RaitingDate') as D(V)) Dates
INNER JOIN
(SELECT
D.V.value('text()[1]', 'nvarchar(50)') AS MinimumRatingPartner,
ROW_NUMBER() over (order by ##rowcount) AS RowNum
FROM #XML.nodes('Document/MinimumRatingPartner') as D(V)) Partners
ON Dates.RowNum = Partners.RowNum
Probably not the best solution, but works for the given xml
SELECT RaitingDate,MinimumRatingPartner
FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY RaitingDate) AS Num
,RaitingDate
FROM
(
SELECT
Node.Data.value('(.)[1]','DATETIME') as RaitingDate
FROM #XML.nodes('/Document/RaitingDate') Node(Data)
) AS A
) AS DateTable
JOIN
(
SELECT ROW_NUMBER() OVER(ORDER BY MinimumRatingPartner) AS Num
,MinimumRatingPartner
FROM
(
SELECT
Node.Data.value('(.)[1]','VARCHAR(50)') as MinimumRatingPartner
FROM #XML.nodes('/Document/MinimumRatingPartner') Node(Data)
) AS B
) AS PartnerTable
ON DateTable.Num=PartnerTable.Num

Resources