Generate complex XML with T-SQL syntax - sql-server

I am trying to build a query that would return more or less complex XML structure. This is expected output I would like to have:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IGeocodeService/HereRouteMatchExtension</a:Action>
<a:MessageID>urn:uuid: some_messageID</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">ServiceURL</a:To>
</s:Header>
<s:Body>
<HereRouteMatchExtension xmlns="http://tempuri.org/">
<vehicleTrace xmlns:b="http://schemas.datacontract.org/2004/07/FMCommonTypes.WCF" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<s:Latitude>2</s:Latitude>
<s:Longitude>2</s:Longitude>
<s:PositionGuid>577AF773-C7A8-4D65-82DA-37A15CC7611D</s:PositionGuid>
</vehicleTrace>
</HereRouteMatchExtension>
</s:Body>
</s:Envelope>
I am using following code:
CREATE TABLE #test
(
Latitude INT,
Longitude INT,
PositionGuid UNIQUEIDENTIFIER
)
INSERT INTO #test VALUES (1,1,NEWID())
INSERT INTO #test VALUES (2,2,NEWID())
WITH XMLNAMESPACES ('http://www.w3.org/2003/05/soap-envelope' AS s, 'http://www.w3.org/2005/08/addressing' AS a)
SELECT
( SELECT '1' AS [a:Action/#mustUnderstand],
'http://tempuri.org/IGeocodeService/HereRouteMatchExtension' AS [a:Action]
FOR XML PATH (''), TYPE
),
'urn:uuid: some_messageID' AS 'a:MessageID',
( SELECT 'http://www.w3.org/2005/08/addressing/anonymous' AS [a:Address]
FOR XML PATH ('a:ReplyTo'), TYPE
),
( SELECT '1' AS [a:To/#mustUnderstand],
'ServiceURL' AS [a:To]
FOR XML PATH (''), TYPE
),
( SELECT '1' AS [a:Action/#mustUnderstand]
FOR XML PATH (''), TYPE
),
(SELECT Latitude AS 's:Latitude',
Longitude AS 's:Longitude',
PositionGuid AS 's:PositionGuid'
FROM #test
FOR XML PATH ('s:Body'), TYPE
)
FOR XML RAW ('s:Header'), ELEMENTS, ROOT('s:Envelope')
There are 2 problems in the code i generated:
1) reference URLS are in every sub-section and I would like to have just once;
2) Body tag is inside the header and it is supposed to go straight after it...
How can I achieve that? This is the result I am getting:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope" mustUnderstand="1">http://tempuri.org/IGeocodeService/HereRouteMatchExtension</a:Action>
<a:MessageID>urn:uuid: some_messageID</a:MessageID>
<a:ReplyTo xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope" mustUnderstand="1">ServiceURL</a:To>
<a:Action xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope" mustUnderstand="1" />
<s:Body xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Latitude>1</s:Latitude>
<s:Longitude>1</s:Longitude>
<s:PositionGuid>34BD8E91-8567-4D58-A18E-61C3FBBF5C8F</s:PositionGuid>
</s:Body>
</s:Header>
</s:Envelope>

Try that
DECLARE #test TABLE
(
Latitude INT,
Longitude INT,
PositionGuid UNIQUEIDENTIFIER
)
INSERT INTO #test VALUES (1,1,NEWID());
INSERT INTO #test VALUES (2,2,NEWID());
WITH XMLNAMESPACES ('http://www.w3.org/2003/05/soap-envelope' AS s, 'http://www.w3.org/2005/08/addressing' AS a)
SELECT
1 AS [s:Envelope/s:Header/a:Action/#s:mustUnderstand],
'http://tempuri.org/IGeocodeService/HereRouteMatchExtension' AS [s:Envelope/s:Header/a:Action],
'urn:uuid: some_messageID' AS [s:Envelope/s:Header/a:MessageID],
'http://www.w3.org/2005/08/addressing/anonymous' [s:Envelope/s:Header/a:ReplyTo/a:Address],
1 as [s:Envelope/s:Header/To/#s:mustUnderstand],
'ServiceURL' as [s:Envelope/s:Header/To],
Latitude as [s:Envelope/s:Body/s:Latitude],
Longitude as [s:Envelope/s:Body/s:Longitude],
PositionGuid as [s:Envelope/s:Body/s:PositionGuid]
FROM #test
FOR XML PATH('')
This way you produce two Envelope elements one for each row of the #table.
Edit:
I could not find a way to remove extra namespaces from child elements when generated with TYPE in a subquery with FOR XML PATH.
I came up with this solution which does not fully satisfy me:
DECLARE #test TABLE
(
Latitude INT,
Longitude INT,
PositionGuid UNIQUEIDENTIFIER,
x xml
)
DECLARE #x xml =
'<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IGeocodeService/HereRouteMatchExtension</a:Action>
<a:MessageID>urn:uuid: some_messageID</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">ServiceURL</a:To>
</s:Header>
<s:Body>
<HereRouteMatchExtension xmlns="http://tempuri.org/">
<vehicleTrace xmlns:b="http://schemas.datacontract.org/2004/07/FMCommonTypes.WCF" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<s:Latitude>2</s:Latitude>
<s:Longitude>2</s:Longitude>
<s:PositionGuid>577AF773-C7A8-4D65-82DA-37A15CC7611D</s:PositionGuid>
</vehicleTrace>
</HereRouteMatchExtension>
</s:Body>
</s:Envelope>'
INSERT INTO #test VALUES (10,10,NEWID(),#x);
INSERT INTO #test VALUES (20,20,NEWID(),#x);
UPDATE #test
SET x.modify('declare namespace s="http://www.w3.org/2003/05/soap-envelope";declare default element namespace "http://tempuri.org/";
replace value of (/s:Envelope/s:Body/HereRouteMatchExtension/vehicleTrace/s:Latitude/text())[1] with sql:column("Latitude")')
UPDATE #test
SET x.modify('declare namespace s="http://www.w3.org/2003/05/soap-envelope";declare default element namespace "http://tempuri.org/";
replace value of (/s:Envelope/s:Body/HereRouteMatchExtension/vehicleTrace/s:Longitude/text())[1] with sql:column("Longitude")')
UPDATE #test
SET x.modify('declare namespace s="http://www.w3.org/2003/05/soap-envelope";declare default element namespace "http://tempuri.org/";
replace value of (/s:Envelope/s:Body/HereRouteMatchExtension/vehicleTrace/s:PositionGuid/text())[1] with sql:column("PositionGuid")')
SELECT * FROM #test
and all at once with an insert:
DECLARE #test TABLE
(
Latitude INT,
Longitude INT,
PositionGuid UNIQUEIDENTIFIER,
x xml
)
DECLARE #x xml =
'<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IGeocodeService/HereRouteMatchExtension</a:Action>
<a:MessageID>urn:uuid: some_messageID</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">ServiceURL</a:To>
</s:Header>
<s:Body>
<HereRouteMatchExtension xmlns="http://tempuri.org/">
</HereRouteMatchExtension>
</s:Body>
</s:Envelope>'
INSERT INTO #test VALUES (10,10,NEWID(),#x);
INSERT INTO #test VALUES (20,20,NEWID(),#x);
UPDATE #test
SET x.modify('declare namespace s="http://www.w3.org/2003/05/soap-envelope";declare default element namespace "http://tempuri.org/";
insert <vehicleTrace xmlns:b="http://schemas.datacontract.org/2004/07/FMCommonTypes.WCF" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><s:Latitude>{sql:column("Latitude")}</s:Latitude>
<s:Longitude>{sql:column("Longitude")}</s:Longitude>
<s:PositionGuid>{sql:column("PositionGuid")}</s:PositionGuid></vehicleTrace> into (/s:Envelope/s:Body/HereRouteMatchExtension)[1]')
SELECT * FROM #test

Related

XML output in SQL help needed

Hi I have the following code in SQL that outputs xml between 2 tables. The output is 90% correct but if possible add to the output and then also remove some of the output text.
I am not sure if SQL has the ability to code a type of a element in the output. Please see the code below with the output. Currently if possible I would like to make 2 changes to my current output. The changes are list at the end of the port
DECLARE #ID_Rechnung int = 1978,
#XMLData xml;
WITH XMLNAMESPACES ('?xml version="1.0" encoding="UTF-8"?' as ext)
SELECT
#XMLData = xmldat.xmldataCol
FROM
(
SELECT (
SELECT
-- HIER XML Daten generieren
[InvoiceHeader].[InvoiceDate] AS 'invoice-date',
([InvoiceHeader].[InvoiceNumber]) AS 'invoice-number',
cast(replace([InvoiceHeader].[GrossValue],' ','') as decimal(18,2)) AS 'gross-total',
cast(replace([InvoiceHeader].[NetValue],' ','') as decimal(18,2)) AS 'amount-due',
[InvoiceHeader].[VatRate] AS 'tax-rate',
cast(replace([InvoiceHeader].[VatValue],' ','') as decimal(18,2)) AS 'tax-amount',
[ImagePath] AS 'image-scan-url',
[InvoiceType] AS 'document-type',
[LegalEntityVATNo] AS 'account-type/id',
[LegalEntityName] AS 'account-type/name',
[SupplierCode] as 'supplier/number',
[Currency] as 'currency/code',
(
SELECT rtrim([InvoiceLines].[LineNumber]) AS [order-line-num]
, [PONumber] as [po-number],
CAST([InvoiceLines].[UnitPrice] AS decimal(18,2)) AS Price ,
[Quantity] as quantity,
[TaxAmount] as [tax-amount],
[LineTotal] as [total],
[Decsription] as description
FROM [InvoiceLines] WHERE [InvoiceLines].[DOCID] = #id_Rechnung
FOR XML PATH('Invoice-line'), ROOT('invoice-lines'), TYPE
)
FROM [InvoiceHeader]
WHERE [InvoiceHeader].[DOCID] = #ID_Rechnung
FOR XML PATH(''), TYPE, ROOT('invoice-header')
) AS xmldataCol
) AS xmldat;
SELECT #XMLData
.query('<invoice-header>
{
for $x in /invoice-header/*[local-name()!="root"]
return $x,
for $x in /invoice-header/root/r
return <invoice-lines>/<invoice-line>{$x/*}</invoice-line></invoice-lines>
}
</invoice-header>');
Output:
<invoice-header>
<invoice-date>20180509</invoice-date>
<invoice-number>1075440</invoice-number>
<gross-total>1376.67</gross-total>
<amount-due>1197.10</amount-due>
<tax-rate>15.00%</tax-rate>
<tax-amount>179.57</tax-amount>
<image-scan-url>\\INTEL-SQL01\Attachment\2018-06-20\7e0dd165-81d6-445a-95d1-8aac686d44ed\f9a1179c-2a54-480e-b97a-ce6ac7327ae0.000</image-scan-url>
<account-type>
<id>4010112052</id>
<name>CONSOLIDATEDPOWERPROJECTS</name>
</account-type>
<supplier>
<number>12345</number>
</supplier>
<currency>
<code>ZAR</code>
</currency>
<invoice-lines xmlns:ext="?xml version="1.0" encoding="UTF-8"?">
<Invoice-line>
<order-line-num>4</order-line-num>
<po-number>120934861</po-number>
<Price>50.00</Price>
<quantity>1.000000</quantity>
<tax-amount>7.500000</tax-amount>
<total>50.00</total>
<description>Test1</description>
</Invoice-line>
<Invoice-line>
<order-line-num>2</order-line-num>
<po-number>120934861</po-number>
<Price>10.00</Price>
<quantity>2.000000</quantity>
<tax-amount>4.500000</tax-amount>
<total>20.00</total>
<description>Test2</description>
</Invoice-line>
</invoice-lines>
</invoice-header>
1.How do I get rid of the following xmlns:ext="?xml version="1.0" encoding="UTF-8&in the line: "<invoice-lines xmlns:ext="?xml version="1.0" encoding="UTF-8"?">"
How would I code "<tax-amount>7.500000</tax-amount> to get the output": "<tax-amount type="decimal">7.500000</tax-amount>"
Without a minimal reproducible example it is not possible to give you a full working answer.
(1) As #JeroenMostert already pointed out, the
'?xml version="1.0" encoding="UTF-8"?'
is an XML prolog declaration. Just delete the following line:
WITH XMLNAMESPACES ('?xml version="1.0" encoding="UTF-8"?' as ext)
(2) Here is a conceptual example how to add an attribute to an XML element. What is important here is a sequential order of adding, i.e. attribute shall be first, element itself is 2nd.
SQL
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, [tax-amount] VARCHAR(20));
INSERT INTO #tbl ([tax-amount]) VALUES
(N'7.500000'),
(N'18.000000');
SELECT
'decimal' AS [tax-amount/#type]
, [tax-amount]
FROM #tbl
FOR XML PATH('r'), TYPE, ROOT('root');
Output
<root>
<r>
<tax-amount type="decimal">7.500000</tax-amount>
</r>
<r>
<tax-amount type="decimal">18.000000</tax-amount>
</r>
</root>

Manipulate XMLNamespaces

I have the following output from SQL using FOR XML clause:
<q17:DestinationSection xmlns:q17="http://ITrack.Transmission/2011/02/25/Objects">
<q17:DestinationCode>1</q17:DestinationCode>
<q17:DestinationName>Strada Rampei 9, Iasi</q17:DestinationName>
<q17:DestinationAddress1>Strada Rampei 9, Iasi</q17:DestinationAddress1>
<q17:DestinationAddress2>
xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
</q17:DestinationAddress2>
</q17:DestinationSection>
The DestinationSection is the main root for this block of data. Is there any possibility to do some workaround and to have something like below in the <q17:DestinationAddress2></q17:DestinationAddress2> tag?
<q17:DestinationAddress2 xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"</q17:DestinationAddress2>
I have tried something but I get an error that says that I need to declare the namespace but I really don't know where to "introduce" that definition.
My SQL statement
DECLARE #XMLFINAL VARCHAR(MAX)
SET #XMLFINAL=''
DECLARE #XMLFINAL2 VARCHAR(MAX)
SET #XMLFINAL2=''
DECLARE #NUMBER NVARCHAR(100)
DECLARE #NUMBER2 NVARCHAR(100)
DECLARE #XML VARCHAR(MAX)
DECLARE #XML2 VARCHAR(MAX)
DECLARE Rec CURSOR FAST_FORWARD FOR
SELECT GID FROM PurchaseDocumentsHeader
OPEN Rec
FETCH NEXT FROM Rec INTO #NUMBER2
WHILE ##FETCH_STATUS = 0
BEGIN
SET #XML2=''
;WITH XMLNAMESPACES ('http://ITrack.Transmission/2011/02/25/Objects' as q17)
SELECT #XML2= (
SELECT
DestCode AS 'q17:DestinationCode', DestDescr 'q17:DestinationName', DestAddr AS 'q17:DestinationAddress1', DestAddr2 AS 'q17:DestinationAddress2',
DestZIP AS 'q17:DestinationZIP'
FROM PurchaseDocumentsHeader WHERE GID=#NUMBER2
FOR XML RAW('q17:DestinationSection'),ELEMENTS
)
FETCH NEXT FROM Rec INTO #NUMBER2
SET #XMLFINAL2=#XMLFINAL2+#XML2
END
CLOSE Rec DEALLOCATE Rec
EDIT
Please find below my DDL. It's a view used to extract the data from an official table.
CREATE VIEW [dbo].[PurchaseDocumentsHeader]
AS
SELECT esd.GID, esd.ADRegistrationDate, esgo.Code AS DestCode, esgo.Description AS DestDescr, esgo.Address1 AS DestAddr, esgo.fCityCode AS DestCity,
esgp.TaxRegistrationNumber AS DestZIP, 'xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' AS DestAddr2, esgo.Description AS DestRomanized,
esgo.Address1 AS DestAddress1Romanized,
'xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' AS DestAddress2Romanized, esgp.TaxRegistrationNumber AS DestZIPRom,
esgo.fCityCode AS DestCityRomanized, esgo.fCountryCode AS DestCountry, cast('DestinationGLN xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>' AS XML) AS DestGLN,
'xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' AS DestCoord
FROM ESFIDocumentTrade esd
LEFT JOIN ESGOSites esgo on esd.fDeliverySiteGID=esgo.GID
LEFT JOIN ESFITradeAccount esc on esd.fTradeAccountGID=esc.GID
LEFT JOIN ESGOPerson esgp on esc.fPersonCodeGID=esgo.GID
LEFT JOIN ESFIDocumentType est on esd.fADDocumentTypeGID=est.GID
WHERE esd.fTransitionStepCode='APPROVED' AND est.Code='CVR'
AND YEAR(esd.ADRegistrationDate)=YEAR(GETDATE())
AND MONTH(esd.ADRegistrationDate)=MONTH(GETDATE())
AND DAY(esd.ADRegistrationDate)=DAY(GETDATE())
GO
Later edit
CREATE TABLE DocPurcharseHeader
(
ADRegistrationDate date,
Code NVARCHAR(4000),
Description NVARCHAR(4000),
Address1 NVARCHAR (4000),
Address2 NVARCHAR (4000),
City NVARCHAR (4000),
ZIPCode NVARCHAR(4000)
)
INSERT INTO DocPurcharseHeader (ADRegistrationDate, Code, Description, Address1, Address2, City, ZIPCode)
VALUES('2017-10-16', '01', 'MyPOS', 'MyPOSAddress1', 'MyPOSAddress2', 'BUCHAREST', '123456')
2nd Later edit
;WITH XMLNAMESPACES ('http://ITrack.Transmission/2011/02/25/Objects' as q17)
SELECT #XMLSalesOrders=(
SELECT DestCode AS [q17:DestinationCode]
,DestDescr AS [q17:DestinationName]
,DestAddr AS [q17:DestinationAddress1]
,DestAddr2 AS [q17:DestinationAddress2]
FROM PurchaseDocumentsHeader
FOR XML PATH('q17:DestinationSection'),ELEMENTS XSINIL,ROOT('q17:DestinationSections'))
This code above is generating the below output, without XSINL directive:
<q17:DestinationSections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:q17="http://ITrack.Transmission/2011/02/25/Objects">
<q17:DestinationSection>
<q17:DestinationCode>1</q17:DestinationCode>
<q17:DestinationName>Strada Rampei 9, Iasi</q17:DestinationName>
<q17:DestinationAddress1>Strada Rampei 9, Iasi</q17:DestinationAddress1>
<q17:DestinationAddress2/>
</q17:DestinationSection>
<q17:DestinationSection>
you are trying to out-trick the very mighty XML engine.
As stated in your other question:
Never do this in a CURSOR! They are bad and evil, coming directly of the hell of procedural thinking and were invented by the devil of spaghetti code...
Try it like this:
I use your table DDL an insert some data. The second row will have a NULL in address2. Normally XML will simply omit NULL values. A not existing node is read as a NULL value. But you can force NULLs to be introduced as xsi.nil="true" with ELEMENTS XSINIL:
CREATE TABLE DocPurcharseHeader
(
ADRegistrationDate date,
Code NVARCHAR(4000),
Description NVARCHAR(4000),
Address1 NVARCHAR (4000),
Address2 NVARCHAR (4000),
City NVARCHAR (4000),
ZIPCode NVARCHAR(4000)
);
INSERT INTO DocPurcharseHeader (ADRegistrationDate, Code, Description, Address1, Address2, City, ZIPCode)
VALUES('2017-10-16', '01', 'MyPOS', 'MyPOSAddress1', 'MyPOSAddress2', 'BUCHAREST', '123456')
,('2017-10-16', '01', 'MyPOS', 'MyPOSAddress1', NULL, 'BUCHAREST', '123456');
WITH XMLNAMESPACES('http://ITrack.Transmission/2011/02/25/Objects' AS q17)
SELECT Code AS [q17:DestinationCode]
,Description AS [q17:DestinationName]
,Address1 AS [q17:DestinationAddress1]
,Address2 AS [q17:DestinationAddress2]
FROM DocPurcharseHeader
FOR XML PATH('q17:DestinationSection'),ELEMENTS XSINIL,ROOT('q17:DestinationSections');
The result
<q17:DestinationSections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:q17="http://ITrack.Transmission/2011/02/25/Objects">
<q17:DestinationSection>
<q17:DestinationCode>01</q17:DestinationCode>
<q17:DestinationName>MyPOS</q17:DestinationName>
<q17:DestinationAddress1>MyPOSAddress1</q17:DestinationAddress1>
<q17:DestinationAddress2>MyPOSAddress2</q17:DestinationAddress2>
</q17:DestinationSection>
<q17:DestinationSection>
<q17:DestinationCode>01</q17:DestinationCode>
<q17:DestinationName>MyPOS</q17:DestinationName>
<q17:DestinationAddress1>MyPOSAddress1</q17:DestinationAddress1>
<q17:DestinationAddress2 xsi:nil="true" />
</q17:DestinationSection>
</q17:DestinationSections>
UPDATE: About NULL values:
Try this
DECLARE #DummyTable TABLE(SomeDescription VARCHAR(500), SomeValue VARCHAR(100));
INSERT INTO #DummyTable VALUES('A real NULL value',NULL)
,('An empty string','')
,('A blank string',' ')
,('Some Text','blah blah');
WITH XMLNAMESPACES('SomeURL' AS q17)
SELECT SomeDescription AS [q17:Description]
,SomeValue AS [q17:Value]
FROM #DummyTable
FOR XML PATH('q17:row'),ELEMENTS XSINIL,ROOT('root');
To get this
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:q17="SomeURL">
<q17:row>
<q17:Description>A real NULL value</q17:Description>
<q17:Value xsi:nil="true" />
</q17:row>
<q17:row>
<q17:Description>An empty string</q17:Description>
<q17:Value></q17:Value>
</q17:row>
<q17:row>
<q17:Description>A blank string</q17:Description>
<q17:Value> </q17:Value>
</q17:row>
<q17:row>
<q17:Description>Some Text</q17:Description>
<q17:Value>blah blah</q17:Value>
</q17:row>
</root>
You can see, that the real NULL is encoded as xsi:nil="true", while the empty string is shown as <q17:Value></q17:Value> (which is exactly the same as <q17:Value />).
Check this answer for some examples about NULL and empty. Check this answer to understand more about text()

SQL Server insert from a XML file

I'm trying to insert info from a XML file to a temporal table in SQL Server, but I can't get it.
First I declare a table variable, then I make an insert into this table, and the values come from an XML file, at the end I select data from the table variable that should have the info inserted before, but the select just returns an empty result without errors.
Any ideas?
This is the XML
<?xml version="1.0" encoding="UTF-8"?>
<cfdi:Comprobante Moneda="MXN" NumCtaPago="3746" LugarExpedicion="something" metodoDePago="03" tipoDeComprobante="ingreso" total="434.30" descuento="0.00" subTotal="402.14"
noCertificado="00001000000403736552" formaDePago="pago en una sola exhibición" sello="something" fecha="something" folio="something" serie="something" version="3.2" xsi:schemaLocation="http://something http://something" xmlns:xsi="http://something" xmlns:cfdi="http://something">
<cfdi:Addenda xsi:schemaLocation="https://something" xmlns:xsi="http://something" xmlns="https://something">
<ADDENDABENAVIDES>
<HEADERFACTURA INTNOTAENTRADA="something" STRREMISIONID="something" STRCLAVEFACTREM= "something" FLTIEPSFACTURA="something" FLTIVADESCUENTO="something" FLTDESCUENTOFACTURA="something" FLTBRUTOFACTURA="something" FLTIVAFACTURA="something" FLTNETOFACTURA="something" STRALMACENID="something" STRCENTROLOGISTICOID="something" DTMFECHAFACTURA="something" INTNOREGISTRO="something" STRFOLIO="something" STRSERIE="something" INTBODEGAID="something" INTMAYORISTAID="something" STRNUMEROPROVEEDOR="something"/>
<DETALLEFACTURA>
<DETALLEPRODUCTO />
</DETALLEFACTURA>
</ADDENDABENAVIDES>
</cfdi:Addenda>
</cfdi:Comprobante>
And this is from SQL
DECLARE #HEADERFACTURA TABLE
(
Id int IDENTITY(1,1),
[INTNOTAENTRADA] int,
[STRREMISIONID] NVARCHAR(max),
[STRCLAVEFACTREM] NVARCHAR(max),
[FLTIEPSFACTURA] decimal(10,2),
[FLTIVADESCUENTO] decimal(10,2),
[FLTDESCUENTOFACTURA] decimal(10,2),
[FLTBRUTOFACTURA] decimal(10,2),
[FLTIVAFACTURA] decimal(10,2),
[FLTNETOFACTURA] decimal(10,2),
[STRALMACENID] int,
[STRCENTROLOGISTICOID] NVARCHAR(max),
[DTMFECHAFACTURA] NVARCHAR(max),
[INTNOREGISTRO] int,
[STRFOLIO] int,
[STRSERIE] NVARCHAR(max),
[INTBODEGAID] int,
[INTMAYORISTAID] int,
[STRNUMEROPROVEEDOR] NVARCHAR(max)
)
;with xmlnamespaces('http://something' as cfdi)
INSERT INTO #HEADERFACTURA ([INTNOTAENTRADA], [STRREMISIONID],
[STRCLAVEFACTREM], [FLTIEPSFACTURA],
[FLTIVADESCUENTO], [FLTDESCUENTOFACTURA],
[FLTBRUTOFACTURA], [FLTIVAFACTURA],
[FLTNETOFACTURA], [STRALMACENID],
[STRCENTROLOGISTICOID], [DTMFECHAFACTURA],
[INTNOREGISTRO], [STRFOLIO],
[STRSERIE], [INTBODEGAID],
[INTMAYORISTAID], [STRNUMEROPROVEEDOR])
SELECT
X.Solicitud.query('INTNOTAENTRADA').value('.', 'int'),
X.Solicitud.query('STRREMISIONID').value('.', 'nvarchar(50)'),
X.Solicitud.query('STRCLAVEFACTREM').value('.', 'nvarchar(50)'),
X.Solicitud.query('FLTIEPSFACTURA').value('.', 'decimal(10,2)'),
X.Solicitud.query('FLTIVADESCUENTO').value('.', 'decimal(10,2)'),
X.Solicitud.query('FLTDESCUENTOFACTURA').value('.', 'decimal(10,2)'),
X.Solicitud.query('FLTBRUTOFACTURA').value('.', 'decimal(10,2)'),
X.Solicitud.query('FLTIVAFACTURA').value('.', 'decimal(10,2)'),
X.Solicitud.query('FLTNETOFACTURA').value('.', 'decimal(10,2)'),
X.Solicitud.query('STRALMACENID').value('.', 'int'),
X.Solicitud.query('STRCENTROLOGISTICOID').value('.', 'nvarchar(50)'),
X.Solicitud.query('DTMFECHAFACTURA').value('.', 'nvarchar(50)'),
X.Solicitud.query('INTNOREGISTRO').value('.', 'int'),
X.Solicitud.query('STRFOLIO').value('.', 'int'),
X.Solicitud.query('STRSERIE').value('.', 'nvarchar(50)'),
X.Solicitud.query('INTBODEGAID').value('.', 'int'),
X.Solicitud.query('INTMAYORISTAID').value('.', 'int'),
X.Solicitud.query('STRNUMEROPROVEEDOR').value('.', 'nvarchar(50)')
FROM
(SELECT
CAST (X AS XML)
FROM
OPENROWSET (BULK 'C:\aa.xml', SINGLE_BLOB) AS T(X)
) AS T(X)
CROSS APPLY
x.nodes('/cfdi:Comprobante/cfdi:Addenda/ADDENDABENAVIDES/HEADERFACTURA') AS X(Solicitud);
SELECT *
FROM #HEADERFACTURA
Thanks in advance.
You have a specific namespace and default namespace in your input xml. Fix the following line and you will get results:
CROSS APPLY
x.nodes('//cfdi:Comprobante/cfdi:Addenda/*:ADDENDABENAVIDES/*:HEADERFACTURA') AS X(Solicitud);
Take note that your query will still fail from your example because all of the attributes you are querying are strings and your query is casting them into types.
Also note, you can simplify each of your attribute statements as per this example:
X.Solicitud.query('STRNUMEROPROVEEDOR').value('.', 'nvarchar(50)') becomes
X.Solicitud.value('#STRNUMEROPROVEEDOR', 'nvarchar(50)')
Lastly, notice your xml is <?xml version="1.0" encoding="UTF-8"?>; I believe it should be <?xml version="1.0" encoding="UTF-16"?> since you are using accented characters. Your XML file may fail to parse.
In your attempt to provide an XML with cleaned data you went much to far...
Values, which should be numeric (e.g. int) show up as "Something", actually everything shows up as "something"...
Another problem is, that your namespaces are all set to the same URL.
Look at this (highly condensed) sample of your XML (the default namespace is in the second level!):
<?xml version="1.0" encoding="UTF-8"?>
<cfdi:Comprobante xmlns:cfdi="http://something" Moneda="MXN">
<cfdi:Addenda xmlns="https://Default">
<ADDENDABENAVIDES>
<HEADERFACTURA INTNOTAENTRADA="123" STRREMISIONID="something" />
<DETALLEFACTURA>
<DETALLEPRODUCTO />
</DETALLEFACTURA>
</ADDENDABENAVIDES>
</cfdi:Addenda>
</cfdi:Comprobante>
One more problem is, that your XML-file starts with a declaration with encoding="utf-8", but your content includes special characters. This declaration will be ommitted by SQL Server in any case, but you cannot read UTF-8 via NVARCHAR into XML. Therefore I read this into NVARCHAR(MAX), call REPLACE to introduce utf-16 and cast this to XML.
The next point is, that you want to read attributes, but you try to find them as elements.
You would query this like here:
;WITH XMLNAMESPACES(DEFAULT 'https://Default'
,'http://something' AS cfdi)
SELECT
--An attribute from <cfdi:Comprobante>
T.X.value('(/cfdi:Comprobante/#Moneda)[1]','nvarchar(max)') AS Moneda,
--Many attributes in <HEADERFACTURA>
X.Solicitud.value('#INTNOTAENTRADA', 'int') AS INTNOTAENTRADA,
X.Solicitud.value('#STRREMISIONID', 'nvarchar(50)') AS STRREMISIONID
FROM
(SELECT
CAST(REPLACE(CAST (X AS NVARCHAR(MAX)),'utf-8','utf-16') AS XML)
FROM
OPENROWSET (BULK 'C:\aa.xml', SINGLE_CLOB) AS T(X)
) AS T(X)
CROSS APPLY
x.nodes('/cfdi:Comprobante/cfdi:Addenda/ADDENDABENAVIDES/HEADERFACTURA') AS X(Solicitud);

Retrieving an XML node value with TSQL?

What am I not getting here? I can't get any return except NULL...
DECLARE #xml xml
SELECT #xml = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<webregdataResponse>
<result>0</result>
<regData />
<errorFlag>99</errorFlag>
<errorResult>Not Processed</errorResult>
</webregdataResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>'
DECLARE #nodeVal int
SELECT #nodeVal = #xml.value('(errorFlag)[1]', 'int')
SELECT #nodeVal
Here is the solution:
DECLARE #xml xml
SELECT #xml = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<webregdataResponse>
<result>0</result>
<regData />
<errorFlag>99</errorFlag>
<errorResult>Not Processed</errorResult>
</webregdataResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>'
declare #table table (data xml);
insert into #table values (#xml);
WITH xmlnamespaces (
'http://schemas.xmlsoap.org/soap/envelope/' as [soap])
SELECT Data.value('(/soap:Envelope/soap:Body/webregdataResponse/errorFlag)[1]','int') AS ErrorFlag
FROM #Table ;
Running the above SQL will return 99.
Snapshot of the result is given below,
That's because errorFlag is not the root element of your XML document. You can either specify full path from root element to errorFlag, for example* :
SELECT #nodeVal = #xml.value('(/*/*/*/errorFlag)[1]', 'int')
or you can use descendant-or-self axis (//) to get element by name regardless of it's location in the XML document, for example :
SELECT #nodeVal = #xml.value('(//errorFlag)[1]', 'int')
*: I'm using * instead of actual element name just to simplify the expression. You can also use actual element names along with the namespaces, like demonstrated in the other answer.

How to extract results from SOAP response using XQuery in SQL Server?

I have a SOAP response with below structure and need to get the all the values for the below tags 1. result , documentNumber , costElementCode .
This is my XML sample :
DECLARE #myXML XML = '<commitmentsResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<result xmlns="http://response.cim.its.test.edu.au/">SUCCESS</result>
<value>
<documentNumber xmlns="http://finance.cim.its.test.edu.au/">123456789</documentNumber>
<commitmentLine>
<lineNumber>2</lineNumber>
<costElementCode>costElementCode</costElementCode>
<internalOrderNumber>1000002</internalOrderNumber>
<costCentreCode>9999</costCentreCode>
<wbsCode>3000</wbsCode>
<lineDescription>2 packets of pencils</lineDescription>
<accountNumber>100000</accountNumber>
<itemAmount>105.5</itemAmount>
<fundsDueDate>2015-06-15</fundsDueDate>
</commitmentLine>
<commitmentLine xmlns="http://finance.cim.its.test.edu.au/">
<lineNumber>2</lineNumber>
<costElementCode>costElementCode</costElementCode>
<internalOrderNumber>1000002</internalOrderNumber>
<costCentreCode>9999</costCentreCode>
<wbsCode>3000</wbsCode>
<lineDescription>2 packets of pencils</lineDescription>
<accountNumber>100000</accountNumber>
<itemAmount>105.5</itemAmount>
<fundsDueDate>2015-06-15</fundsDueDate>
</commitmentLine>
</value>
<value>
<documentNumber xmlns="http://finance.cim.its.test.edu.au/">12345</documentNumber>
<commitmentLine>
<lineNumber>2</lineNumber>
<costElementCode>costElementCode</costElementCode>
<internalOrderNumber>1000002</internalOrderNumber>
<costCentreCode>9999</costCentreCode>
<wbsCode>3000</wbsCode>
<lineDescription>2 packets of pencils</lineDescription>
<accountNumber>100000</accountNumber>
<itemAmount>105.5</itemAmount>
<fundsDueDate>2015-06-15</fundsDueDate>
</commitmentLine>
<commitmentLine xmlns="http://finance.cim.its.test.edu.au/">
<lineNumber>2</lineNumber>
<costElementCode>costElementCode</costElementCode>
<internalOrderNumber>1000002</internalOrderNumber>
<costCentreCode>9999</costCentreCode>
<wbsCode>3000</wbsCode>
<lineDescription>2 packets of pencils</lineDescription>
<accountNumber>100000</accountNumber>
<itemAmount>105.5</itemAmount>
<fundsDueDate>2015-06-15</fundsDueDate>
</commitmentLine>
</value>
</commitmentsResponse>'
I have tried using the below but only get the first value and not all nodes :
DECLARE #DocumentNumber INT
;WITH XMLNAMESPACES (N'http://finance.cim.its.test.edu.au/' as DYN)
SELECT #DocumentNumber = c.value('(DYN:documentNumber)[1]', 'INT')
FROM #myXML.nodes('/commitmentsResponse/value') t(c)
DECLARE #Result VARCHAR(256)
;WITH XMLNAMESPACES (N'http://response.cim.its.test.edu.au/' as DYN)
SELECT #Result = c.value('(DYN:result)[1]', 'VARCHAR(256)')
FROM #myXML.nodes('/commitmentsResponse') t(c)
DECLARE #CostElementCode VARCHAR(256)
SELECT #CostElementCode = c.value('(costElementCode)[1]', 'VARCHAR(256)')
FROM #myXML.nodes('/commitmentsResponse/value/commitmentLine') t(c)
SELECT #Result
SELECT #DocumentNumber
SELECT #CostElementCode
You can use OUTER APPLY to shred XML data type into multiple rows. This is one example for extracting multiple costElementCodes value (implementing for the other elements should be trivial) :
SELECT x.value('.[1]', 'VARCHAR(256)') as costElementCode
FROM #myXML.nodes('/commitmentsResponse/value/commitmentLine') t(c)
OUTER APPLY t.c.nodes('costElementCode') cec(x)

Resources