SQL: Cast VARCHAR to XML removes CDATA section [duplicate] - sql-server

When I generate Xml in Sql Server 2008 R2 using For Explicit (because my consumer wants one of the elements wrapped in CDATA) and store the results in an Xml variable, the data I want wrapped in CDATA tags no longer appears wrapped in CDATA tags. If I don't push the For Xml Explicit results into an Xml variable then the CDATA tags are retained. I am using the #Xml variable as an SqlParameter from .Net.
In this example, the first select (Select #Xml) does not have Line2 wrapped in CDATA tags. But the second select (the same query used to populate the #Xml variable) does have the CDATA tags wrapping the Line2 column.
Declare #Xml Xml
Begin Try
Drop Table #MyTempTable
End Try
Begin Catch
End Catch
Select
'Record' As Record
, 'Line1' As Line1
, 'Line2' As Line2
Into
#MyTempTable
Select #Xml =
(
Select
x.Tag
, x.Parent
, x.[Root!1]
, x.[Record!2!Line1!Element]
, x.[Record!2!Line2!cdata]
From
(
Select
1 As Tag, Null As Parent
, Null As [Root!1]
, Null As [Record!2!Line1!Element]
, Null As [Record!2!Line2!cdata]
From
#MyTempTable
Union
Select
2 As Tag, 1 As Parent
, Null As [Root!1]
, Line1 As [Record!2!Line1!Element]
, Line2 As [Record!2!Line2!cdata]
From
#MyTempTable
) x
For
Xml Explicit
)
Select #Xml
Select
x.Tag
, x.Parent
, x.[Root!1]
, x.[Record!2!Line1!Element]
, x.[Record!2!Line2!cdata]
From
(
Select
1 As Tag, Null As Parent
, Null As [Root!1]
, Null As [Record!2!Line1!Element]
, Null As [Record!2!Line2!cdata]
From
#MyTempTable
Union
Select
2 As Tag, 1 As Parent
, Null As [Root!1]
, Line1 As [Record!2!Line1!Element]
, Line2 As [Record!2!Line2!cdata]
From
#MyTempTable
) x
For
Xml Explicit
Begin Try
Drop Table #MyTempTable
End Try
Begin Catch
End Catch

You can't. The XML data type does not preserve CDATA sections.
Have a look here for a discussion about the subject.
http://social.msdn.microsoft.com/forums/en-US/sqlxml/thread/e22efff3-192e-468e-b173-ced52ada857f/

Related

How to loop through XML with same elements which are not enclosed by a parent element in SQL Server

I have this:
DECLARE #accountIds XML = '<AccountId>1</AccountId><AccountId>2</AccountId>'
SELECT [aid].[Col].value('/AccountId', 'BIGINT')
FROM #accountIds.nodes('/') as [aid]([Col])
The problem is that I dont know what to put in the first select item to get all the text enclosed by AccountId tags. I only know how to get 1 text
DECLARE #accountIds XML = '<AccountId>1</AccountId><AccountId>2</AccountId>'
SELECT [aid].[Col].value('/AccountId[1]', 'BIGINT')
FROM #accountIds.nodes('/') as [aid]([Col])
or
DECLARE #accountIds XML = '<AccountId>1</AccountId><AccountId>2</AccountId>'
SELECT [aid].[Col].value('/AccountId[2]', 'BIGINT')
FROM #accountIds.nodes('/') as [aid]([Col])
Here you go..
DECLARE #accountIds XML = '<AccountId>1</AccountId><AccountId>2</AccountId>'
SELECT [aid].[Col].value('(.)[1]', 'BIGINT') FROM #accountIds.nodes('/AccountId') as [aid]([Col])

MSSQL Bulk String to XML Insert Into

Currently we import a table Import_File which has an Options column which has delimited values.
We need to load those delimited values to a different table.
Currently we do that one row at a time which tends to be slower as the number of rows can be 100k+
Is there a way to speed up the code below?
Declare #InvId uniqueidentifier
Declare #xml xml
Declare CurrFeatureList Cursor For
Select
import.InventoryId,
N'<root><r><![CDATA[' + replace( import.OPTIONS ,',',']]></r><r><![CDATA[') + ']]></r></root>'
From Import_File import with (nolock)
Where
import.options IS NOT NULL
And ISNULL(import.IsFeatureProcessed,0) = 0
And LEN(ISNULL(import.OPTIONS,''))>10
And import.InventoryId Is Not Null
OPEN CurrFeatureList
FETCH NEXT FROM CurrFeatureList
INTO #InvId, #xml
Print 'Inventory Import #10000'
Print GetDate()
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
Insert Into Import_File_Feature
(
FeatureId,
InventoryId,
FeatureText,
FeatureGroup,
FeatureCategory,
FeatureIsAvailable,
FeatureIsStandard
)
Select
NEWID(),
#InvId,
t.value('.','varchar(250)'),
'',
'',
1,
1
From #xml.nodes('//root/r') as a(t)
FETCH NEXT FROM CurrFeatureList
INTO #InvId, #xml
END TRY
BEGIN CATCH
Print 'Error '
Print #InvId
Print ERROR_NUMBER()
Print ERROR_SEVERITY()
Print ERROR_STATE()
Print ERROR_PROCEDURE()
Print ERROR_LINE()
Print ERROR_MESSAGE()
FETCH NEXT FROM CurrFeatureList
INTO #InvId, #xml
END CATCH
END
Close CurrFeatureList
Deallocate CurrFeatureList
GO
The only reason I have ever seen a cursor & TRY/CATCH block used for this kind of thing is for identifying and analyzing bad records while developing a new ETL process. If that's not what you're doing then the cursor is not required and will slow you down.
Let's review what you're doing:
You're pulling data into your cursor (CurrFeatureList) and turning Import_File.Options into an XML field so you can later use the XML NODES method to "split" your string.
Kick off the cursor
For each InventoryId you're:
a. splitting the Import_File.Options into multiple rows
b. inserting that InventoryId and associated rows into Import_File_Feature
c. If there's an error you print it and move on to the next record
What you should be doing
Note how I split this string using XML nodes:
DECLARE #x varchar(100) = 'abc,cde,fff';
SELECT item = xxx.value('(text())[1]', 'varchar(100)')
FROM (VALUES (CAST(('<r>'+REPLACE(#x,',','</r><r>') +'</r>') AS xml))) x(xx)
CROSS APPLY xx.nodes('r') xxx(xxx);
Results
item
-----
abc
cde
fff
OPTION 1
Combine your initial join and subsequent XML/XML nodes splitter logic into one statement and do the insert:
WITH
yourData AS
(
Select
import.InventoryId,
x = N'<r><![CDATA[' + replace( import.OPTIONS ,',',']]></r><r><![CDATA[') + ']]></r>'
From Import_File import with (nolock)
Where
import.options IS NOT NULL
And ISNULL(import.IsFeatureProcessed,0) = 0
And LEN(ISNULL(import.OPTIONS,''))>10
And import.InventoryId Is Not Null
),
split AS
(
SELECT InventoryId, item = i.value('.', 'varchar(8000)')
FROM yourData
CROSS APPLY x.nodes('r') s(i)
)
Insert Into Import_File_Feature
(
FeatureId,
InventoryId,
FeatureText,
FeatureGroup,
FeatureCategory,
FeatureIsAvailable,
FeatureIsStandard
)
Select
newid(),
import.InventoryId,
item, -- this is the split out item from import.Options
'',
'',
1,
1
FROM split;
OPTION 2
Get a copy of DelimitedSplit8K and use it to do your splitting.
WITH split AS
(
Select
import.InventoryId,
import.OPTIONS
From Import_File import with (nolock)
CROSS APPLY dbo.DelimitedSplit8K(import.OPTIONS, ',')
Where
import.options IS NOT NULL
And ISNULL(import.IsFeatureProcessed,0) = 0
And LEN(ISNULL(import.OPTIONS,''))>10
And import.InventoryId Is Not Null
)
Insert Into Import_File_Feature
(
FeatureId,
InventoryId,
FeatureText,
FeatureGroup,
FeatureCategory,
FeatureIsAvailable,
FeatureIsStandard
)
Select
newid(),
import.InventoryId,
item,
'',
'',
1,
1
FROM split
Note that, because I don't have any table definitions or sample data there was no way for me to test the code above.

SQL SERVER FOR XML EXPLICIT

I want to assign the result of a SELECT FOR XML EXPLICIT statement to a XML Variable such as
CREATE PROCEDURE BILLING_RESPONSE
AS DECLARE #Data AS XML
SET #Data = (SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'CallTransactions!1!',
NULL AS 'TCALTRS!2!TRS_DAT_TE!cdata',
NULL AS 'TCALTRS!2!TRS_CRT_DT!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
TRS_DAT_TE,
TRS_CRT_DT
FROM TCALTRS
WHERE TRS_CRT_DT between CONVERT(date,GETDATE()-1) and CONVERT(date,getdate()) and
TRS_DAT_TE like '%(Submit Response)%'
FOR XML EXPLICIT
)
SELECT #DATA
GO
When i execute this query am getting the following error
Msg 1086, Level 15, State 1, Procedure BILLING_RESPONSE, Line 22
The FOR XML clause is invalid in views, inline functions, derived tables, and subqueries when they contain a set operator. To work around, wrap the SELECT containing a set operator using derived table syntax and apply FOR XML on top of it.
If this is it, you don't need the #Data variable. Let your sp return the result of query directly and you're done.
CREATE PROCEDURE BILLING_RESPONSE AS
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'CallTransactions!1!',
NULL AS 'TCALTRS!2!TRS_DAT_TE!cdata',
NULL AS 'TCALTRS!2!TRS_CRT_DT!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
TRS_DAT_TE,
TRS_CRT_DT
FROM TCALTRS
WHERE TRS_CRT_DT between CONVERT(date,GETDATE()-1) and CONVERT(date,getdate()) and
TRS_DAT_TE like '%(Submit Response)%'
FOR XML EXPLICIT
The error is not particularly clear, but what it is saying is that you can't use the FOR XML clause in the inline subquery because it contains a UNION (a type of set operator)
The suggested workaround is to wrap the subquery in something else and call it separately, for example:
CREATE PROCEDURE BILLING_RESPONSE
AS DECLARE #Data AS XML
;WITH DATA AS(
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'CallTransactions!1!',
NULL AS 'TCALTRS!2!TRS_DAT_TE!cdata',
NULL AS 'TCALTRS!2!TRS_CRT_DT!Element'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
TRS_DAT_TE,
TRS_CRT_DT
FROM TCALTRS
WHERE TRS_CRT_DT between CONVERT(date,GETDATE()-1) and CONVERT(date,getdate()) and
TRS_DAT_TE like '%(Submit Response)%'
FOR XML EXPLICIT
)
SELECT #Data = (SELECT * FROM DATA FOR XML EXPLICIT)
SELECT #DATA
GO

Using stored procedures to simplify XML generation

I am trying to write an SP to return invoice details in XML for transferring to a third party.
I have a working SP but it's a bit messy (simplified below):
SELECT (
SELECT GETDATE() AS HEADER_SLAStartTime
, DATEADD(HOUR, #SLA_HOURS, GETDATE()) AS HEADER_SLAEndTime
FOR XML PATH ('Header'), TYPE
) , (
SELECT ACCT AS CustomerCode
, ACCTNAME As CustomerName
, ADDR#1 As AddressLine1
, ADDR#2 AS AddressLine2
, ADDR#3 AS AddressLine3
, ADDR#4 AS AddressLine4
, POSTCODE AS AddressPostcode
, TELNO AS AddressTelno
FROM InvHdr
WHERE INVNO = #INVNO
FOR XML PATH('Customer'), TYPE
) , (
SELECT (
SELECT INVNO AS InvoiceNo
, [DATE] AS InvoiceDate
, [INVTYPE] AS InvoiceType
, CASE [SOURCE] WHEN 0 THEN 'Contract' WHEN 1 THEN 'Manual' WHEN 2 THEN 'Sales Order' ELSE '' END AS InvoiceSourceText
, THEIRREF AS CustomerReference
, YOURREF AS InternalReference
, (
SELECT ITEMNO AS ItemCode
, [ITEMDESC#1] AS ItemDesc
, [TYPE] AS ItemType
, [MEMO] AS ItemMemo
, [GOODS] AS ItemCharge
, [DISCOUNT] AS ItemDiscount
FROM InvItems
WHERE INVNO = HDR.INVNO
FOR XML PATH('InvItem'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceHeader'), TYPE
) , (
SELECT HDR.[GOODS] AS InvoiceNet
, HDR.VAT AS InvoiceVAT
, HDR.[GOODS] + HDR.VAT AS InvoiceGross
, (
SELECT VATCODE AS VATListCode
, VATAMT AS VATListAmount
, VATDESC AS VATListDescription
, VATRATE AS VATListRate
, VATGOODS AS VATListGoods
FROM InvVAT
WHERE InvVAT.INVNO = HDR.INVNO
ORDER BY VATAMT DESC
FOR XML PATH('VATSummary'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceFooter'), TYPE
)
FOR XML PATH('Invoices'), TYPE
)
FOR XML PATH(''), ROOT('Output')
This procedure works but I have to create lots of these to get different bits of information in different orders, I have tried creating separate SP's to get the data in sections, below is my first section SP:
CREATE PROCEDURE UDEF_DC_XML_INVOICEFOOTER(
#INVNO INT
)
AS
BEGIN
SELECT HDR.[GOODS] AS InvoiceNet
, HDR.VAT AS InvoiceVAT
, HDR.[GOODS] + HDR.VAT AS InvoiceGross
, (
SELECT VATCODE AS VATListCode
, VATAMT AS VATListAmount
, VATDESC AS VATListDescription
, VATRATE AS VATListRate
, VATGOODS AS VATListGoods
FROM InvVAT
WHERE InvVAT.INVNO = HDR.INVNO
ORDER BY VATAMT DESC
FOR XML PATH('VATSummary'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceFooter'), TYPE
END
When I try calling this:
SELECT UDEF_DC_XML_INVOICEFOOTER(#INVNO)
FOR XML PATH('Invoices'), TYPE
I get the error:
Msg 4121, Level 16, State 1, Line 1
Cannot find either column "dbo" or the user-defined function or aggregate "dbo.UDEF_DC_XML_INVOICEFOOTER", or the name is ambiguous.
In the end I'm hoping to be able to create multiple 4/5 line SP's that will call all the sections in the correct order. Either via calling the individual SP's in order or writing each section to variables and building the full XML afterwards.
Is it possible to call multiple stored procedures returning XML within a single statement?
Is it possible to call multiple stored procedures returning XML within
a single statement?
No, but you can use functions that return XML.
create function dbo.GetXML(#Value int) returns xml
as
begin
return (
select #Value as X
for xml path('Y'), type
)
end
Use like this:
select dbo.GetXML(1)
for xml path('Z')
Result:
<Z>
<Y>
<X>1</X>
</Y>
</Z>

Query XML creating field names whithout knowing node names

If I have a SQL SERVER 2012 table containing an XML field type. The records it could contain are as follows.
I have simplified my problem to the following.
Record 1:
ID_FIELD='nn1'
XML_FIELD=
<KNOWN_NAME_1>
<UNKNOWN_NAME1>Some value</UNKNOWN_NAME1>
<UNKNOWN_NAME2>Some value</UNKNOWN_NAME2>
... Maybe more ...
</KNOWN_NAME_1>
Record 2:
ID_FIELD='nn2'
XML_FIELD=
<KNOWN_NAME_2>
<UNKNOWN_NAME1>Some value</UNKNOWN_NAME1>
<UNKNOWN_NAME2>Some value</UNKNOWN_NAME2>
... Maybe more unknown fields ...
</KNOWN_NAME_2>
I want to output non xml:
UNKNOWN_NAME1 | UNKNOWN_NAME2 | ETC
-----------------------------------
Some Value Some value
For a known root value (i.e. KNOWN_NAME_1)
I.e. If I new the node values (which I don't) I could
SELECT
XMLData.Node.value('UNKNOWN_NAME1[1]', 'varchar(100)') ,
XMLData.Node.value('UNKNOWN_NAME2[1], 'varchar(100)')
FROM FooTable
CROSS APPLY MyXmlField.nodes('//KNOWN_NAME_1') XMLData(Node)
-- WHERE SOME ID value = 'NN1' (all XML records have a separate id)
All is good however I want to do this for all the nodes (unknown quantity) without knowing the node names. The root will only contain nodes it wont get any deeper.
Is this possible in SQL?
I have looked at this but I doubt I can get enough rights to implement it.
http://architectshack.com/ClrXmlShredder.ashx
If you don't know the column names in the output you have to use dynamic SQL:
-- Source table
declare #FooTable table
(
ID_FIELD char(3),
XML_FIELD xml
)
-- Sample data
insert into #FooTable values
('nn1', '<KNOWN_NAME_1>
<UNKNOWN_NAME1>Some value1</UNKNOWN_NAME1>
<UNKNOWN_NAME2>Some value2</UNKNOWN_NAME2>
</KNOWN_NAME_1>')
-- ID to look for
declare #ID char(3) = 'nn1'
-- Element name to look for
declare #KnownName varchar(100) = 'KNOWN_NAME_1'
-- Variable to hold the XML to process
declare #XML xml
-- Get the XML
select #XML = XML_FIELD
from #FooTable
where ID_FIELD = #ID
-- Variable for dynamic SQL
declare #SQL nvarchar(max)
-- Build the query
select #SQL = 'select '+stuff(
(
select ',T.N.value('''+T.N.value('local-name(.)', 'sysname')+'[1]'', ''varchar(max)'') as '+T.N.value('local-name(.)', 'sysname')
from #XML.nodes('/*[local-name(.)=sql:variable("#KnownName")]/*') as T(N)
for xml path(''), type
).value('.', 'nvarchar(max)'), 1, 1, '')+
' from #XML.nodes(''/*[local-name(.)=sql:variable("#KnownName")]'') as T(N)'
-- Execute the query
exec sp_executesql #SQL,
N'#XML xml, #KnownName varchar(100)',
#XML = #XML,
#KnownName = #KnownName
Result:
UNKNOWN_NAME1 UNKNOWN_NAME2
--------------- ---------------
Some value1 Some value2
The dynamically generated query looks like this:
select T.N.value('UNKNOWN_NAME1[1]', 'varchar(max)') as UNKNOWN_NAME1,
T.N.value('UNKNOWN_NAME2[1]', 'varchar(max)') as UNKNOWN_NAME2
from #XML.nodes('/*[local-name(.)=sql:variable("#KnownName")]') as T(N)
SE-Data

Resources