SQL Server 2005 "FOR XML PATH" Grouping - sql-server-2005

I'm trying to generate a XML document from the SQL Server 2005 database by using "FOR XML" construct.
There are two simple tables in the database with a one-to-many relationship:
1) Magazines
| Id | Number | Name |
----------------------------
| 53 | 0001 | Magazine 1 |
| 54 | 0002 | Magazine 2 |
| 55 | 0003 | Magazine 3 |
2) Articles
| Id | Title | MagazineId | Size |
--------------------------------------
| 1 | Article 1 | 53 | 1205 |
| 2 | Article 2 | 53 | 817 |
| 3 | Article 3 | 54 | 1570 |
| 4 | Article 4 | 54 | 2510 |
| 5 | Article 5 | 55 | 910 |
Let's assume I have to find all magazines that have an article with size greater than 1000
and to produce the following xml:
<Magazines>
<Magazine Id="53">
<Number>0001</Number>
<Articles>
<Article Id="1">
<Title>Article 1</Title>
<Size>1205</Size>
</Article>
</Articles>
</Magazine>
<Magazine Id="54">
<Number>0002</Number>
<Articles>
<Article Id="3">
<Title>Article 3</Title>
<Size>1570</Size>
</Article>
<Article Id="4">
<Title>Article 4</Title>
<Size>2510</Size>
</Article>
</Articles>
</Magazine>
</Magazines>
I'm trying to produce such xml by using a "PATH" mode:
SELECT Magazines.Id AS "#Id",
Magazines.Number AS "Number",
Articles.Id AS "Articles/Article/#Id",
Articles.Title AS "Articles/Article/Title",
Articles.Size AS "Articles/Article/Size"
FROM Magazines INNER JOIN Articles ON Magazines.Id = Articles.MagazineId
WHERE Articles.Size > 1000
FOR XML PATH('Magazine'), ROOT('Magazines'), TYPE
It will produce the following xml:
<Magazines>
<Magazine Id="53">
<Number>0001</Number>
<Articles>
<Article Id="1">
<Title>Article 1</Title>
<Size>1205</Size>
</Article>
</Articles>
</Magazine>
<Magazine Id="54">
<Number>0002</Number>
<Articles>
<Article Id="3">
<Title>Article 3</Title>
<Size>1570</Size>
</Article>
</Articles>
</Magazine>
<Magazine Id="54">
<Number>0002</Number>
<Articles>
<Article Id="4">
<Title>Article 4</Title>
<Size>2510</Size>
</Article>
</Articles>
</Magazine>
</Magazines>
So there are two elements for the Magazine with Id="54" (one for each article) and this is the problem.
I can rewrite the query using a subquery like this:
SELECT M.Id AS "#Id",
M.Number AS "Number",
(SELECT Articles.Id AS "#Id",
Articles.Title AS "Title",
Articles.Size AS "Size"
FROM Articles
WHERE Articles.MagazineId = M.Id
FOR XML PATH('Article'), ROOT('Articles'), TYPE
)
FROM Magazines AS M
FOR XML PATH('Magazine'), ROOT('Magazines'), TYPE
And this produce the following xml:
<Magazines>
<Magazine Id="53">
<Number>0001</Number>
<Articles>
<Article Id="1">
<Title>Article 1</Title>
<Size>1205</Size>
</Article>
<Article Id="2">
<Title>Article 2</Title>
<Size>817</Size>
</Article>
</Articles>
</Magazine>
<Magazine Id="54">
<Number>0002</Number>
<Articles>
<Article Id="3">
<Title>Article 3</Title>
<Size>1570</Size>
</Article>
<Article Id="4">
<Title>Article 4</Title>
<Size>2510</Size>
</Article>
</Articles>
</Magazine>
<Magazine Id="55">
<Number>0003</Number>
<Articles>
<Article Id="5">
<Title>Article 5</Title>
<Size>910</Size>
</Article>
</Articles>
</Magazine>
</Magazines>
But by using a subquery I can not filter magazines by articles columns (without complex additional queries).
The "FOR XML AUTO" mode is not suitable, because it is very simple and does not support some "PATH" features (like attributes using #, ROOT, etc..)
So, Is there any possibility in "PATH" mode to group inner table data like in "AUTO" mode?
Thank you!

Well, you can get one step closer by specifying the "size > 1000" inside your subquery:
SELECT M.Id AS "#Id",
M.Number AS "Number",
(SELECT Articles.Id AS "#Id",
Articles.Title AS "Title",
Articles.Size AS "Size"
FROM Articles
WHERE Articles.MagazineId = M.Id
AND Articles.Size > 1000
FOR XML PATH('Article'), ROOT('Articles'), TYPE
)
FROM Magazines AS M
FOR XML PATH('Magazine'), ROOT('Magazines'), TYPE
What you're missing now is the fact you'll still get magazines that have no articles with a size > 1000. You can eliminate those something like this:
SELECT M.Id AS "#Id",
M.Number AS "Number",
(SELECT Articles.Id AS "#Id",
Articles.Title AS "Title",
Articles.Size AS "Size"
FROM Articles
WHERE Articles.MagazineId = M.Id
AND Articles.Size > 1000
FOR XML PATH('Article'), ROOT('Articles'), TYPE
)
FROM Magazines AS M
WHERE EXISTS(SELECT * FROM Articles
WHERE Articles.MagazineId = M.Id
AND Articles.Size > 1000)
FOR XML PATH('Magazine'), ROOT('Magazines'), TYPE
(untested, I don't have a SQL server at hand right now).
Does that work for you? Does it give you the magazines and articles you're looking for?
Marc

Use FOR XML explicit. Is longest code to write but disappear the performance issues provocate by sub queries.

Related

How to parse xml data from a table and iterate each xml for showing inside perticular data

I have a table as following
table 1
=======
ID | XMLcol
----------------------------
1 <?xml version="...</users>
2 <?xml version="...</users>
In my each xml data in the XMLcol contains informations about severel users.
As a example firs row 1 contain three users. row 2 contains two users.
row 1 xml data
<?xml version="1.0"?>
<users>
<user>
<name>user1</name>
</user>
<user>
<name>user2</name>
</user>
<user>
<name>user3</name>
</user>
</users>
row 2 xml data
<?xml version="1.0"?>
<users>
<user>
<name>user4</name>
</user>
<user>
<name>user5</name>
</user>
</users>
I want to create a view by allow to repeating the ID and show the each user name in another column as follows.
so final view should be like this,
ID | name
----------------------------
1 user1
1 user2
1 user3
2 user4
2 user5
Is there anyway to do such a kind of thing?
Use an XMLTABLE:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table1 ( id, xml ) AS
SELECT 1, '<?xml version="1.0"?>
<users>
<user><name>user1</name></user>
<user><name>user2</name></user>
<user><name>user3</name></user>
</users>' FROM DUAL UNION ALL
SELECT 2, '<?xml version="1.0"?>
<users>
<user><name>user4</name></user>
<user><name>user5</name></user>
</users>' FROM DUAL
Query 1:
SELECT t.id,
x.name
FROM table1 t
CROSS JOIN
XMLTABLE(
'/users/user'
PASSING XMLType( t.xml )
COLUMNS name VARCHAR2(200) PATH 'name'
) x
Results:
| ID | NAME |
|----|-------|
| 1 | user1 |
| 1 | user2 |
| 1 | user3 |
| 2 | user4 |
| 2 | user5 |

SQL query hierarchical XML with multiple sub-elements

I'm using Microsoft SQL server.
I have a simple hierarchy like a directional graph in Xml:
DECLARE #XML as XML = CAST(
'<ROOT>
<NODE NODE_ID="1">
<EDGE>2</EDGE>
<EDGE>3</EDGE>
<EDGE>4</EDGE>
</NODE>
<NODE NODE_ID="2">
<EDGE>1</EDGE>
<EDGE>3</EDGE>
</NODE>
</ROOT>' AS XML);
My desired output would be a table like this:
SOURCE_NODE_ID | DEST_NODE_ID
1 | 2
1 | 3
1 | 4
2 | 1
2 | 3
A query like this:
SELECT B.value('data(#NODE_ID)','int') AS SOURCE_NODE_ID,
A.B.value('(EDGE/text())[1]', 'int') AS DEST_NODE_ID
FROM #XML.nodes('/ROOT/NODE') AS A(B);
Only returns the first edge:
SOURCE_NODE_ID | DEST_NODE_ID
1 | 2
2 | 1
This one does a little better:
SELECT B.value('data(#NODE_ID)','int') AS SOURCE_NODE_ID,
B.query('EDGE').value('.', 'int') AS DEST_NODE_ID
FROM #XML.nodes('/ROOT/NODE') AS A(B);
Only it concatenates all edges into one cell:
SOURCE_NODE_ID | DEST_NODE_ID
1 | 234
2 | 13
How can I get my desired result? Should I join with an inner query or something? Probably I'm making it too complicated, surely there is a simple solution to this?
Try it like this
As there are many NODE elements, you need to call .nodes() for them. As there are many EDGE elements nested, you need to call CROSS APPLY .nodes() for them.
The rest is easy...
DECLARE #XML as XML = CAST(
'<ROOT>
<NODE NODE_ID="1">
<EDGE>2</EDGE>
<EDGE>3</EDGE>
<EDGE>4</EDGE>
</NODE>
<NODE NODE_ID="2">
<EDGE>1</EDGE>
<EDGE>3</EDGE>
</NODE>
</ROOT>' AS XML);
SELECT Nd.value('#NODE_ID','INT') AS SOURCE_NODE_ID
,Edg.value('.','INT') AS DEST_NODE_ID
FROM #XML.nodes('/ROOT/NODE') AS A(Nd)
CROSS APPLY A.Nd.nodes('EDGE') AS B(Edg)
The result
SOURCE_NODE_ID DEST_NODE_ID
1 2
1 3
1 4
2 1
2 3

How to extract data in tabular format from XML field in Oracle database?

I'm trying to extract data in tabular format from an XML field in an Oracle database.
Please see sample xml field below:
<?xml version='1.0' encoding='UTF-8'?>
<root>
<element1>
<Header Client_ID="100" Sent_date_time="2015-03-02T9:30:43.808-06:00"/>
<element2>
<element3 UnitPrice="3.2" ItemID="njh1"/>
<element3 UnitPrice="4.1" ItemID="ole5"/>
<element3 UnitPrice="4.6" ItemID="usd3"/>
<element3 UnitPrice="8.2" ItemID="eor9"/>
<element3 UnitPrice="2.9" ItemID="abc8"/>
<element3 UnitPrice="5.1" ItemID="gfd3"/>
<element3 UnitPrice="4.9" ItemID="kdu0"/>
<element3 UnitPrice="6.1" ItemID="uso8"/>
</element2>
</element1>
</root>
My aim is to query the itemID and UnitPrice fields from the xml field above in the format shown below:
ItemID UnitPrice
njh1 3.2
ole5 4.1
usd3 4.6
eor9 8.2
abc8 2.9
gfd3 5.1
kdu0 4.9
uso8 6.1
I'm fairly new to querying data from xml fields. I have tried using the getStringVal, but all I get is one single long string.
Please advise on a solution. Please note that I don't have dba rights to this database.
Thank you
You have to use XMLTABLE function for this.
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table xml_test(
xml_col varchar2(2000)
);
insert into xml_test values(
'<?xml version=''1.0'' encoding=''UTF-8''?>
<root>
<element1>
<Header Client_ID="100" Sent_date_time="2015-03-02T9:30:43.808-06:00"/>
<element2>
<element3 UnitPrice="3.2" ItemID="njh1"/>
<element3 UnitPrice="4.1" ItemID="ole5"/>
<element3 UnitPrice="4.6" ItemID="usd3"/>
<element3 UnitPrice="8.2" ItemID="eor9"/>
<element3 UnitPrice="2.9" ItemID="abc8"/>
<element3 UnitPrice="5.1" ItemID="gfd3"/>
<element3 UnitPrice="4.9" ItemID="kdu0"/>
<element3 UnitPrice="6.1" ItemID="uso8"/>
</element2>
</element1>
</root>'
);
Query:
select cols.ItemID, cols.UnitPrice
from xml_test x,
xmltable('root/element1/element2/element3'
passing xmltype(x.xml_col)
columns ItemID varchar2(10) path '#ItemID',
UnitPrice varchar2(10) path '#UnitPrice'
) cols;
Results:
| ITEMID | UNITPRICE |
|--------|-----------|
| njh1 | 3.2 |
| ole5 | 4.1 |
| usd3 | 4.6 |
| eor9 | 8.2 |
| abc8 | 2.9 |
| gfd3 | 5.1 |
| kdu0 | 4.9 |
| uso8 | 6.1 |

How to select only one field where id number is the same sql

I have a table that is as follows
bugID | name | description | comment
----------------------------------------------------------------
1 | bug1 | first bug | <p></p>
1 | bug1 | first bug | <p>this is the first bug</p>
1 | bug1 | first bug | <p>this needs fixing</p>
2 | bug2 | second bug | <p>this is the second bug</p>
3 | bug3 | third bug | <p>bug number 3</p>
I want to get the records back as follows if I do a select * from this table
1, bug1, first bug, <p></p>, <p>this is the first bug</p>, <p>this needs fixing</p>
2, bug2, second bug, <p>this is the second bug</p>
3, bug3, third bug, <p>bug number 3</p>
Is there a way I can do this in SQL?
Assuming this is SQL Server
SELECT bugID,
name,
description,
STUFF(( SELECT ',' + comment
FROM Table1 I Where I.bugID= O.bugID
FOR
XML PATH('')
), 1, 1, '')
FROM Table1 O
GROUP BY bugID,
name,
description

xQuery and T-SQL to Extract Data

Got some basic XML as a XML datatype within SQL 2005. One record/row looks like this
<doc>
<level1>
<level2>
<name>James</name>
<age>12</age>
</level2>
<level2>
<name>John</name>
<age>23</age>
</level2>
</level1>
</doc>
When I perform some basic T_SQL
SELECT TOP 1
DocumentXML.query('data(//doc/name)'),
DocumentXML.query('data(//doc/age)')
FROM [DBNAME].[dbo].[TBLNAME]
I get
ID | Name | Age
----------------------
1 | JamesJohn | 1223
How do I re-write the T-SQL so it displays as
ID | Name | Age
--------------------
1 | James | 12
2 | John | 23
Your example doesn't work for me; the second level2 opens with </level2>. And //doc/name doesn't exist; might be //doc/level1/level2/name.
Here's an example of how to retrieve a rowset from an XML:
declare #t table (id int identity, doc xml)
insert #t (doc) values (
'<doc>
<level1>
<level2>
<name>James</name>
<age>12</age>
</level2>
<level2>
<name>John</name>
<age>23</age>
</level2>
</level1>
</doc>')
SELECT x.a.value('(name)[1]','varchar(50)') as col1
, x.a.value('(age)[1]','varchar(50)') as col2
FROM #t t
cross apply
t.doc.nodes('//level2') x(a)
This prints:
col1 col2
James 12
John 23