SQL Server XML output with CDATA into xml variable - sql-server

Following this question, I need to put the select result into a xml variable. How to do this avoiding the error message "The FOR XML and FOR JSON clauses are 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 or common table expression or view and apply FOR XML or FOR JSON on top of it.", please?
Here the test code:
declare #agent table
(
AgentID int,
Fname varchar(5),
SSN varchar(11)
)
insert into #agent
select 1, 'Vimal', '123-23-4521' union all
select 2, 'Jacob', '321-52-4562' union all
select 3, 'Tom', '252-52-4563'
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!cdata'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
AgentID,
Fname,
SSN
FROM #agent
FOR XML EXPLICIT
And here an example of what I want to do:
Declare #xml xml
...
set #xml= (SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!cdata'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
AgentID,
Fname,
SSN
FROM #agent
FOR XML EXPLICIT)

This is tricky...
You can move the UNION ALL part to a CTE like here. The problem is not the FOR XML but rahter the UNION (be carefull, it might be necessary to add an ORDER BY clause):
DECLARE #xml XML;
WITH UnionAllCte AS
(
SELECT
1 AS Tag,
NULL AS Parent,
NULL AS 'Agents!1!',
NULL AS 'Agent!2!AgentID',
NULL AS 'Agent!2!Fname!Element',
NULL AS 'Agent!2!SSN!cdata'
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
AgentID,
Fname,
SSN
FROM #agent
)
SELECT #xml=
(
SELECT * FROM UnionAllCte
FOR XML EXPLICIT
)
SELECT #xml;
And you should be aware, that CDATA as well as FOR XML EXPLICIT are outdated. Rather use FOR XML PATH() and for reading the appropriate methods the XML data type provides.
You might read this and read the following links too!

Related

How to parse XML from table in SQL Server

I have data in XML in column in table
SELECT ObjectXML
FROM DispOps_Events
[ObjectXML] [nvarchar](max) NOT NULL
A sample of the XML data:
<Document>
<DocumentId>3352597</DocumentId>
<DocumentFullPath>xxx</DocumentFullPath>
<Category>xxx</Category>
<ClientId>xxx</ClientId>
<ApplicationNumber>xxx</ApplicationNumber>
<ContractNumber>xxx</ContractNumber>
<Created>xxx</Created>
<Creator>xxx</Creator>
</Document>
And I need get data from DocumentId>XXXX/DocumentId> and insert into #tmpTable.
So 1. I cast varchar(max) to xml
select CAST(ObjectXML as XML) as fileXML
INTO #tmpXML
FROM DispOps_Events T WHERE MetastormMapName = 'DocumentsMap'
I tried
select
m.c.value('#DocumentId', 'varchar(max)') as DocumentId
--into #tmpTable
from #tmpXML as s
outer apply s.fileXML.nodes('Document/DocumentId') as m(c)
Error:
null data in the table
You don't need #temp tables to do this, you can just cast the nvarchar(max) data to the xml data type in a single query, e.g.:
/*
* Setup test data...
*/
drop table if exists dbo.DispOps_Events;
create table dbo.DispOps_Events (
ID int not null identity(1,1),
ObjectXML nvarchar(max)
);
insert dbo.DispOps_Events (ObjectXML) values
(N'<Document><DocumentId>2554742</DocumentId><!--...--></Document>'),
(N'<Document><DocumentId>2576868</DocumentId><!--...--></Document>'),
(N'<Document><DocumentId>2576869</DocumentId><!--...--></Document>'),
(N'<Document><DocumentId>2576870</DocumentId><!--...--></Document>');
/*
* Query XML...
*/
select ID, [DocumentId] = Document.DocumentId.value('text()[1]', 'nvarchar(50)')
from dbo.DispOps_Events
cross apply ( select try_cast(ObjectXML as xml) ) Transformers(RoolyTroolyXml)
cross apply RoolyTroolyXml.nodes('/Document/DocumentId') as Document(DocumentId);
ID
DocumentId
1
2554742
2
2576868
3
2576869
4
2576870

Get the Missing and Excess tags from a XML field

I have a table, Customer(Id int,Name nvarchar(100),Detail xml)
Sample Data:
1,'Abc','<ROOT> <TAG1>False</TAG1> <TAG3>value</TAG3> <TAG14>value</TAG14> </ROOT>'
2,'Pqr','<ROOT> <TAG2>False</TAG2> <TAG8>value</TAG8> <TAG11>value</TAG11> </ROOT>'
Also I have XML variable , #v_xml = '<ROOT> <TAG1>value</TAG1> <TAG2>value</TAG2> <TAG8>False</TAG8> <TAG14>False</TAG14> </ROOT>'.
Now I want get the Missing Tags and Excess Tags (in XML format) of each Customer comparing to the XML variable #v_xml (No need to consider the value, what ever it may be)
Expected Result:
Id Name Missing Excess
1,'Abc','<ROOT><TAG2>value</TAG2> <TAG8>value</TAG8> </ROOT>','<ROOT><TAG3>value</TAG3> </ROOT>'
2,'Pqr','<ROOT><TAG1>value</TAG1> <TAG14>False</TAG14> </ROOT>','<ROOT><TAG11>value</TAG11> </ROOT>'
There is no nested nodes/level in the XML. Only direct child elements under ROOT tag. But the number of child tags will vary. I am looking for a simple and common logic to resolve this (with or without SQL query).
Main idea parse tag name (local-name(.)) and concat diffs into xml
DECLARE #t TABLE (
Id INT PRIMARY KEY,
Name VARCHAR(50),
X XML
)
INSERT INTO #t
VALUES
(1, 'Abc', N'<ROOT><TAG1>False</TAG1><TAG3>value</TAG3><TAG14>value</TAG14></ROOT>'),
(2, 'Pqr', N'<ROOT><TAG2>False</TAG2><TAG8>value</TAG8><TAG11>value</TAG11></ROOT>')
DECLARE #x XML = N'<ROOT><TAG1>value</TAG1><TAG2>value</TAG2><TAG8>False</TAG8><TAG14>False</TAG14></ROOT>'
SELECT t.Id, t.Name, t2.val.query('Missing/*'), t2.val.query('Excess/*')
FROM #t t
CROSS APPLY (
SELECT
Missing = Missing.query,
Excess = Excess.query
FROM (
SELECT
query = t.c.query('.'),
tag = t.c.value('local-name(.)', 'SYSNAME')
FROM x.nodes('*/*') t(c)
) Excess
FULL JOIN (
SELECT
query = t.c.query('.'),
tag = t.c.value('local-name(.)', 'SYSNAME')
FROM #x.nodes('*/*') t(c)
) Missing ON Missing.tag = Excess.tag
WHERE Missing.tag IS NULL
OR Excess.tag IS NULL
FOR XML PATH(''), TYPE
) t2 (val)
Output -
----------- ---------- ------------------------- ------------------------------------------
1 Abc <TAG3>value</TAG3> <TAG2>value</TAG2><TAG8>False</TAG8>
2 Pqr <TAG11>value</TAG11> <TAG1>value</TAG1><TAG14>False</TAG14>

Remove Root XML nodes from default output of Sql Server statement

I want #MyActualXMLOut to look like #MyDesiredXMLOut... How? Thanks in advance!
#MyDesiredXMLOut =
<MyRequiredRoot>
<Property1>Value1</Property1>
<Property2>Value2</Property2>
</MyRequiredRoot>
#MyActualXMLOut
<_x0040_MyTableVar>
<MyXML>
<MyRequiredRoot>
<Property1>Value1</Property1>
<Property2>Value2</Property2>
</MyRequiredRoot>
</MyXML>
</_x0040_MyTableVar>
The code below can be run as is...
DECLARE #MyDesiredXMLOut XML;
DECLARE #MyActualXMLOut XML;
SELECT #MyDesiredXMLOut =
CONVERT( XML,
'<MyRequiredRoot><Property1>Value1</Property1>
<Property2>Value2</Property2>
</MyRequiredRoot>' );
DECLARE #MyTableVar table( ID int NOT NULL, MyXML XML NOT NULL );
INSERT INTO #MyTableVar VALUES( 1, #MyDesiredXMLOut )
SELECT #MyActualXMLOut =
( SELECT MyXML
FROM #MyTableVar
WHERE ID = 1
FOR XML AUTO )
SELECT #MyDesiredXMLOut;
SELECT #MyActualXMLOut;
FOR XML AUTO is trying to add information about your table name (which likely contains characters that aren't valid XML element names) and the column name it came from.
Change
SELECT #MyActualXMLOut =
( SELECT MyXML
FROM #MyTableVar
WHERE ID = 1
FOR XML AUTO )
to
SELECT #MyActualXMLOut =
( SELECT MyXML as '*'
FROM #MyTableVar
WHERE ID = 1
FOR XML PATH('') )
Explanation: as '*' tells SQL Server that you just want the column value directly, don't use the column name as a tag name; FOR XML PATH('') says you don't want to add any additional root node around the output, just use as is.
You can use query('/')
SELECT #MyActualXMLOut =
( SELECT MyXML.query ('/')
FROM #MyTableVar
WHERE ID = 1
FOR XML PATH('')
)
I don't know if this is real life example, but:
The value in the table is the XML already...
Leave away the FOR XML AUTO (Anyway as pointed out one should prefer PATH):
SELECT #MyActualXMLOut =
( SELECT MyXML
FROM #MyTableVar
WHERE ID = 1);
Or even simpler
SELECT #MyActualXMLOut = MyXML
FROM #MyTableVar
WHERE ID = 1;

TSQL Select Clause with Case Statement

I have a basic select statement that is getting me a list of types that are stored in the database:
SELECT teType
FROM BS_TrainingEvent_Types
WHERE source = #source
FOR XML PATH ('options'), TYPE, ELEMENTS, ROOT ('types')
My table contains a type column and a source column.
There is a record in that table where I need to include it for two separate sources but I can't create a separate record for it.
**Table Data**
type | source
test users
test2 members
test3 admins
I need a case statement to be able to say IF source = admins also give me the type test2.
Does this make sense and is it possible to do with a basic select?
Update
I came up with this temp solution but I still think there is a better way to handle this.:
DECLARE #tmp AS TABLE (
QID VARCHAR (10));
INSERT INTO #tmp (QID)
SELECT DISTINCT qid
FROM tfs_adhocpermissions;
SELECT t.QID,
emp.FirstName,
emp.LastName,
emp.NTID,
(SELECT accessKey
FROM TFS_AdhocPermissions AS p
WHERE p.QID = t.QID
FOR XML PATH ('key'), TYPE, ELEMENTS, ROOT ('keys'))
FROM #tmp AS t
LEFT OUTER JOIN
dbo.EmployeeTable AS emp
ON t.QID = emp.QID
FOR XML PATH ('data'), TYPE, ELEMENTS, ROOT ('root');
try this
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--create temp table for testing
IF OBJECT_ID('Tempdb..#BS_TrainingEvent_Types') IS NOT NULL
DROP TABLE #BS_TrainingEvent_Types
SELECT [type] ,
[source]
INTO #BS_TrainingEvent_Types
FROM ( VALUES ( 'test', 'users'), ( 'test2', 'members'),
( 'test3', 'admins') ) t ( [type], [source] )
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--final query
DECLARE #Source VARCHAR(10) = 'users'
IF #Source = 'admins'
BEGIN
SELECT [Type]
FROM #BS_TrainingEvent_Types
WHERE source = #source
OR [type] = 'test2'
FOR XML PATH('options') ,
TYPE ,
ELEMENTS ,
ROOT('types')
END
ELSE
BEGIN
SELECT [Type]
FROM #BS_TrainingEvent_Types
WHERE source = #source
FOR XML PATH('options') ,
TYPE ,
ELEMENTS ,
ROOT('types')
END
select sq.teType
from (
SELECT t.teType
FROM BS_TrainingEvent_Types t
WHERE t.source = #source
union all
SELECT t.teType
FROM BS_TrainingEvent_Types t
WHERE #source = 'admins' and t.source = 'members'
) sq
FOR XML PATH ('options'), TYPE, ELEMENTS, ROOT ('types');
Though normally it would be better to introduce an additional table that would store these relationships, so that the whole idea would be more expandable.

T- SQL transform unstructured XML into columns

I have a table from a vendor application that stores some xml data into a column of type varchar(200).
Table structure and sample data is here
declare #table table
(
MerchantID int not null
,Data varchar(200) not null)
insert into #table
select 1, '<product><productID>1</productID><pdesc>ProductDesc</pdesc></product>'
union all
select 2, '<product><itemid>1</itemid><itemname>name of item</itemname></product>'
Is there a way to transform raw xml data into relation format like below in a stored procedure?
for e.g when merchantID passed is 1
MerchantID productID pdesc
1 1 Product Desc
when MerchantID pass is 2 output should be
MerchantID itemid itemname
2 1 name of item
You can use XPath in SQL Server to access XML data nodes.
Here's an example, using your data.
declare #test xml
set #test = '<product><productID>1</productID><pdesc>ProductDesc</pdesc></product>'
SELECT
#test.value('(/product/productID/node())[1]', 'nvarchar(max)') as productID,
#test.value('(/product/pdesc/node())[1]', 'nvarchar(max)') as pdesc
From there, you should be able to perform your union like so:
SELECT 1,
xmlfield1.value('(/product/productID/node())[1]', 'int') as id,
xmlfield1.value('(/product/pdesc/node())[1]', 'nvarchar(max)') as desc
union
SELECT 2,
xmlfield2.value('/product/itemid/node())[1]', 'int') as id,
xmlfield2.value('/product/itemname/node())[1]', 'nvarchar(max)') as desc
if your data is in the same column, you can use a case statement to resolve it.
case
when merchantId = 1 data.value('(/product/productID/node())[1]', 'int')
else data.value('/product/itemid/node())[1]', 'int')
end as id

Resources