NULL elements in FOR XML Clause / Alternative to XSINIL - sql

I have some legacy code similar to:
...
'<field1>' +
case when field1 is null then '' else cast( field1 as varchar ) end +
'</field1>' +
...
Which generates the following XML for empty elements:
....
<field1></field1>
...
And I'm replacing the query with FOR XML:
SELECT field1,
...
FOR XML RAW, ELEMENTS
Now, this does not output an element for columns with NULL values. I know about XSINIL:
FOR XML RAW, ELEMENTS XSINIL
But this generates a namespaced empty XML element, which is not compatible with the legacy code reading this output.
...
<field1 xsi:nil="true" />
...
Any suggestions on generating the format below while still using the FOR XML Clause?
....
<field1></field1>
...
Thanks!

One very simple solution would be: just don't specify the "XSINIL" after ELEMENTS!
FOR XML RAW, ELEMENTS
In that case, you'll just get no XML entry for any values that are NULL.
If you really want an empty XML tag, you need to use something like this:
SELECT
......
ISNULL(CAST(field1 AS VARCHAR(100)), '') AS 'field1',
......
FROM dbo.YourTable
FOR XML RAW, ELEMENTS
thus turning the empty field1 into an empty string and thus serializing it into the XML.

You can double up on the columns that can have a null value with an empty string. The values will be concatenated and the empty string makes sure you will always have something that builds the node.
You need to use for xml path instead of for xml raw.
declare #T table
(
Col1 int,
Col2 int
)
insert into #T values(1, 2)
insert into #T values(1, null)
insert into #T values(null, 2)
insert into #T values(null, null)
select Col1,
'' as Col1,
Col2,
'' as Col2
from #T
for xml path('row')
Result:
<row>
<Col1>1</Col1>
<Col2>2</Col2>
</row>
<row>
<Col1>1</Col1>
<Col2></Col2>
</row>
<row>
<Col1></Col1>
<Col2>2</Col2>
</row>
<row>
<Col1></Col1>
<Col2></Col2>
</row>

you can avoid labor work of isnull by this
declare #Var varchar(max)
set #Var=(select
FirstName,
LastName,
Middldle_Name
From Customer
FOR XML AUTO, ELEMENTS xsinil , ROOT('Customer')
)
select CONVERT(xml,REPLACE(#Var,' xsi:nil="true"',''))

Related

Concatenate/aggregate strings with JSON in SQL Server

This might be a simple question for those who are experienced in working with JSON in SQL Server. I found this interesting way of aggregating strings using FOR XML in here.
create table #t (id int, name varchar(20))
insert into #t
values (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus')
select id
,Names = stuff((select ', ' + name as [text()]
from #t xt
where xt.id = t.id
for xml path('')), 1, 2, '')
from #t t
group by id
How can I do the same using JSON instead of XML?
You cannot replace the XML approach with JSON. This string concatenation works due to some XML inner peculiarities, which are not the same in JSON.
Starting with SQL Server 2017 onwards you can use STRING_AGG(), but with earlier versions, the XML approach is the way to go.
Some background and a hint
First the hint: The code you showed is not safe for the XML special characters. Check my example below.
First I declare a simple XML
DECLARE #xml XML=
N'<a>
<b>1</b>
<b>2</b>
<b>3</b>
<c>
<d>x</d>
<d>y</d>
<d>z</d>
</c>
</a>';
--The XPath . tells the XML engine to use the current node (and all within)
--Therefore this will return any content within the XML
SELECT #xml.value('.','varchar(100)')
--You can specify the path to get 123 or xyz
SELECT #xml.query('/a/b').value('.','varchar(100)')
SELECT #xml.query('//d').value('.','varchar(100)')
Now your issue to concatenate tabular data:
DECLARE #tbl TABLE(SomeString VARCHAR(100));
INSERT INTO #tbl VALUES('This'),('will'),('concatenate'),('magically'),('Forbidden Characters & > <');
--The simple FOR XML query will tag the column with <SomeString> and each row with <row>:
SELECT SomeString FROM #tbl FOR XML PATH('row');
--But we can create the same without any tags:
--Attention: Look closely, that the result - even without tags - is XML typed and looks like a hyper link in SSMS.
SELECT SomeString AS [*] FROM #tbl FOR XML PATH('');
--Now we can use as a sub-select within a surrounding query.
--The result is returned as string, not XML typed anymore... Look at the forbidden chars!
SELECT
(SELECT SomeString FROM #tbl FOR XML PATH('row'))
,(SELECT SomeString AS [*] FROM #tbl FOR XML PATH(''))
--We can use ,TYPE to enforce the sub-select to be treated as XML typed itself
--This allows to use .query() and/or .value()
SELECT
(SELECT SomeString FROM #tbl FOR XML PATH('row'),TYPE).query('data(//SomeString)').value('.','nvarchar(max)')
,(SELECT SomeString AS [*] FROM #tbl FOR XML PATH(''),TYPE).value('.','nvarchar(max)')
XQuery's .data() can be used to concatenate named elements with blanks in between.
XQuery's .value() must be used to re-escpae forbidden characters.

SQL Server 2012 create list of VARBINARY in single XML element

I am currently struggling to find a solution to the following problem.
I have a result set of VARBINARY values - for example:
QAAAAAAAAAE=
QAAAAAAAAAQ=
these results Need to be packed into a XML Element delimited by a single space (to siginify an array of values). Example of how the result should look:
<results>QAAAAAAAAAE= QAAAAAAAAAQ=</results>
The issue I am having while using XML PATH is that I cannot combine a ' ' (varchar) and the result varbinary field. If I add the ' ' before the result and then convert to varbinary the result is incorrect due to having converted the space as well. Here is an example of what I have attempted thus far:
(
STUFF((SELECT DISTINCT ' ' + CONVERT( VARBINARY, id)
FROM results
FOR XML PATH('ns2:children')
),1,1,'')
),
You should not deal with XML on string level. This might have various side effects...
You can try this:
I fill a table with some values in a VARBINARY column:
DECLARE #table TABLE(SomeData VARBINARY(MAX));
INSERT INTO #table VALUES(0x12AF)
,(CAST('test' AS VARBINARY(MAX)))
,(CAST(GETDATE() AS VARBINARY(MAX)));
SELECT *
FROM #table;
The result
SomeData
0x12AF
0x74657374
0x0000A81100AD9F69
If you get this as XML, the conversion to base64 is done implicitly
SELECT * FROM #table FOR XML PATH('result')
the result
<result>
<SomeData>Eq8=</SomeData>
</result>
<result>
<SomeData>dGVzdA==</SomeData>
</result>
<result>
<SomeData>AACoEQCtn2k=</SomeData>
</result>
Now there is XQuery-function data() to your rescue. It will automatically concatenate all sub-values separated by a blank:
SELECT(
SELECT *
FROM #table
FOR XML PATH('result'),TYPE
).query('data(/result/SomeData)') AS result
FOR XML PATH('results')
The result is
<results>
<result>Eq8= dGVzdA== AACoEQCtn2k=</result>
</results>
Pls. try short of SQL Command which could help u to achieve the above result :
SELECT REPLACE(T.Data, '</results><results>', ' ')
FROM
(
SELECT STUFF(
(
SELECT DATA [results]
FROM <table_name> FOR XML PATH('')
), 1, 1, '<') [Data]
) T;
Result :
<results>QAAAAAAAAAE= QAAAAAAAAAQ=</results>
If I understand what do you want?! A way to get <results>QAAAAAAAAAE= QAAAAAAAAAQ=</results> as result is:
select ltrim((
select ' '+cast(id as varchar(50))
from t
group by id
for xml path(''))) results
for xml path('');
SQL Fiddle Demo

Why does STUFF remove XML?

Please see the DDL below:
create table #Test (id int,Name varchar(30))
insert into #Test values (1,'Ian')
insert into #Test values(1,'Mark')
insert into #Test values(2,'James')
insert into #Test values(3,'Karen')
insert into #Test values(3,'Suzie')
and the SQL below:
select * from #Test for xml path('')
which returns:
<id>1</id>
<Name>Ian</Name>
<id>1</id>
<Name>Mark</Name>
<id>2</id>
<Name>James</Name>
<id>3</id>
<Name>Karen</Name>
<id>3</id>
<Name>Suzie</Name>
This is what I would expect. Now see the SQL below:
SELECT distinct ID,
STUFF( (select ','+ NAME from #Test as #Test1 where #Test1.id=#Test2.id FOR XML PATH('')),1,1,'') FROM #Test as #Test2
which returns:
1 Ian,Mark
2 James
3 Karen,Suzie
This is what I want returned. However, where have the XML elements gone?
You have to compare apples to apples. While it's true that
select * from #Test for xml path('')
produces something that looks like XML (but technically isn't because it doesn't have a root element), this (what you're actually running)
select ',' + name from #Test for xml path('')
doesn't. On my machine, it produces the ff string: ",Ian,Mark,James,Karen,Suzie". From there, the stuff function whacks the first comma and you get a list of comma-separated values.
Why does STUFF remove XML?
STUFF removes the first comma in the string, it is not responsible for removing the XML element names.
FOR XML PATH uses the column names to create the XML element names. When you concat two values together ','+ NAME the resulting column has no name so FOR XML PATH can not generate an element name for you.
The behavior is documented in Columns without a Name.
Any column without a name will be inlined. For example, computed
columns or nested scalar queries that do not specify column alias will
generate columns without any name.
It's not the STUFF, this is only for removing the superfluous first ,.
The concat removes the XML stuff:
','+ NAME
or
NAME + ''
Don't ask me why it's working like this, maybe it's documented somewhere :-)
Inner for xml statement is just for producing concatenating result. Add outer for xml statement:
SELECT distinct ID,
STUFF( (select ','+ NAME
from Test as #Test1
where #Test1.id=#Test2.id
FOR XML PATH('')),1,1,'') as Names
FROM Test as #Test2
FOR XML PATH('')
Output:
<ID>1</ID><Names>Ian,Mark</Names><ID>2</ID><Names>James</Names><ID>3</ID><Names>Karen,Suzie</Names>
Fiddle http://sqlfiddle.com/#!6/5f254/13
alias the column - then you get xml tags
do not alias the column - then no xml tags
-- with tags
select 'apple' apple for xml path('')
-- without tags
select 'apple' for xml path('')

Concatenate XML From Various XML Columns without type casting to string

I have three XML columns in my SQL Table (single row table just used for storing XML) and the XML structure is something like this:
In Column 1 -
<Column1XML>
....
</Column1XML>
In Column 2 -
<Column2XML>
....
</Column2XML>
In Column 3 -
<Column3XML>
....
</Column3XML>
The final resultant XML that I am looking for is:
<SomeTagName>
<Column1XML>
....
</Column1XML>
<Column2XML>
....
</Column2XML>
<Column3XML>
....
</Column3XML>
</SomeTagName>
How can I obtain this required structure without doing string concatenation? I am sure there must be a way out with Typed XML.
Thanks in advance for looking up my question.
Here are two ways for you.
Sample data
declare #T table
(
Col1 xml,
Col2 xml,
Col3 xml
)
insert into #T values
(
'<Column1XML></Column1XML>',
'<Column2XML></Column2XML>',
'<Column3XML></Column3XML>'
)
Use the root name as column alias
select Col1 as 'SomeTagName',
Col2 as 'SomeTagName',
Col3 as 'SomeTagName'
from #T
for xml path('')
Use * as column alias and specify a path().
select Col1 as '*',
Col2 as '*',
Col3 as '*'
from #T
for xml path('SomeTagName')
Result:
<SomeTagName>
<Column1XML />
<Column2XML />
<Column3XML />
</SomeTagName>

SQL Server XML Import - Is there a way to make the “value” operator return null on missing elements

I have a stored proc that inserts XML into an underlying table. Unfortunately the XML passed in as a Sql XML type sometimes has particular elements missing. This makes the INSERT statement insert XML node values into the wrong columns.
My T-SQL looks something like this:
ALTER PROCEDURE [dbo].[up_Test_Insert]
#xmlData XML
AS
BEGIN
INSERT INTO
TestTable
(
ColumnA,
ColumnB,
ColumnC
)
SELECT
Tbl.Col.value(ColumnA[1]', 'INT'),
Tbl.Col.value('ColumnB[1]', 'INT'),
Tbl.Col.value('ColumnC[1]', 'INT')
FROM
#xmlData.nodes('DocumentElement/DataTable') Tbl(Col)
Sometimes ColumnB element may be missing in the XML structure. This results in the ColumnC node value being inserted into the ColumnB table column.
Is there a way to make the “value” operator return null on missing elements?
You have probably simplified your sample a bit too much. What you have here works as expected and will not insert ColumnC values to table ColumnA and missing ColumnB tags will give you a null value.
Try this:
declare #xmlData xml = '
<DocumentElement>
<DataTable>
<ColumnA>1</ColumnA>
<ColumnC>3</ColumnC>
</DataTable>
</DocumentElement>
'
SELECT
Tbl.Col.value('ColumnA[1]', 'INT'),
Tbl.Col.value('ColumnB[1]', 'INT'),
Tbl.Col.value('ColumnC[1]', 'INT')
FROM
#xmlData.nodes('DocumentElement/DataTable') Tbl(Col)
Result:
----------- ----------- -----------
1 NULL 3