MS SQL 2005 Table to XML - sql

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

Related

Sql2012 FOR XML Inconsistent "empty element" Behavior

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');

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.

SQL Server Populate XML Variable from VARCHAR rows

I have a single column table in SQL Server.
This column hold each row of an XML document.
Sample table :
Column
---------------
Row1: <ROOT>
Row2: <Name>name1</Name>
Row3: </ROOT>
Column data type is nvarchar(max)
I want to do:
DECLARE #RES_XML XML
SET #XML = Set from table above
How can I sum up all rows of the table above and populate #RES_XML?
Note: when concatenated; all data in the table exceeds nvarchar(max) limit.
In general, easiest way to concatenate values into variable is
declare #res nvarchar(max)
select #res = isnull(#res, '') + [Column]
from <table above>
select cast(#res as xml)
Of course, order of concatenation in this query is not defined (but there is a trick to check if order is maintained)
I am as surprised by your exceeding the limit of nvarchar(max) columns as Marc is, but maybe you have column limit settings in your database set-up that you can't change. Here's how I would do it, and I'm assuming there is some column that let's you order the tags in the appropriate order, you cannot rely on the order in which the database decided to store and retrieve the rows.
declare #t table (
id int,
x nvarchar(max)
)
insert into #t
select 1, '<ROOT>' union all
select 2, '<Name>name1</Name>' union all
select 3, '</ROOT>'
DECLARE #RES_XML XML
select #RES_XML = cast((
select x
from #t
order by id
for xml path(''), type
).value('.', 'nvarchar(max)') as xml)

Xml select query xpath is slow

My XML structure:
<Items>
<Item>
<guid>FC550573-7171-997F-752D-8D65590CBFD6</guid>
<Objects>
<Object>
<type>0</type>
<guid>E10D9DA9-2C8D-8024-2F07-DF21395811BF</guid>
</Object>
<Object>
<type>0</type>
<guid>D8338400-35C7-781E-A039-C0FDDF80714A</guid>
</Object>
</Objects>
</Item>
</Items>
When filling the Objects Table:
CREATE TABLE [dbo].[Objects](
[item_guid] [varchar](36) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[type] [int] NOT NULL,
[guid] [varchar](36) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
) ON [PRIMARY]
Using the Query:
INSERT INTO [dbname].[dbo].[Objects]
([item_guid]
,[type]
,[guid])
SELECT
X.source.query('../../guid').value('.','VARCHAR(36)') as item_guid,
X.source.query('type').value('.','INT') as type,
X.source.query('guid').value('.','VARCHAR(36)') as guid
FROM(
Select xmldata from XmlFiles where fullpath=#fp
) AS T(x)
CROSS APPLY x.nodes('Items/Item/Objects/Object') As X(source)
This line is making the query VERY slow:
X.source.query('../../guid').value('.','VARCHAR(36)') as item_guid
What is the proper approach here?
Using /text() to get the value is good for performance on untyped XML. It can also be bad to use the parent axis ../.. (as #marc_s suggested).
Here is a version with a extra cross apply and /text() to get the values.
Try this:
select T2.N.value('(guid/text())[1]', 'uniqueidentifier') as item_guid,
T3.N.value('(type/text())[1]', 'int') as type,
T3.N.value('(guid/text())[1]', 'uniqueidentifier') as guid
from (SELECT xmldata FROM dbo.XmlFiles WHERE fullpath = #fp) as T1(N)
cross apply T1.N.nodes('Items/Item') as T2(N)
cross apply T2.N.nodes('Objects/Object') as T3(N)
You have to be the judge which query is the fastest for you.
I just want to add, in case anybody else runs across this, that adding the following option makes a huge difference.
OPTION (OPTIMIZE FOR (#testXml = NULL))
If you'd like to test this yourself, here is a short test script I was running. Just look at the estimated subtree cost between these.
declare #testXml xml set #testXml = '<filters><filter name="test name" type="GREATERTHAN">1</filter><filter name="CLAIMID" type="GREATERTHAN">1</filter></filters>'
select x.value('#name','nvarchar(100) ') filtername,
x.value('.','nvarchar(200)')filtervalue,
x.value('#type','nvarchar(50) ') filtertype
from #testXml.nodes('/filters/filter') as ref(x)
--vs...
select x.value('#name','nvarchar(100) ') filtername,
x.value('.','nvarchar(200)')filtervalue,
x.value('#type','nvarchar(50) ') filtertype
from #testXml.nodes('/filters/filter') as ref(x)
OPTION (OPTIMIZE FOR (#testXml = NULL))
Try this,
We will create a temp table variable for store this xml values & insert to corresponding table Objects
//..Xml value to temp variable
Declare #x xml ='<Items><Item><guid>FC550573-7171-997F-752D-8D65590CBFD6</guid><Objects><Object>
<type>0</type><guid>E10D9DA9-2C8D-8024-2F07-DF21395811BF</guid></Object><Object>
<type>0</type><guid>D8338400-35C7-781E-A039-C0FDDF80714A</guid></Object></Objects>
</Item></Items>';
Declare #Temp_Tbl table (RowId int identity, item_guid nvarchar(36), [type] int, [guid] nvarchar(36));
Insert into #Temp_Tbl SELECT #x.value('(/Items/Item/guid)[1]', 'nvarchar(36)'),
Cont.value('(type)[1]', 'int'), Cont.value('(guid)[1]', 'nvarchar(36)')
FROM #x.nodes('/Items/Item/Objects/Object') AS Obj(Cont);
INSERT INTO [dbo].[Objects] Select item_guid,[type],[guid] from #Temp_Tbl;

SQL: How can I get the value of an attribute in XML datatype?

I have the following xml in my database:
<email>
<account language="en" ... />
</email>
I am using something like this now: but still have to find the attribute value.
SELECT convert(xml,m.Body).query('/Email/Account')
FROM Mail
How can I get the value of the language attribute in my select statement with SQL?
Use XQuery:
declare #xml xml =
'<email>
<account language="en" />
</email>'
select #xml.value('(/email/account/#language)[1]', 'nvarchar(max)')
declare #t table (m xml)
insert #t values
('<email><account language="en" /></email>'),
('<email><account language="fr" /></email>')
select m.value('(/email/account/#language)[1]', 'nvarchar(max)')
from #t
Output:
en
fr
This should work:
DECLARE #xml XML
SET #xml = N'<email><account language="en" /></email>'
SELECT T.C.value('#language', 'nvarchar(100)')
FROM #xml.nodes('email/account') T(C)
It depends a lot on how you're querying the document. You can do this, though:
CREATE TABLE #example (
document NText
);
INSERT INTO #example (document)
SELECT N'<email><account language="en" /></email>';
WITH XmlExample AS (
SELECT CONVERT(XML, document) doc
FROM #example
)
SELECT
C.value('#language', 'VarChar(2)') lang
FROM XmlExample CROSS APPLY
XmlExample.doc.nodes('//account') X(C);
DROP TABLE #example;
EDIT after changes to your question.
if the xml data is stored in sql server as a string column then use cast
select cast(your_field as XML)
.value('(/email/account/#language)[1]', 'varchar(20)')
from your_table