Sql2012 FOR XML Inconsistent "empty element" Behavior - sql

I have an empty string in a table that I am selecting into XML. Ideally, this will produce an empty element, like <name /> instead of <name></name>.
It seems that if my empty string is the only thing I select, I get the undesired (verbose) empty tag ... but if I select other things, including another empty string (or the same empty string), I get the desirable empty tag ().
Can anyone tell me why this happens? And more importantly, how to control it?
declare #table table( TextValue nvarchar(100) )
insert into #table( TextValue ) values ( '' )
select
( select t.TextValue name for xml path( '' ), type )
from #table t
for xml path( 'row' ), root( 'root' ), type
/* result:
<root>
<row>
<name></name> <!-- select just once, verbose -->
</row>
</root>
*/
select
( select t.TextValue name for xml path( '' ), type ),
( select t.TextValue name for xml path( '' ), type )
from #table t
for xml path( 'row' ), root( 'root' ), type
/* result:
<root>
<row>
<name /> <!-- select twice, nice and neat -->
<name />
</row>
</root>
*/
Thanks!

It made me curious too:
That's what I came up with:
If you tell the XML engine to create XML it is done the way
open the element
fill in the content
close the element
All of these example lead to <element></element>
select '' name
for xml path( 'row' )
select '' name
,'' name2
for xml path( 'row' )
select '' name
,''
,'' name
for xml path( 'row' )
But if the XML-elment was created before the XML Engine is dealing with it, the (better) short form is used.
All of them lead to <element/>
select CAST('<name></name>' AS XML)
for xml path( 'row' )
select CAST('<name/>' AS XML)
for xml path( 'row' )
select (SELECT '' AS name FOR XML PATH(''),TYPE)
for xml path( 'row' )
UPDATE
You even can combine this
select (SELECT '' AS name FOR XML PATH(''),TYPE)
,'' AS name
for xml path( 'row' )
leads to
<row>
<name />
<name></name>
</row>
UPDATE 2
I think this has nothing to do with the element name's length. Neither is it bound to the count of columns you call. This is merely bound to: Is the XML created this moment or was it create before?
DECLARE #n1 VARCHAR(100)='';
DECLARE #n2 XML='<name></name>';
DECLARE #n3 XML='<name/>';
SELECT #n1 AS name FOR XML PATH('root');
SELECT #n2 FOR XML PATH('root');
SELECT #n3 FOR XML PATH('root');

Related

Generate XML of the Structure of a table when it's empty in SQL Server

I am trying to generate a table containing one row that contains the XML of each temporary table in a stored procedure.
The problem is when the table is empty the FOR XML PATH('RS'), root('OR_RS') returns null.
What I want is: when the table is empty, return the structure of this table
something like this :
<OR_RS>
<ContactCode></ContactCode>
<EmailPaper></EmailPaper>
<ShortEmail></ShortEmail>
<WebSite></WebSite>
<Providers></Providers>
</OR_RS>
I have tried to do this :
-- ,ISNULL( (SELECT * FROM #OR_RS FOR XML PATH('RS'), root('OR_RS')), (SELECT ISNULL(ContactCode,'') , ISNULL(EmailPaper,'') , ISNULL(ShortEmail,'') , ISNULL(WebSite,'') , ISNULL(Providers,'') FROM #OR_RS FOR XML RAW)) as OR_RS
but it returns always Null:
SELECT 1 as Id, (SELECT * FROM #OR_MK FOR XML PATH('MK'), root('OR_MK') ) as OR_MK
,(SELECT * FROM #OR_CA FOR XML PATH('CA'), root('OR_CA') ) as OR_CA
-- ,ISNULL( (SELECT * FROM #OR_RS FOR XML PATH('RS'), root('OR_RS')), (SELECT ISNULL(ContactCode,'') , ISNULL(EmailPaper,'') , ISNULL(ShortEmail,'') , ISNULL(WebSite,'') , ISNULL(Providers,'') FROM #OR_RS FOR XML AUTO)) as OR_RS
,(SELECT * FROM #OR_RS FOR XML PATH('RS'), root('OR_RS')) as OR_RS
,(SELECT * FROM #OR_DC FOR XML PATH('DC'), root('OR_DC') ) as OR_DC
,(SELECT * FROM #BENEFICIARY FOR XML PATH('BEN'), root('BENEFICIARY') ) as BENEFICIARY
,(SELECT * FROM #MK_REPORT for XML PATH('REP'), root('MK_REPORT')) as MK_REPORT
Whatever you try to do with this...
You might want to have a look at sp_describe_first_result_set, but, since this is a procedure, it will not be that easy to get its result into your query...
You can use a trick (a RIGHT JOIN) to enforce a resultset. And with ELEMENTS XSINIL you force the engine to include the column in any case. Otherwise XML takes missing elements as NULL, so your empty result would not be written into the result XML at all otherwise. Try this:
DECLARE #tbl TABLE(ID INT IDENTITY,SomeValue INT,SomeString VARCHAR(100));
SELECT *
FROM #tbl
RIGHT JOIN (SELECT 1 AS x) AS tbl ON 1=1
FOR XML RAW, ELEMENTS XSINIL
The result
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ID xsi:nil="true" />
<SomeValue xsi:nil="true" />
<SomeString xsi:nil="true" />
<x>1</x>
</row>
If you want to get more details, you might add ,XMLDATA or ,XMLSCHEMA to generate full meta data.

how to generate xml with element and attribute using xml explicit

im trying to generate xml in the following format:
<Root>
<Domain>Abc</Domain>
<Name>Xyz</Name>
<Contents>
<Content>
<ID>1</ID>
<Value>Test 1</Value>
<Record ID="1">Test 1</Record>
</Content>
<Content>
<ID>2</ID>
<Value>Test 2</Value>
<Record ID="2">Test 2</Record>
</Content>
</Contents>
</Root>
My query is as follows:
declare #TestTable table (ID int, Value varchar(100))
insert into #TestTable values (1,'Test 1')
insert into #TestTable values (2,'Test 2')
declare #Domain varchar(max)='Abc'
declare #Name varchar(max)='Xyz'
SELECT
1 AS Tag,
NULL AS Parent,
#Domain as 'Root!1!Domain!Element',
#Name as 'Root!1!Name!Element',
NULL as 'Contents!2!Element',
NULL as 'Content!3!ID!Element',
NULL as 'Content!3!Value!Element',
NULL as 'Content!3!Record!Element'
union
SELECT
2 AS Tag,
1 AS Parent,
NULL,NULL,NULL,NULL,NULL,NULL
union
select
3 as Tag,
2 as Parent,
NUll,NUll,NULL,
ID,Value,Value
from #TestTable
FOR XML EXPLICIT
my query does not produce the record tag completely, currently it is
<Record>Test 2</Record>
which should be as
<Record ID=2>Test 2</Record>
I tried all the possibilities but not getting the tag. Can anyone help me solving this issue.
I could not get the expected output from xml explicit, instead i used xml path and got the output. this is my updated query
SELECT
#Domain "Domain",
#Name "Name",
(
SELECT
ID "ID",
Value "Value",
(select
ID "#ID",
Value as "text()"
FOR XML PATH('Record'), ELEMENTS, TYPE )
FROM #TestTable
FOR XML PATH ('Content'), TYPE, ROOT('Contents')
)
FOR XML PATH ('Root')
you are welcome to post the fix which uses xml explicit.
Don't forget the ORDER BY when use FOR XML EXPLICIT.
SELECT
1 AS Tag,
NULL AS Parent,
#Domain as 'Root!1!Domain!Element',
#Name as 'Root!1!Name!Element',
NULL as 'Contents!2!Element',
NULL as 'Content!3!ID!Element',
NULL as 'Content!3!Value!Element',
NULL as 'Record!4!ID',
NULL as 'Record!4!'
union
SELECT
2 AS Tag,
1 AS Parent,
#Domain,NULL,NULL,NULL,NULL,NULL, null
union
select
3 as Tag,
2 as Parent,
#Domain,NUll,NULL,
ID,Value,null,null
from #TestTable
union
select
4 as Tag,
3 as Parent,
#Domain,NUll,NULL,
ID,Value,ID,Value
from #TestTable
ORDER BY 'Root!1!Domain!Element','Contents!2!Element','Content!3!ID!Element','Record!4!ID'
FOR XML EXPLICIT

How to Generate xml from sql for below pattern

I'm writing one stored procedure, which I have to create a xml column from db.
µ = CHAR(181) this is value separator,
¶ = CHAR(182) this is row separator
This is the statement I wrote. I know its not well formed.
SELECT #xmlString= CAST('<root><Section> ID =' + REPLACE(REPLACE ('20211µ1¶20212µ2', CHAR(182),
'</Section><Section> ID ='),CHAR(181), ' Slno=') + '</Section></root>' AS XML)
This is the pattern which I need to display like this.
<root>
<sections id="20211" slno="1" ></sections>
<sections id="20215" slno="2" ></sections>
</root>
declare #s varchar(50) = '20211µ1¶20212µ2'
declare #xmlString xml
;with C as
(
select T.N.value('value[1]', 'int') as id,
T.N.value('value[2]', 'int') as slno
from (select cast('<item><value>'+replace(replace(#s, 'µ','</value><value>'), '¶','</value></item><item><value>')+'</value></item>' as xml)) as X(XMLCol)
cross apply X.XMLCol.nodes('item') as T(N)
)
select #xmlString =
(
select C.id as [#id] ,
C.slno as [#slno]
from C
for xml path('sections'), root('root'), type
)
select #xmlString
Result:
<root>
<sections id="20211" slno="1" />
<sections id="20212" slno="2" />
</root>

How to remove column name when selecting from a XMLTYPE column using FOR XML PATH

I have a table with a XMLTYPE column named 'InvoiceXML'.
The data in this column is XML in the form:
<Invoice CustomerNum="1234" >
<CustomDeliveryDetails />
</Invoice>
When I do a
SELECT ... FOR XML PATH(''), ROOT('Invoices')
I end up with:
<Invoices>
<InvoiceXML>
<Invoice CustomerNum="1234" >
<CustomDeliveryDetails />
</Invoice>
</InvoiceXML>
</Invoices>
How do I stop the column name InvoiceXML appearing in the output?
declare #T table (invoiceXML xml)
insert into #T values (
'<Invoice CustomerNum="1234" >
<CustomDeliveryDetails />
</Invoice>
')
insert into #T values (
'<Invoice CustomerNum="4321" >
<CustomDeliveryDetails />
</Invoice>
')
select (select T.invoiceXML)
from #T as T
for xml path(''), root('Invoices')
Edit 1 The subquery (select T.invoiceXML) has no column name so it is removed.
Try:
SELECT cast(cast(InvoiceXML as nvarchar(max)) + '' as XML)
FROM whatever
FOR XML PATH(''), ROOT('Invoices')
Try this:
SELECT InvoiceXML.query('//Invoice')
FROM <YOUR_TABLE>
FOR XML PATH('')
Try specifying a xpath query to invoice in the FPR XML PATH
e.g: `FOR XML PATH('//InvoiceXML')`

MS SQL 2005 Table to XML

I have a simple table in SQL Server 2005, I wish to convert this to XML (using the "FOR XML" clause). I'm having trouble getting my XML to look like the required output.
I've tried looking through various tutorials on the web, but I am struggling. Can someone help?
The table I have looks like this
TYPE,GROUP,VALUE
Books,Hardback,56
Books,Softcover,34
CDs,Singles,45
CDS,Multis,78
The output style I need is:
<data>
<variable name="TYPE">
<row>
<column>GROUP</column>
<column>VALUE</column>
</row>
<row>
<column>GROUP</column>
<column>VALUE</column>
</row>
</variable>
<variable name="TYPE">
<row>
<column>GROUP</column>
<column>VALUE</column>
</row>
<row>
<column>GROUP</column>
<column>VALUE</column>
</row>
</variable>
</data>
Edit:
As far as I can tell I require the multiple values. I'm generating XML for use with Xcelsius (Linking XML and Xcelsius) so have no control over in the formatting of the XML. I can generate the XML using ASP as per the linked tutorial, but I was hoping to get it straight from SQL Server.
Edit 2:
I was hoping for something elegant and tidy... but Godeke's example got the closest. Some fiddling with the SQL and I've come up with:
select
"type" as '#name',
"group" as 'row/column',
null as 'row/tmp',
"value" as 'row/column'
from tableName
for xml path('variable'), root('data')
Outputs almost in the exact way I wanted. The null/tmp line doesn't even output; it is just preventing the concatenation. Still the tag <variable name="TYPE"> repeats for each row, which I can't have.
As close as I can get is this:
select "type" as '#name', "group" as 'row/column1', "value" as 'row/column2'
from tableName
for xml path('variable'), root('data')
Naming two items the same ("column" and "column") isn't something I know how to do in one pass, but on the other hand it is an odd XML schema choice; normally elements have unique names if they contain distinct data. The obvious choice (name them both 'row/column') simply concatenates them in the output into one value.
Also note that each returned row will be a "variable" element distinct from the others. To get the nesting without redundant records will require a subquery:
select distinct "type" as '#name'
from Agent
for xml path('variable'), root('data')
was my first thought, but the distinct prevents nesting.
All this makes me think that to get the exact output you need you might have to use EXPLICIT mode. Perhaps my problem is for something like this I punt and use a DOMDocument in code :).
I prefer using for XML PATH, it provides a nicer way to control your elements etc.
See
But this is quite tricky
/*
create table #tablename
(
[type] varchar(20),
[group] varchar(20),
[value] varchar(20)
)
insert into #tablename select 'type1','group11','value111'
insert into #tablename select 'type1','group11','value112'
insert into #tablename select 'type1','group12','value121'
insert into #tablename select 'type1','group12','value122'
insert into #tablename select 'type2','group21','value211'
insert into #tablename select 'type2','group21','value212'
insert into #tablename select 'type2','group22','value221'
insert into #tablename select 'type2','group22','value222'
alter table #tablename add id uniqueidentifier
update #tablename set id = newid()
*/
select [type] as '#name',
(select
(select [column] from
(
select [group] as 'column', tbn1.type, tbn2.[group]
from #tablename tbn3 WHERE tbn3.type = tbn1.type and tbn2.[group] = tbn3.[group]
union
select [value], tbn1.type, tbn2.[group]
from #tablename tbn3 WHERE tbn3.type = tbn1.type and tbn2.[group] = tbn3.[group]
) as s
for xml path(''),type
)
from #tablename tbn2
where tbn2.type = tbn1.type
for xml path('row3'), type
)
from #tableName tbn1
GROUP BY [type]
for xml path('variable'), root('data')
gives you what you are asking for I, but elegant and tidy it is not.
The script below produces the desired format
<DATA>
<VARIABLE TYPE="Books">
<row TYPE="Books">
<GROUP>Hardback</GROUP>
<VALUE>56</VALUE>
</row>
<row TYPE="Books">
<GROUP>Softcover</GROUP>
<VALUE>34</VALUE>
</row>
</VARIABLE>
<VARIABLE TYPE="CDs">
<row TYPE="CDs">
<GROUP>Singles</GROUP>
<VALUE>45</VALUE>
</row>
<row TYPE="CDS">
<GROUP>Multis</GROUP>
<VALUE>78</VALUE>
</row>
</VARIABLE>
</DATA>
Invoke
DECLARE #tblItems table (
[TYPE] varchar(50)
,[GROUP] varchar(50)
,[VALUE] int
)
DECLARE #tblShredded table (
[TYPE] varchar(50)
,[XmlItem] xml
)
DECLARE #xmlGroupValueTuples xml
insert into #tblItems([TYPE],[GROUP],[VALUE]) values( 'Books','Hardback',56)
insert into #tblItems([TYPE],[GROUP],[VALUE]) values( 'Books','Softcover',34)
insert into #tblItems([TYPE],[GROUP],[VALUE]) values( 'CDs','Singles',45)
insert into #tblItems([TYPE],[GROUP],[VALUE]) values( 'CDS','Multis',78)
SET #xmlGroupValueTuples =
(
SELECT
"#TYPE" = [TYPE]
,[GROUP]
,[VALUE]
FROM #tblItems
FOR XML PATH('row'), root('Root')
)
INSERT #tblShredded([TYPE], XmlItem)
SELECT
[TYPE] = XmlItem.value('./row[1]/#TYPE', 'varchar(50)')
,XmlItem
FROM dbo.tvfShredGetOneColumnedTableOfXmlItems(#xmlGroupValueTuples)
SELECT
(
SELECT
VARIABLE =
(
SELECT
"#TYPE" = t.[TYPE]
,(
SELECT
tInner.XmlItem.query('./child::*')
FROM #tblShredded tInner
WHERE tInner.[TYPE] = t.[TYPE]
FOR XML PATH(''), ELEMENTS, type
)
FOR XML PATH('VARIABLE'),type
)
)
FROM #tblShredded t
GROUP BY
t.[TYPE]
FOR XML PATH(''), ROOT('DATA')
where
-- Example Inputs
/*
DECLARE #xmlListFormat xml
SET #xmlListFormat =
'
<XmlListRoot>
<Item>004421UB7</Item>
<Item>59020UH24</Item>
<Item>542514NA8</Item>
</XmlListRoot>
'
*/
-- =============================================
-- Author: 6eorge Jetson
-- Create date: 01/22/3003
-- Description: Shreds an input XML list conforming to the expected list schema
-- =============================================
CREATE FUNCTION [dbo].[tvfShredGetOneColumnedTableOfXmlItems] (#xmlListFormat xml)
RETURNS
#tblResults TABLE (XmlItem xml)
AS
BEGIN
INSERT #tblResults
SELECT
tblShredded.colXmlItem.query('.') as XmlItem
FROM
#xmlListFormat.nodes('/child::*/child::*') as tblShredded(colXmlItem)
RETURN
END