CDATA with Child elements - sql-server

I would like the outcome with a subset of the MakeType .
Right now I get the entire block and not elements of the MakeType.
When the XML resolves the Type, the Series and class tag an element for those should be created. This is on SQL Server Standard 2017.And I really don't know CDATA xml style and would not use it but the vendor requires the CDATA type.
---Create Temp Table
declare #RepCar table
(
[Name] varchar(10),
[Make] varchar(10),
[Model] varchar(10),
[Price] money,
[Type] varchar(10),
[Series] varchar(10),
[Class] Varchar(10)
);
insert into #RepCar
(
Name, Make, Model, Price, Type, Series, Class
)
values
('Car1', 'Make1', 'Model1', 100, 'Type1', 'IS', 'Sedan'),
('Car1', 'Make1', 'Model1', 100, 'Type1', 'LS' , 'Sport'),
('Car2', 'Make2', 'Model2', 200, 'Type2', 'M3' , 'Sport'),
('Car3', 'Make3', 'Model3', 300, 'Type3','GS350','Sedan');
--Declare Variables
DECLARE #TransactionId NVARCHAR(100)
DECLARE #TransactionDateTime DATETIME
--Setting Variable
SET #TransactionId= (SELECT CONVERT(VARCHAR, CURRENT_TRANSACTION_ID()))
SET #TransactionDateTime= GETDATE()
--Create the XML
select 1 AS Tag,
0 AS Parent,
'CollectSamplingData' as 'Message!1!TransactionType!cdata',
#TransactionId as 'Message!1!TransactionID!cdata',
#TransactionDateTime as 'Message!1!TransactionDate!cdata',
[Name] as 'Message!1!CName!cdata',
[Make] as 'Message!1!MakeCar!cdata',
[Model] as 'Message!1!MakeModel!cdata',
[Price] as 'Message!1!DataValue!cdata',
[Type] as 'Message!1!MakeType!cdata' ,
-----This is the SQL that is'nt working.
( select
1 AS Tag,
0 AS Parent,
[Series] as 'Message!2!MakeSeries!cdata',
[Class] as 'Message!2!MakeClass!cdata'
from #RepCar
FOR XML EXPLICIT
)
from #RepCar
FOR XML EXPLICIT, ROOT('Message');
The Outcome should look like this.When the code does see a the MakeType should have the Series and class below as the child element. These are the desired output XML
<Message>
<Message>
<TransactionType><![CDATA[CollectSamplingData]]></TransactionType>
<TransactionID><![CDATA[1482282230]]></TransactionID>
<TransactionDate><![CDATA[2020-02-03T11:05:17.340]]></TransactionDate>
<CName><![CDATA[Car1]]></CName>
<MakeCar><![CDATA[Make1]]></MakeCar>
<MakeModel><![CDATA[Model1]]></MakeModel>
<DataValue><![CDATA[100.0000]]></DataValue>
<MakeType><![CDATA[Type1]]>
<Series><![CDATA[IS]></Series>
<Class><![CDATA[Sedan]]></Class>
<Series><![CDATA[LS]></Series>
<Class><![CDATA[Sport]]></Class>
<Series><![CDATA[M3]></Series>
<Class><![CDATA[Sport]]></Class>
<Series><![CDATA[GS350]></Series>
<Class><![CDATA[Sedan]]></Class>>
</MakeType>
</Message>

I struggled to produce what you need by using FOR XML EXPLICIT. Eventually, I reverted to using XQuery FLWOR expression. Please remember that SQL Server XML data type cannot hold CDATA sections. You need to use the NVARCHAR(MAX) data type. Check it out here: How to use CDATA in SQL XML
SQL
-- DDL and sample data population, start
DECLARE #RepCar TABLE
(
[Name] VARCHAR(10),
[Make] VARCHAR(10),
[Model] VARCHAR(10),
[Price] MONEY,
[Type] VARCHAR(10),
[Series] VARCHAR(10),
[Class] VARCHAR(10)
);
INSERT INTO #RepCar
(
Name,
Make,
Model,
Price,
Type,
Series,
Class
)
VALUES
('Car1', 'Make1', 'Model1', 100, 'Type1', 'IS', 'Sedan'),
('Car1', 'Make1', 'Model1', 100, 'Type1', 'LS', 'Sport'),
('Car2', 'Make2', 'Model2', 200, 'Type2', 'M3', 'Sport'),
('Car3', 'Make3', 'Model3', 300, 'Type3', 'GS350', 'Sedan');
-- DDL and sample data population, end
--Declare Variables
DECLARE #TransactionId NVARCHAR(100) = CURRENT_TRANSACTION_ID();
DECLARE #TransactionDateTime DATETIME = GETDATE();
DECLARE #lt NCHAR(4) = '<'
, #gt NCHAR(4) = '>';
SELECT REPLACE(REPLACE(TRY_CAST((SELECT 'CollectSamplingData' AS [TransactionType]
, #TransactionId AS [TransactionID]
, #TransactionDateTime AS [TransactionDate]
, *
FROM #RepCar
FOR XML PATH('r'), TYPE, ROOT('root')).query('<Messages><Message>
{
for $x in /root/r[1]
return (<TransactionType>{concat("<![CDATA[", data($x/TransactionType[1]), "]]>")}</TransactionType>,
<TransactionID>{concat("<![CDATA[", data($x/TransactionID[1]), "]]>")}</TransactionID>,
<TransactionDate>{concat("<![CDATA[", data($x/TransactionDate[1]), "]]>")}</TransactionDate>,
<CName>{concat("<![CDATA[", data($x/Name[1]), "]]>")}</CName>,
<MakeCar>{concat("<![CDATA[", data($x/Make[1]), "]]>")}</MakeCar>,
<MakeModel>{concat("<![CDATA[", data($x/Model[1]), "]]>")}</MakeModel>,
<DataValue>{concat("<![CDATA[", data($x/Price[1]), "]]>")}</DataValue>,
<MakeType>{concat("<![CDATA[", data($x/Type[1]), "]]>")}
{
for $y in /root/r
return (
<Series>{concat("<![CDATA[", data($y/Series[1]), "]]>")}</Series>,
<Class>{concat("<![CDATA[", data($y/Class[1]), "]]>")}</Class>
)
}
</MakeType>)
}
</Message></Messages>') AS NVARCHAR(MAX)), #lt,'<'), #gt, '>') AS [XML with CDATA sections];
Output
<Messages>
<Message>
<TransactionType><![CDATA[CollectSamplingData]]></TransactionType>
<TransactionID><![CDATA[1149709]]></TransactionID>
<TransactionDate><![CDATA[2020-02-03T16:23:43.020]]></TransactionDate>
<CName><![CDATA[Car1]]></CName>
<MakeCar><![CDATA[Make1]]></MakeCar>
<MakeModel><![CDATA[Model1]]></MakeModel>
<DataValue><![CDATA[100.0000]]></DataValue>
<MakeType><![CDATA[Type1]]>
<Series><![CDATA[IS]]></Series>
<Class><![CDATA[Sedan]]></Class>
<Series><![CDATA[LS]]></Series>
<Class><![CDATA[Sport]]></Class>
<Series><![CDATA[M3]]></Series>
<Class><![CDATA[Sport]]></Class>
<Series><![CDATA[GS350]]></Series>
<Class><![CDATA[Sedan]]></Class>
</MakeType>
</Message>
</Messages>

Just for comparison, I would like to show how easy to implement CDATA section when the XQuery engine fully supports standards. Below is BaseX 9.3.1 implementation which is using cdata-section-elements serialization parameter: List of elements to be output as CDATA, separated by whitespaces.
Two elements <city> and <motto> are emitted as CDATA section in a simple declarative way.
XQuery
xquery version "3.1";
declare option output:omit-xml-declaration "no";
declare option output:cdata-section-elements "city motto";
declare context item := document {
<root>
<row>
<state>FL</state>
<motto>In God We Trust</motto>
<city>Miami</city>
</row>
<row>
<state>NJ</state>
<motto>Liberty and Prosperity</motto>
<city>Trenton</city>
</row>
</root>
};
<root>
{
for $r in ./root/row
return $r
}
</root>
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<state>FL</state>
<motto><![CDATA[In God We Trust]]></motto>
<city><![CDATA[Miami]]></city>
</row>
<row>
<state>NJ</state>
<motto><![CDATA[Liberty and Prosperity]]></motto>
<city><![CDATA[Trenton]]></city>
</row>
</root>

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>

T-SQL to produce XML

We have a table with the column structure of:
MainPhoneTypeCode
MainPhoneNumber
AlternateNumberTypeCode
AlternatePhoneNumber
And require to output the follow XML:
<ns0:PhoneNumberList>
<ns0:PhoneNumber Type="0096">
<ns0:Number>(08) 8232 5550</ns0:Number>
</ns0:PhoneNumber>
<ns0:PhoneNumber Type="0026">
<ns0:Number>(08) 8336 1050</ns0:Number>
</ns0:PhoneNumber>
</ns0:PhoneNumberList>
For the main phone so far I have:
WITH xmlnamespaces ('http://www.sifassociation.org/datamodel/au/3.4' AS ns0)
SELECT si.MainPhoneTypeCode AS [#Type]
,si.MainPhoneNumber AS [ns0:Number]
FROM EDU.tbl_EDU_SchoolInfo si
WHERE si.SchoolInfoID = 4
FOR XML PATH('ns0:PhoneNumber'), TYPE, ROOT('ns0:PhoneNumberList')
Which generates:
<ns0:PhoneNumberList xmlns:ns0="http://www.sifassociation.org/datamodel/au/3.4">
<ns0:PhoneNumber Type="0096">
<ns0:Number>(08) 8232 5550</ns0:Number>
</ns0:PhoneNumber>
</ns0:PhoneNumberList>
But unsure how to add the alternative number to be included in the PhoneNumberList element.
There is a need for a namespace declaration because all XML tags have a namespace prefix in them.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE
(
SchoolInfoID INT PRIMARY KEY
, MainPhoneTypeCode CHAR(4)
, MainPhoneNumber VARCHAR(20)
, AlternateNumberTypeCode CHAR(4)
, AlternatePhoneNumber VARCHAR(20)
);
INSERT INTO #tbl (
SchoolInfoID,
MainPhoneTypeCode,
MainPhoneNumber,
AlternateNumberTypeCode,
AlternatePhoneNumber
)
VALUES
(4, '0096', '(08) 8232 5550', '0026', '(08) 8336 1050');
-- DDL and sample data population, end
;WITH XMLNAMESPACES ('http://www.sifassociation.org/datamodel/au/3.4' AS ns0)
SELECT MainPhoneTypeCode AS [ns0:PhoneNumber/#Type]
, MainPhoneNumber AS [ns0:PhoneNumber/ns0:Number]
, ''
, AlternateNumberTypeCode AS [ns0:PhoneNumber/#Type]
, AlternatePhoneNumber AS [ns0:PhoneNumber/ns0:Number]
FROM #tbl
WHERE SchoolInfoID = 4
FOR XML PATH(''), TYPE, ROOT('ns0:PhoneNumberList');
Output
<ns0:PhoneNumberList xmlns:ns0="http://www.sifassociation.org/datamodel/au/3.4">
<ns0:PhoneNumber Type="0096">
<ns0:Number>(08) 8232 5550</ns0:Number>
</ns0:PhoneNumber>
<ns0:PhoneNumber Type="0026">
<ns0:Number>(08) 8336 1050</ns0:Number>
</ns0:PhoneNumber>
</ns0:PhoneNumberList>

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()

Generate complex XML with T-SQL syntax

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

How do you pass collections of data into a T-SQL stored procedure in SQL Server 2003

I have a stored procedure that ideally should be able to accept a list/table of NVARCHARs from the database client. I'm aware of table parameters in SQL Server 2008 but I'm stuck with running SQL Server 2003.
Currently, I'm concatenating the strings with a separator character on the client side, passing the resulting string as a NVARCHAR parameter, and then teasing apart the string on entry to the stored procedure, but this leaves much to be desired.
Have you looked at passing in XML?
So, for XML a little like:
<ArrayOfService xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Service Id="2" Name="AUSTRALIA" Code="AUS" />
<Service Id="10" Name="FAR EAST" Code="FEE" />
</ArrayOfService>
In SQL Server 2005 you could do:
-- Lookup Services
DECLARE #ServiceXml AS XML
CREATE TABLE #Service
(
Id INT,
[Name] VARCHAR( 50 ),
Code VARCHAR( 10 )
)
INSERT INTO #Service
(
Id,
[Name],
Code
)
SELECT
CASE
WHEN LegsTbl.rows.value('#Id', 'nvarchar(255)') = '' THEN NULL
WHEN LegsTbl.rows.value('#Id', 'int') = 0 THEN NULL
ELSE LegsTbl.rows.value('#Id', 'int')
END AS Id,
LegsTbl.rows.value('#Name', 'varchar(50)') AS [Name],
LegsTbl.rows.value('#Code', 'varchar(50)') AS TopazCode
FROM
#ServiceXml.nodes('/ArrayOfService/Service') LegsTbl(rows)
Or SQL Server 2000:
DECLARE #ServiceXml AS NTEXT
DECLARE #iServiceXml AS INT
--Create an internal representation of the XML document.
EXEC sp_xml_preparedocument #iServiceXml OUTPUT, #ServiceXml
CREATE TABLE #Service
(
Id INT,
[Name] VARCHAR( 50 ),
Code VARCHAR( 10 )
)
INSERT INTO #Service
(
Id,
[Name],
Code
)
SELECT
Id,
Name,
Code
FROM
OPENXML( #iServiceXml, '/ArrayOfService/Service', 3)
WITH (Link8Id int '#Id',
Name varchar(50) '#Name',
Code varchar(10) '#TopazCode')
IN SQL2005 (which I assume you mean?) I build up an XML string and pass it
CREATE PROCEDURE dbo.ig_SelectRecentConfigurableAppsByMakes
(
#MakesXML XML, -- <makes><value>GMC</value>...</makes>
#TopN INT
)
AS
DECLARE #Makes TABLE (Make NVARCHAR(30))
INSERT INTO #Makes
SELECT ParamValues.make.value('.','NVARCHAR(30)')
FROM #MakesXML.nodes('makes/value') AS ParamValues(make)
;

Resources