Loop on XML in SQL Server - sql

I have to query an XML to extract the data and put it into columns. This works perfectly. However, I want to include a loop because the structure in the XML is as follows:
<BlockOrderMessage>
<FlightOrder>
<Flight>
<FlightNr>5</FlightNr>
<AircraftType>A255</AircraftType>
</Flight>
<PositionOrders>
<PositionOrder Unit="Unit 5">
<UnitName>UnitName5</UnitName>
<CardColor>Blue</CardColor>
</PositionOrder>
<PositionOrder Unit='Unit 6">
<UnitName>UnitName6</UnitName>
<CardColor>Red</CardColor>
</PositionOrder>
</PositionOrders>
</FlightOrder>
</BlockOrderMessage>
There's always only one , but there can be more ... Now, I can generate the column (when knowing the amount of ) using following code:
DECLARE #Data XML
SET #Data =
'<?xml version="1.0" encoding="UTF-8"?>
<BlockOrderMessage xmlns="http://www...."
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.... file:/C:/Users/.....xsd">
<FlightOrder>
<Flight>
<FlightNr>FlightNr0</FlightNr>
<AircraftType>AircraftType0</AircraftType>
<PositionOrders>
<PositionOrder Unit="Unit 5">
<UnitName>UnitName5</UnitName>
<CardColor>Blue</CardColor>
</PositionOrder>
<PositionOrder Unit="Unit 6">
<UnitName>UnitName6</UnitName>
<CardColor>Red</CardColor>
</PositionOrder>
</PositionOrders>
</FlightOrder>
</BlockOrderMessage>'
;WITH XMLNAMESPACES (DEFAULT 'http://www....')
SELECT #Data.value('(/BlockOrderMessage/FlightOrder/Flight/FlightNr)[1]','VARCHAR(20)') AS 'FlightNr',
#Data.value('(/BlockOrderMessage/FlightOrder/Flight/AircraftType)[1]','VARCHAR(20)') AS 'AircraftType',
#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder/#Unit)[1]','VARCHAR(30)') AS 'PosOrder1_Unit ',
#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder/UnitName)[1]','VARCHAR(30)') AS 'PosOrder1_UnitName',
#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder/CardColor)[1]','VARCHAR(30)') AS 'PosOrder1_CardColor',
#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder[2]/#Unit)[1]','VARCHAR(30)') AS 'PosOrder2_Unit',
#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder[2]/UnitName)[1]','VARCHAR(30)') AS 'PosOrder2_UnitName',
#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder[2]/CardColor)[1]','VARCHAR(30)') AS 'PosOrder2_CardColor'
But I want to insert a loop for the PositionOrder bit, I tried the following code (only the last part, since the rest stays the same):
;WITH XMLNAMESPACES (DEFAULT 'http://www....')
SELECT #Data.value('(/BlockOrderMessage/FlightOrder/Flight/FlightNr)[1]','VARCHAR(20)') AS 'FlightNr',
#Data.value('(/BlockOrderMessage/FlightOrder/Flight/AircraftType)[1]','VARCHAR(20)') AS 'AircraftType'
DECLARE #counter INT
SET #counter = 1
WHILE (#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder/#Unit)[1]') IS NOT NULL)
BEGIN
;WITH XMLNAMESPACES (DEFAULT 'http://www....')
SELECT #Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder/#Unit)[1]','VARCHAR(30)') AS 'PosOrder_#counter_Unit ',
#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder/UnitName)[1]','VARCHAR(30)') AS 'PosOrder_#counter_UnitName',
#Data.value('(/BlockOrderMessage/FlightOrder/PositionOrders/PositionOrder/CardColor)[1]','VARCHAR(30)') AS 'PosOrder_#counter_CardColor'
SET #counter = #counter +1
END
GO
Now, I have the following problems with this loop and the result of my query:
The results of the query for the PositionOrder are all NULL, this wasn't the case when I did it without loops.
I get two tables as query result, but I want them in one table. I tried to do it so I only use one SELECT statement, but I can't get the code right.
I want the name of the column to show the counter number: So if we are in the second loop, we want the name of the column to be PosOrder_2_..., now it shows PosOrder_#counter_...
Does anybody know what I am doing wrong or how I should fix this problem?
Thanks in advance!

Are you looking for something like this?
DECLARE #Flights XML = '<BlockOrderMessage>
<FlightOrder>
<Flight>
<FlightNr>5</FlightNr>
<AircraftType>A255</AircraftType>
</Flight>
<PositionOrders>
<PositionOrder Unit="Unit 5">
<UnitName>UnitName5</UnitName>
<CardColor>Blue</CardColor>
</PositionOrder>
<PositionOrder Unit="Unit 6">
<UnitName>UnitName6</UnitName>
<CardColor>Red</CardColor>
</PositionOrder>
</PositionOrders>
</FlightOrder>
</BlockOrderMessage>'
SELECT
FlightNr = FltOrder.value('(Flight/FlightNr)[1]', 'int'),
AircraftType = FltOrder.value('(Flight/AircraftType)[1]', 'varchaR(100)'),
PosOrderUnit = PosOrder.value('#Unit', 'varchar(50)'),
PosOrderUnitName = PosOrder.value('(UnitName)[1]', 'varchar(50)'),
PosOrderCardColor = PosOrder.value('(CardColor)[1]', 'varchar(50)')
FROM
#Flights.nodes('/BlockOrderMessage/FlightOrder') AS XTbl(FltOrder)
CROSS APPLY
FltOrder.nodes('PositionOrders/PositionOrder') AS XTbl2(PosOrder)
This would produce an output something like this:
Basically, it grabs the "base" data from the <BlockOrderMessage> / <FlightOrder> node and displays those in columns 1 & 2, and then it cross applies all subnodes <PositionOrders> / <PositionOrder> inside that <FlightOrder> node and extracts the remaining information from those subnodes (any number of them).

Related

How to add additional XML node on top of my SQL generated XML

I have generated XML from a SQL Server FOR XML PATH statement as shown here:
USE MySQLDB
SELECT *
FROM BillTable
FOR XML PATH ('BillAdd'), ROOT ('BillAddRq')
And this is the result:
<BillAddRq>
<BillAdd>
<TxnID>2432-1071510295</TxnID>
<TimeCreated>2003-12-16T01:44:55</TimeCreated>
<TimeModified>2015-12-15T22:38:33</TimeModified>
<EditSequence>1450190313</EditSequence>
<TxnNumber>413</TxnNumber>
<VendorRef_ListID>E0000-933272656</VendorRef_ListID>
<VendorRef_FullName>Timberloft Lumber</VendorRef_FullName>
<APAccountRef_ListID>C0000-933270541</APAccountRef_ListID>
<APAccountRef_FullName>Accounts Payable</APAccountRef_FullName>
<TxnDate>2016-12-01T00:00:00</TxnDate>
<DueDate>2017-12-31T00:00:00</DueDate>
<AmountDue>80.50000</AmountDue>
<TermsRef_ListID>50000-933272659</TermsRef_ListID>
<TermsRef_FullName>1% 10 Net 30</TermsRef_FullName>
<IsPaid>0</IsPaid>
</BillAdd>
<BillAdd>
<TxnID>243A-1071510389</TxnID>
<TimeCreated>2003-12-16T01:46:29</TimeCreated>
<TimeModified>2015-12-15T22:38:33</TimeModified>
<EditSequence>1450190313</EditSequence>
<TxnNumber>414</TxnNumber>
<VendorRef_ListID>C0000-933272656</VendorRef_ListID>
<VendorRef_FullName>Perry Windows & Doors</VendorRef_FullName>
<APAccountRef_ListID>C0000-933270541</APAccountRef_ListID>
<APAccountRef_FullName>Accounts Payable</APAccountRef_FullName>
<TxnDate>2016-12-02T00:00:00</TxnDate>
<DueDate>2018-01-01T00:00:00</DueDate>
<AmountDue>50.00000</AmountDue>
<TermsRef_ListID>10000-933272658</TermsRef_ListID>
<TermsRef_FullName>Net 30</TermsRef_FullName>
<IsPaid>0</IsPaid>
</BillAdd>
</BillAddRq>
Now, I'd like to encapsulate the above with these nodes:
<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="15.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
//above generated xml//
</QBXMLMsgsRq>
</QBXML>
How will I achieve this in a SQL Query I created above?
I am new to SQL Server and XML. I am trying to generate this XML directly from my database and vice versa to make it more efficient and faster — let my SQL directly communicate with XML.
ATTEMPT 1:
USE MySQLDB;
GO
DECLARE #myDoc XML;
SET #myDoc = '<QBXML>
<QBXMLMsgsRq onError="stopOnError">
</QBXMLMsgsRq>
</QBXML>';
SET #myDoc.modify('
insert
-- instead of inserting string here.. I would like to insert here the query I made above
into (/QBXML/QBXMLMsgsRq)[1]');
SELECT #myDoc;
ATTEMPT 2:
USE MySQLDB;
GO
DECLARE #myDoc XML;
SET #myDoc = '<QBXML>
<QBXMLMsgsRq onError="stopOnError">
</QBXMLMsgsRq>
</QBXML>';
DECLARE #qry XML;
SET #qry = (SELECT * FROM BillTable FOR XML PATH ('BillAdd'), ROOT ('BillAddRq'));
-- SELECT #qry;
SET #myDoc.modify('insert #qry
into (/QBXML/QBXMLMsgsRq)[1]');
SELECT #myDoc;
There are many ways to construct your XML result, consider the following three alternatives...
Use XML.modify() to insert the BillTable XML into an XML scalar variable (which includes the ?qbxml XML processing instruction):
declare #BillTableXml xml = (
select *
from BillTable
for xml path('BillAdd'), root('BillAddRq')
);
declare #myDoc xml = '<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="15.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
</QBXMLMsgsRq>
</QBXML>';
set #myDoc.modify('
insert sql:variable("#BillTableXml")
into (/QBXML/QBXMLMsgsRq)[1]
');
select #myDoc as Result;
Use a nested query to construct the entire XML result (which does not, however, include the ?qbxml XML processing instruction):
select
'stopOnError' as [QBXML/QBXMLMsgsRq/#onError],
(
select *
from BillTable
for xml path('BillAdd'), root('BillAddRq'), type
) as [QBXML/QBXMLMsgsRq]
for xml path('');
Or use an XQuery to construct the entire XML result (which also includes the ?qbxml XML processing instruction):
select BillTableXml.query('
<?qbxml version="15.0"?>,
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
{ /BillAddRq }
</QBXMLMsgsRq>
</QBXML>
') as Result
from (
select *
from BillTable
for xml path('BillAdd'), root('BillAddRq'), type
) Data (BillTableXml);

Syntax to read xml inside of a stored procedure

I am passing into #xCriteria a XML parameter that looks like:
<?xml version="1.0" encoding="utf-8"?>
<searchParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<includeInactive enabled="True" />
</searchParameters>
I know the xpath is: //includeInactive/#enabled
And have tried:
--determine if we include inactive users
DECLARE #isInactive AS bit
-- SELECT #isInactive = #xCriteria.value('(/includeInactive/#enabled', 'bit')
-- SET #isInactive = #xCriteria.query('/includeInactive/#enabled')
-- SELECT #isInactive.value('(/includeInactive/enabled)[1]', 'bit')
-- SELECT #isInactive = #xCriteria.query('/includeInactive/#enabled')
PRINT '#isInactive'
PRINT #isInactive
How can I fetch the boolean value?
You try like this:
DECLARE #isInactive AS BIT;
SELECT
#isInactive = CASE #param.value('(/searchParameters/includeInactive)[1]/#enabled', 'bit')
WHEN 1 THEN 0 ELSE 1
END
SELECT #isInactive;
You basically need to select the first ((....)[1]) <includeInactive> node inside the root node, and then get the #enabled attribute off that node.
And you need to clunky CASE expression, since your XML stores the value of enabled, while the variable you want to get is the exact opposite - #isInactive.

Load XML data with same value in SQL Server

Can anyone help me to load the following XML into my SQL Server database:
declare #learnxml varchar(5000);
set #learnxml = '
<alerts>
<alert id="XYQ">
<alertTime>2020-01-06T09:49:59.999-0500</alertTime>
<participants>
<item marketCode="CC">MAIN</item>
<item marketCode="CC">PASS</item>
</participants>
</alert>
</alerts>';
DECLARE #xml XML = #learnxml
PRINT #learnxml;
SELECT
xData.value('../../#id','Varchar(100)') AlertId,
xData.value('../../alertTime[1]','Varchar(50)') AlertTime,
xData.value('../item[1]', 'varchar(50)') ParticipantHouse,
xData.value('(.)/#marketCode', 'varchar(20)') HouseMarketCode
FROM
#xml.nodes('./alerts/alert/participants/item') AS x(xData)
Because of the same market code for both the items, the above query populates same value 'Main' instead of populating 2 rows with 2 different values Main and Pass.
What needs to be changed in above query to have 2 different values for the same market code?
Following query returns correct values:
SELECT xData.value('../../#id','Varchar(100)') AlertId,
xData.value('../../alertTime[1]','Varchar(50)') AlertTime,
xData.value('.', 'varchar(50)') ParticipantHouse,
xData.value('#marketCode', 'varchar(20)') HouseMarketCode
FROM #xml.nodes('./alerts/alert/participants/item') as x(xData)

Query To Read Data From XML

There's already a few similar questions on here, but they don't seem to work for me. I need to query an XML file to extract data from it, so we can use the result of the query for other purposes. I have searched on the net for a method to do this, and it seems the value () method is the recommended method. I tried this with the following code:
DECLARE #x XML
SET #x =
'<?xml version="1.0" encoding="UTF-8"?>
<BlockOrderMessage xmlns="http://www.test.com/production/block"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.test.com/production/block file:/C:/Users/test.xsd"
BlockNumber="BlockNumber1">
<BlockStartTime>01:01:01.001</BlockStartTime>
<ProductionDate>2006-05-04</ProductionDate>
<BlockType>Bloc</BlockType>
<FlightOrders>
<FlightOrder>
<Flight>
<FlightNr>FlightNr0</FlightNr>
<DepartureDate>2006-05-04</DepartureDate>
<DepartureTime>01:01:01.001</DepartureTime>
<AircraftType>AircraftType0</AircraftType>
<AircraftSeatConfiguration>AircraftSeatConfiguration0</AircraftSeatConfiguration>
</Flight>
<CATC>CATC0</CATC>
</FlightOrder>
<FlightOrder>
<Flight>
<FlightNr>FlightNr1</FlightNr>
<DepartureDate>2006-05-04</DepartureDate>
<DepartureTime>01:01:01.001</DepartureTime>
<AircraftType>AircraftType1</AircraftType>
<AircraftSeatConfiguration>AircraftSeatConfiguration1</AircraftSeatConfiguration>
</Flight>
</FlightOrder>
</FlightOrders>
'
SELECT #x.value('(/BlockOrderMessage/Blocktype)[1]','int') AS 'BlockType',
#x.value('(/BlockOrderMessage/FlightOrders/FlightOrder/Flight/FlightNr)[1]','VARCHAR (20)') AS 'FlightNr',
#x.value('(/BlockOrderMessage/FlightOrders/FlightOrder/Flight/AircraftType)[1]','VARCHAR (20)') AS 'AircraftType'
Then i receive a query result of three columns (so far so good), but the records are NULL for all three columns. It does work when I remove the namespace declarations in BlockOrderMessage, so I assume I need to declare these namespaces somewhere in my query, but I cannot find how. Does anyone how I should do this?
Thanks in advance!
       
Hy,
First of all i think it is case sensitive, so correct Blocktype to BlockType. Do not convert BlockType to int because it is string so use nvarchar(max). Use xml namespaces in your query, like this
; WITH XMLNAMESPACES (default 'http://www.test.com/production/block')
SELECT #x.value('(/BlockOrderMessage/BlockType)[1]','varchar(max)') AS 'BlockType',
#x.value('(/BlockOrderMessage/FlightOrders/FlightOrder/Flight/FlightNr)[1]','VARCHAR (20)') AS 'FlightNr',
#x.value('(/BlockOrderMessage/FlightOrders/FlightOrder/Flight/AircraftType)[1]','VARCHAR (20)') AS 'AircraftType' `
Here is the full query
DECLARE #x XML
SET #x =
'<?xml version="1.0" encoding="UTF-8"?>
<BlockOrderMessage xmlns="http://www.test.com/production/block"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.test.com/production/block file:/C:/Users/test.xsd"
BlockNumber="BlockNumber1">
<BlockStartTime>01:01:01.001</BlockStartTime>
<ProductionDate>2006-05-04</ProductionDate>
<BlockType>Bloc</BlockType>
<FlightOrders>
<FlightOrder>
<Flight>
<FlightNr>FlightNr0</FlightNr>
<DepartureDate>2006-05-04</DepartureDate>
<DepartureTime>01:01:01.001</DepartureTime>
<AircraftType>AircraftType0</AircraftType>
<AircraftSeatConfiguration>AircraftSeatConfiguration0</AircraftSeatConfiguration>
</Flight>
<CATC>CATC0</CATC>
</FlightOrder>
<FlightOrder>
<Flight>
<FlightNr>FlightNr1</FlightNr>
<DepartureDate>2006-05-04</DepartureDate>
<DepartureTime>01:01:01.001</DepartureTime>
<AircraftType>AircraftType1</AircraftType>
<AircraftSeatConfiguration>AircraftSeatConfiguration1</AircraftSeatConfiguration>
</Flight>
</FlightOrder>
</FlightOrders>
</BlockOrderMessage>'
; WITH XMLNAMESPACES (default 'http://www.test.com/production/block')
SELECT #x.value('(/BlockOrderMessage/BlockType)[1]','varchar(50)') AS 'BlockType',
#x.value('(/BlockOrderMessage/FlightOrders/FlightOrder/Flight/FlightNr)[1]','VARCHAR (20)') AS 'FlightNr',
#x.value('(/BlockOrderMessage/FlightOrders/FlightOrder/Flight/AircraftType)[1]','VARCHAR (20)') AS 'AircraftType'

SQL Server 2005 with XML Namespaces - Query Multiple Nodes and Properties

I have the following XML and code and can pull back the AccountNumber and Status. I also need to pull back...
The ID property of the MFRRequest
Several elements from the CaseInformation node
I am not sure how to get the ID property and I'm wondering... can I get all of this information (from multiple nodes) back in the same query?
Thank you!
DECLARE #doc xml
SET #doc =
'<?xml version="1.0" encoding="UTF-8"?>
<p:OrderRequest xmlns:p="http://XXX.Schemas.OrderRequest/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://XXX.Schemas.OrderRequest/2 OrderRequestV2.xsd ">
<p:Header>
<p:SCRARequestCount>0</p:SCRARequestCount>
<p:MFRRequestCount>1</p:MFRRequestCount>
<p:FileCreatedAt>2001-12-31T12:00:00</p:FileCreatedAt>
<p:RequestFileName>p:RequestFileName</p:RequestFileName>
</p:Header>
<p:RequestItems>
<p:MFRRequest id="1" priority="">
<p:AccountNumber>9999999</p:AccountNumber>
<Status>Initial</Status>
<p:CaseInformation>
<ReferralDate>2011-01-01</ReferralDate>
<LoanType>1A</LoanType>
<ARM>Yes</ARM>
<InvestorNumber>InvestorNumber</InvestorNumber>
<PropertyAddress>PropertyAddress</PropertyAddress>
<PrivateLabel>Yes</PrivateLabel>
<CaseNumber>01-11111/AK/</CaseNumber>
<SuspenseBalance>9999.00</SuspenseBalance>
<TitleOrderedDate>2011-09-01</TitleOrderedDate>
<TotalMonthlyPayment>876.99</TotalMonthlyPayment>
</p:CaseInformation>
</p:MFRRequest>
</p:RequestItems>
</p:OrderRequest>'
;WITH XMLNAMESPACES('http://XXX.Schemas.OrderRequest/2' AS p)
SELECT
Y.i.value('Status[1]', 'varchar(10)') AS Status ,
Y.i.value('p:AccountNumber[1]', 'varchar(10)') AS AccountNumber
FROM #doc.nodes('/p:OrderRequest/p:RequestItems/p:MFRRequest') AS Y(i)
Try something like this:
;WITH XMLNAMESPACES('http://XXX.Schemas.OrderRequest/2' AS p)
SELECT
MFRRequestID = Y.i.value('(#id)[1]', 'int'),
RequestStatus = Y.i.value('Status[1]', 'varchar(10)') ,
AccountNumber = Y.i.value('p:AccountNumber[1]', 'varchar(10)'),
ReferralDate = CIF.value('(ReferralDate)[1]', 'varchar(25)'),
CaseInfoType = CIF.value('(Type)[1]', 'varchar(25)')
FROM
#doc.nodes('/p:OrderRequest/p:RequestItems/p:MFRRequest') AS Y(i)
CROSS APPLY
i.nodes('p:CaseInformation') AS Tbl(CIF)
Basically, once you have the <p:MFRRequest> XML element in your Y(i) pseudo-table, you can then again use a second call to .nodes() to get the (possibly multiple) <p:CaseInfomration> nodes and use .value() calls on this new second pseudo-table to extract single bits of information from it.