UPDATE: I've discovered there is a Microsoft Connect item raised for this issue here
When using FOR XML PATH and WITH XMLNAMESPACES to declare a default namespace, I will get the namespace declaration duplicated in any top level nodes for nested queries that use FOR XML, I've stumbled across a few solutions on-line, but I'm not totally convinced...
Here's an Complete Example
/*
drop table t1
drop table t2
*/
create table t1 ( c1 int, c2 varchar(50))
create table t2 ( c1 int, c2 int, c3 varchar(50))
insert t1 values
(1, 'Mouse'),
(2, 'Chicken'),
(3, 'Snake');
insert t2 values
(1, 1, 'Front Right'),
(2, 1, 'Front Left'),
(3, 1, 'Back Right'),
(4, 1, 'Back Left'),
(5, 2, 'Right'),
(6, 2, 'Left')
;with XmlNamespaces( default 'uri:animal')
select
a.c2 as "#species"
, (select l.c3 as "text()"
from t2 l where l.c2 = a.c1
for xml path('leg'), type) as "legs"
from t1 a
for xml path('animal'), root('zoo')
What's the best solution?
After hours of desperation and hundreds of trials & errors, I've come up with the solution below.
I had the same issue, when I wanted just one xmlns attribute, on the root node only. But I also had a very difficult query with lot's of subqueries and FOR XML EXPLICIT method alone was just too cumbersome. So yes, I wanted the convenience of FOR XML PATH in the subqueries and also to set my own xmlns.
I kindly borrowed the code of 8kb's answer, because it was so nice. I tweaked it a bit for better understanding. Here is the code:
DECLARE #Order TABLE (OrderID INT, OrderDate DATETIME)
DECLARE #OrderDetail TABLE (OrderID INT, ItemID VARCHAR(1), Name VARCHAR(50), Qty INT)
INSERT #Order VALUES (1, '2010-01-01'), (2, '2010-01-02')
INSERT #OrderDetail VALUES (1, 'A', 'Drink', 5),
(1, 'B', 'Cup', 2),
(2, 'A', 'Drink', 2),
(2, 'C', 'Straw', 1),
(2, 'D', 'Napkin', 1)
-- Your ordinary FOR XML PATH query
DECLARE #xml XML = (SELECT OrderID AS "#OrderID",
(SELECT ItemID AS "#ItemID",
Name AS "data()"
FROM #OrderDetail
WHERE OrderID = o.OrderID
FOR XML PATH ('Item'), TYPE)
FROM #Order o
FOR XML PATH ('Order'), ROOT('dummyTag'), TYPE)
-- Magic happens here!
SELECT 1 AS Tag
,NULL AS Parent
,#xml AS [xml!1!!xmltext]
,'http://test.com/order' AS [xml!1!xmlns]
FOR XML EXPLICIT
Result:
<xml xmlns="http://test.com/order">
<Order OrderID="1">
<Item ItemID="A">Drink</Item>
<Item ItemID="B">Cup</Item>
</Order>
<Order OrderID="2">
<Item ItemID="A">Drink</Item>
<Item ItemID="C">Straw</Item>
<Item ItemID="D">Napkin</Item>
</Order>
</xml>
If you selected #xml alone, you would see that it contains root node dummyTag. We don't need it, so we remove it by using directive xmltext in FOR XML EXPLICIT query:
,#xml AS [xml!1!!xmltext]
Although the explanation in MSDN sounds more sophisticated, but practically it tells the parser to select the contents of XML root node.
Not sure how fast the query is, yet currently I am relaxing and drinking Scotch like a gent while peacefully looking at the code...
If I have understood correctly, you are referring to the behavior that you might see in a query like this:
DECLARE #Order TABLE (
OrderID INT,
OrderDate DATETIME)
DECLARE #OrderDetail TABLE (
OrderID INT,
ItemID VARCHAR(1),
ItemName VARCHAR(50),
Qty INT)
INSERT #Order
VALUES
(1, '2010-01-01'),
(2, '2010-01-02')
INSERT #OrderDetail
VALUES
(1, 'A', 'Drink', 5),
(1, 'B', 'Cup', 2),
(2, 'A', 'Drink', 2),
(2, 'C', 'Straw', 1),
(2, 'D', 'Napkin', 1)
;WITH XMLNAMESPACES('http://test.com/order' AS od)
SELECT
OrderID AS "#OrderID",
(SELECT
ItemID AS "#od:ItemID",
ItemName AS "data()"
FROM #OrderDetail
WHERE OrderID = o.OrderID
FOR XML PATH ('od.Item'), TYPE)
FROM #Order o
FOR XML PATH ('od.Order'), TYPE, ROOT('xml')
Which gives the following results:
<xml xmlns:od="http://test.com/order">
<od.Order OrderID="1">
<od.Item xmlns:od="http://test.com/order" od:ItemID="A">Drink</od.Item>
<od.Item xmlns:od="http://test.com/order" od:ItemID="B">Cup</od.Item>
</od.Order>
<od.Order OrderID="2">
<od.Item xmlns:od="http://test.com/order" od:ItemID="A">Drink</od.Item>
<od.Item xmlns:od="http://test.com/order" od:ItemID="C">Straw</od.Item>
<od.Item xmlns:od="http://test.com/order" od:ItemID="D">Napkin</od.Item>
</od.Order>
</xml>
As you said, the namespace is repeated in the results of the subqueries.
This behavior is a feature according to a conversation on devnetnewsgroup (website now defunct) although there is the option to vote on changing it.
My proposed solution is to revert back to FOR XML EXPLICIT:
SELECT
1 AS Tag,
NULL AS Parent,
'http://test.com/order' AS [xml!1!xmlns:od],
NULL AS [od:Order!2],
NULL AS [od:Order!2!OrderID],
NULL AS [od:Item!3],
NULL AS [od:Item!3!ItemID]
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
'http://test.com/order' AS [xml!1!xmlns:od],
NULL AS [od:Order!2],
OrderID AS [od:Order!2!OrderID],
NULL AS [od:Item!3],
NULL [od:Item!3!ItemID]
FROM #Order
UNION ALL
SELECT
3 AS Tag,
2 AS Parent,
'http://test.com/order' AS [xml!1!xmlns:od],
NULL AS [od:Order!2],
o.OrderID AS [od:Order!2!OrderID],
d.ItemName AS [od:Item!3],
d.ItemID AS [od:Item!3!ItemID]
FROM #Order o INNER JOIN #OrderDetail d ON o.OrderID = d.OrderID
ORDER BY [od:Order!2!OrderID], [od:Item!3!ItemID]
FOR XML EXPLICIT
And see these results:
<xml xmlns:od="http://test.com/order">
<od:Order OrderID="1">
<od:Item ItemID="A">Drink</od:Item>
<od:Item ItemID="B">Cup</od:Item>
</od:Order>
<od:Order OrderID="2">
<od:Item ItemID="A">Drink</od:Item>
<od:Item ItemID="C">Straw</od:Item>
<od:Item ItemID="D">Napkin</od:Item>
</od:Order>
</xml>
An alternative solution I've seen is to add the XMLNAMESPACES declaration after building the xml into a temporary variable:
declare #xml as xml;
select #xml = (
select
a.c2 as "#species"
, (select l.c3 as "text()"
from t2 l where l.c2 = a.c1
for xml path('leg'), type) as "legs"
from t1 a
for xml path('animal'))
;with XmlNamespaces( 'uri:animal' as an)
select #xml for xml path('') , root('zoo');
The problem here is compounded by the fact that you cannot directly declare the namespaces manually when using XML PATH. SQL Server will disallow any attribute names beginning with 'xmlns' and any tag names with colons in them.
Rather than having to resort to using the relatively unfriendly XML EXPLICIT, I got around the problem by first generating XML with 'cloaked' namespace definitions and references, then doing string replaces as follows ...
DECLARE #Order TABLE (
OrderID INT,
OrderDate DATETIME)
DECLARE #OrderDetail TABLE (
OrderID INT,
ItemID VARCHAR(1),
ItemName VARCHAR(50),
Qty INT)
INSERT #Order
VALUES
(1, '2010-01-01'),
(2, '2010-01-02')
INSERT #OrderDetail
VALUES
(1, 'A', 'Drink', 5),
(1, 'B', 'Cup', 2),
(2, 'A', 'Drink', 2),
(2, 'C', 'Straw', 1),
(2, 'D', 'Napkin', 1)
declare #xml xml
set #xml = (SELECT
'http://test.com/order' as "#xxmlns..od", -- 'Cloaked' namespace def
(SELECT OrderID AS "#OrderID",
(SELECT
ItemID AS "#od..ItemID",
ItemName AS "data()"
FROM #OrderDetail
WHERE OrderID = o.OrderID
FOR XML PATH ('od..Item'), TYPE)
FROM #Order o
FOR XML PATH ('od..Order'), TYPE)
FOR XML PATH('xml'))
set #xml = cast(replace(replace(cast(#xml as nvarchar(max)), 'xxmlns', 'xmlns'),'..',':') as xml)
select #xml
A few things to point out:
I'm using 'xxmlns' as my cloaked version of 'xmlns' and '..' to stand in for ':'. This might not work for you if you're likely to have '..' as part of text values - you can substitute this with something else as long as you pick something that makes a valid XML identifier.
Since we want the xmlns definition at the top level, we cannot use the 'ROOT' option to XML PATH - instead I needed to add an another outer level to the subselect structure to achieve this.
I'm bit confusing about all these explanation while declaring a "xmlns:animals" manually is doing the job :
Here an example i wrote to generate Open graph meta data
DECLARE #l_xml as XML;
SELECT #l_xml =
(
SELECT 'http://ogp.me/ns# fb: http://ogp.me/ns/fb# scanilike: http://ogp.me/ns/fb/scanilike#' as 'xmlns:og',
(SELECT
(SELECT 'og:title' as 'property', title as 'content' for xml raw('meta'), TYPE),
(SELECT 'og:type' as 'property', OpenGraphWebMetadataTypes.name as 'content' for xml raw('meta'), TYPE),
(SELECT 'og:image' as 'property', image as 'content' for xml raw('meta'), TYPE),
(SELECT 'og:url' as 'property', url as 'content' for xml raw('meta'), TYPE),
(SELECT 'og:description' as 'property', description as 'content' for xml raw('meta'), TYPE),
(SELECT 'og:site_name' as 'property', siteName as 'content' for xml raw('meta'), TYPE),
(SELECT 'og:appId' as 'property', appId as 'content' for xml raw('meta'), TYPE)
FROM OpenGraphWebMetaDatas INNER JOIN OpenGraphWebMetadataTypes ON OpenGraphWebMetaDatas.type = OpenGraphWebMetadataTypes.id WHERE THING_KEY = #p_index
for xml path('header'), TYPE),
(SELECT '' as 'body' for xml path(''), TYPE)
for xml raw('html'), TYPE
)
RETURN #l_xml
returning the expected result
<html xmlns:og="http://ogp.me/ns# fb: http://ogp.me/ns/fb# scanilike: http://ogp.me/ns/fb/scanilike#">
<header>
<meta property="og:title" content="The First object"/>
<meta property="og:type" content="scanilike:tag"/>
<meta property="og:image" content="http://www.mygeolive.com/images/facebook/facebook-logo.jpg"/>
<meta property="og:url" content="http://www.scanilike.com/opengraph?id=1"/>
<meta property="og:description" content="This is the very first object created using the IOThing & ScanILike software. We keep it in file for history purpose. "/>
<meta property="og:site_name" content="http://www.scanilike.com"/>
<meta property="og:appId" content="200270673369521"/>
</header>
<body/>
</html>
hope this will help people are searching the web for similar issue. ;-)
It would be really nice if FOR XML PATH actually worked more cleanly. Reworking your original example with #table variables:
declare #t1 table (c1 int, c2 varchar(50));
declare #t2 table (c1 int, c2 int, c3 varchar(50));
insert #t1 values
(1, 'Mouse'),
(2, 'Chicken'),
(3, 'Snake');
insert #t2 values
(1, 1, 'Front Right'),
(2, 1, 'Front Left'),
(3, 1, 'Back Right'),
(4, 1, 'Back Left'),
(5, 2, 'Right'),
(6, 2, 'Left');
;with xmlnamespaces( default 'uri:animal')
select a.c2 as "#species",
(
select l.c3 as "text()"
from #t2 l
where l.c2 = a.c1
for xml path('leg'), type
) as "legs"
from #t1 a
for xml path('animal'), root('zoo');
Yields the problem XML with repeated namespace declarations:
<zoo xmlns="uri:animal">
<animal species="Mouse">
<legs>
<leg xmlns="uri:animal">Front Right</leg>
<leg xmlns="uri:animal">Front Left</leg>
<leg xmlns="uri:animal">Back Right</leg>
<leg xmlns="uri:animal">Back Left</leg>
</legs>
</animal>
<animal species="Chicken">
<legs>
<leg xmlns="uri:animal">Right</leg>
<leg xmlns="uri:animal">Left</leg>
</legs>
</animal>
<animal species="Snake" />
</zoo>
You can migrate elements between namespaces using XQuery with wildcard namespace matching (that is, *:elementName), as below, but it can be quite cumbersome for complex XML:
;with xmlnamespaces( default 'http://tempuri.org/this/namespace/is/meaningless' )
select (
select a.c2 as "#species",
(
select l.c3 as "text()"
from #t2 l
where l.c2 = a.c1
for xml path('leg'), type
) as "legs"
from #t1 a
for xml path('animal'), root('zoo'), type
).query('declare default element namespace "uri:animal";
<zoo>
{ for $a in *:zoo/*:animal return
<animal>
{attribute species {$a/#species}}
{ for $l in $a/*:legs return
<legs>
{ for $m in $l/*:leg return
<leg>{ $m/text() }</leg>
}</legs>
}</animal>
}</zoo>');
Which yields your desired result:
<zoo xmlns="uri:animal">
<animal species="Mouse">
<legs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</legs>
</animal>
<animal species="Chicken">
<legs>
<leg>Right</leg>
<leg>Left</leg>
</legs>
</animal>
<animal species="Snake" />
</zoo>
Related
I've been reading this article:
https://www.red-gate.com/simple-talk/sql/database-administration/manipulating-xml-data-in-sql-server/
I found what I need, but the only difference in my situation I need to use FOR XML PATH(...) and not ROOT(...) instead of AUTO mode.
I have a XML file that uses columns of the table as a elements. The question that I'm trying to resolve:
1) how to produce a single XML file per each record in the select statement;
2) save each xml file with a unique name based on rowId on a shared server
Note: hardcoding values for each XML row is not an option as my output contains thousands of rows.
The problem here is that FOR XML and XML data type are not allowed in the Cursor statement.
CREATE TABLE #T1
(
ID INT NOT NULL,
LName NVARCHAR(30) NULL,
FName NVARCHAR(30) NULL,
Comments NVARCHAR(MAX) NULL
);
GO
INSERT INTO #T1 (ID, LName, FName, Comments)
VALUES
(1, 'JONOTHAN', 'SMITH', 'is the best friend ever'),
(2, 'ROGER', 'SHU`LTS', 'is the boss!'),
(3, 'Jeremy', 'Deimer', 'is the a good drama actor'),
(4, 'Alexandra', 'Norusis', 'is the smart feminist');
GO
SELECT
t.ID,
t.LName,
t.FName,
t.Comments
FROM #T1 t
FOR XML PATH(''), ROOT ('body');
Below is the current output of the XML file format:
<body>
<ID>1</ID>
<LName>JONOTHAN</LName>
<FName>SMITH</FName>
<Comments>is the best friend ever</Comments>
<ID>2</ID>
<LName>ROGER</LName>
<FName>SHU`LTS</FName>
<Comments>is the boss!</Comments>
<ID>3</ID>
<LName>Jeremy</LName>
<FName>Deimer</FName>
<Comments>is the a good drama actor</Comments>
<ID>4</ID>
<LName>Alexandra</LName>
<FName>Norusis</FName>
<Comments>is the smart feminist</Comments>
</body>
Below is the desired out (based on a record with ID = 1)
<body>
<ID>1</ID>
<LName>JONOTHAN</LName>
<FName>SMITH</FName>
<Comments>is the best friend ever</Comments>
<body>
I think it's this what you need:
DECLARE #tbl TABLE
(
ID INT NOT NULL,
LName NVARCHAR(30) NULL,
FName NVARCHAR(30) NULL,
Comments NVARCHAR(MAX) NULL
);
INSERT INTO #tbl (ID, LName, FName, Comments)
VALUES
(1, 'JONOTHAN', 'SMITH', 'is the best friend ever'),
(2, 'ROGER', 'SHU`LTS', 'is the boss!'),
(3, 'Jeremy', 'Deimer', 'is the a good drama actor'),
(4, 'Alexandra', 'Norusis', 'is the smart feminist');
--use this for your CURSOR
SELECT ID
,(SELECT t1.* FOR XML PATH('body'),TYPE) AS TheRowAsXml
FROM #tbl AS t1
The result
ID TheRowAsXml
1 <body><ID>1</ID><LName>JONOTHAN</LName><FName>SMITH</FName><Comments>is the best friend ever</Comments></body>
2 <body><ID>2</ID><LName>ROGER</LName><FName>SHU`LTS</FName><Comments>is the boss!</Comments></body>
3 <body><ID>3</ID><LName>Jeremy</LName><FName>Deimer</FName><Comments>is the a good drama actor</Comments></body>
4 <body><ID>4</ID><LName>Alexandra</LName><FName>Norusis</FName><Comments>is the smart feminist</Comments></body>
UPDATE Your example using a CURSOR
DECLARE #tbl TABLE
(
ID INT NOT NULL,
LName NVARCHAR(30) NULL,
FName NVARCHAR(30) NULL,
Comments NVARCHAR(MAX) NULL
);
INSERT INTO #tbl (ID, LName, FName, Comments)
VALUES
(1, 'JONOTHAN', 'SMITH', 'is the best friend ever'),
(2, 'ROGER', 'SHU`LTS', 'is the boss!'),
(3, 'Jeremy', 'Deimer', 'is the a good drama actor'),
(4, 'Alexandra', 'Norusis', 'is the smart feminist');
DECLARE #ID INT;
DECLARE #xml XML;
DECLARE #BusinessCursor as CURSOR;
SET #BusinessCursor = CURSOR FOR
SELECT ID
,(
SELECT t1.*
FOR XML PATH(''),ROOT('body'),TYPE
) AS TheXml
FROM #tbl AS t1
OPEN #BusinessCursor;
FETCH NEXT FROM #BusinessCursor INTO #ID, #xml;
WHILE ##FETCH_STATUS = 0
BEGIN
--Do something with the values
PRINT 'ID: ' + CAST(#ID AS VARCHAR(10));
PRINT 'XML: ' + CAST(#xml AS NVARCHAR(MAX));
--Here you can build your BCP command.
--Use the ID or any other information to build the file's name and save the XML out (use -w -T)
FETCH NEXT FROM #BusinessCursor INTO #ID, #xml;
END
CLOSE #BusinessCursor;
DEALLOCATE #BusinessCursor;
SELECT
ID,
LName,
FName,
Comments
FROM #T1
FOR XML PATH('body');
I think this is your expected result:
<body>
<ID>1</ID>
<LName>JONOTHAN</LName>
<FName>SMITH</FName>
<Comments>is the best friend ever</Comments>
</body>
<body>
<ID>2</ID>
<LName>ROGER</LName>
<FName>SHU`LTS</FName>
<Comments>is the boss!</Comments>
</body>
<body>
<ID>3</ID>
<LName>Jeremy</LName>
<FName>Deimer</FName>
<Comments>is the a good drama actor</Comments>
</body>
<body>
<ID>4</ID>
<LName>Alexandra</LName>
<FName>Norusis</FName>
<Comments>is the smart feminist</Comments>
</body>
DECLARE #tbl TABLE
(
ID INT NOT NULL,
LName NVARCHAR(30) NULL,
FName NVARCHAR(30) NULL,
Comments NVARCHAR(MAX) NULL
);
INSERT INTO #tbl (ID, LName, FName, Comments)
VALUES
(1, 'JONOTHAN', 'SMITH', 'is the best friend ever'),
(2, 'ROGER', 'SHU`LTS', 'is the boss!'),
(3, 'Jeremy', 'Deimer', 'is the a good drama actor'),
(4, 'Alexandra', 'Norusis', 'is the smart feminist');
DECLARE #xml XML
DECLARE #BusinessCursor as CURSOR;
SET #BusinessCursor = CURSOR FOR
SELECT
-- ID
-- ,
(
SELECT *
FROM #tbl AS t2
-- WHERE t1.ID=t2.ID
FOR XML PATH(''),ROOT('body'),TYPE
) AS TheRowAsXml
FROM #tbl AS t1
OPEN #BusinessCursor;
FETCH NEXT FROM #BusinessCursor INTO #xml;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT (
SELECT *
FROM #tbl AS t2
WHERE t1.ID=t2.ID
FOR XML PATH(''),ROOT('body'), TYPE
) AS TheRowAsXml
FROM #tbl AS t1
FETCH NEXT FROM #BusinessCursor INTO #xml;
END
CLOSE #BusinessCursor;
DEALLOCATE #BusinessCursor;
There is a solution for remove namespace here!, but I need it works in SQL function, I'm getting this error:
The FOR XML clause is 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 syntax and apply FOR XML on top of it.
Can somebody help me?
Thanks Xtyan
From comment: This is the needed XML-output
<zoo xmlns:an="uri:animal">
<an:animal species="Mouse">
<an:legs>
<an:leg>Front Right</an:leg>
<an:leg>Front Left</an:leg>
<an:leg>Back Right</an:leg>
<an:leg>Back Left</an:leg>
</an:legs>
</an:animal>
<an:animal species="Chicken">
<an:legs>
<an:leg>Right</an:leg>
<an:leg>Left</an:leg>
</an:legs>
</an:animal>
<an:animal species="Snake" />
</zoo>
You will not like this probably...
I did not find a generic solution for this... It is no problem to create this with repeated namespaces. This is not wrong, but annoying:
declare #xml as xml;
WITH XMLNAMESPACES('uri:animal' as an)
select #xml = (
select
a.c2 as "#an:species"
, (
select l.c3 as "text()"
from t2 l where l.c2 = a.c1
for xml path('an:leg'), type
) as "an:legs"
from t1 a
for xml path('an:animal'),root('zoo'));
One solution: build this via string-concatenation
But this is super-ugly...
an (almost) working solution...
The following solution uses FLWOR to re-create the XML after its creation, but this is not generic. It is necessary to add one element of this namespaces to the <zoo>, otherwise the namespace is created on a deeper level repeatedly. I added the attribut an:Dummy.
create table t1 ( c1 int, c2 varchar(50))
create table t2 ( c1 int, c2 int, c3 varchar(50))
insert t1 values
(1, 'Mouse'),
(2, 'Chicken'),
(3, 'Snake');
insert t2 values
(1, 1, 'Front Right'),
(2, 1, 'Front Left'),
(3, 1, 'Back Right'),
(4, 1, 'Back Left'),
(5, 2, 'Right'),
(6, 2, 'Left');
GO
--the function
CREATE FUNCTION dbo.TestFunc()
RETURNS XML
AS
BEGIN
declare #xml as xml;
WITH XMLNAMESPACES('uri:animal' as an)
select #xml = (
select
a.c2 as "#an:species"
, (
select l.c3 as "text()"
from t2 l where l.c2 = a.c1
for xml path('an:leg'), type
) as "an:legs"
from t1 a
for xml path('an:animal'));
set #xml=#xml.query('declare namespace an="uri:animal";
<zoo an:Dummy="dummy">
{
for $a in /an:animal
return <an:animal an:species="{$a/#an:species}"><an:legs>
{
for $l in $a/an:legs/an:leg
return <an:leg>{$l/text()}</an:leg>
}
</an:legs></an:animal>
}
</zoo>');
return #xml;
END
GO
--the call
SELECT dbo.TestFunc();
GO
--clean up
drop function dbo.TestFunc;
drop table t1
drop table t2
The result
<zoo xmlns:an="uri:animal" an:Dummy="dummy">
<an:animal an:species="Mouse">
<an:legs>
<an:leg>Front Right</an:leg>
<an:leg>Front Left</an:leg>
<an:leg>Back Right</an:leg>
<an:leg>Back Left</an:leg>
</an:legs>
</an:animal>
<an:animal an:species="Chicken">
<an:legs>
<an:leg>Right</an:leg>
<an:leg>Left</an:leg>
</an:legs>
</an:animal>
<an:animal an:species="Snake">
<an:legs />
</an:animal>
</zoo>
Previous answer
Okay, I think I got this completely wrong in my first attempt. The following is an example, where a function returns the XML without added namespaces.
I use one of the later answers which builds the inner XML in advance without a namespace and creates the full XML as a second call with a namespace. Please check this out:
create table t1 ( c1 int, c2 varchar(50))
create table t2 ( c1 int, c2 int, c3 varchar(50))
insert t1 values
(1, 'Mouse'),
(2, 'Chicken'),
(3, 'Snake');
insert t2 values
(1, 1, 'Front Right'),
(2, 1, 'Front Left'),
(3, 1, 'Back Right'),
(4, 1, 'Back Left'),
(5, 2, 'Right'),
(6, 2, 'Left');
GO
--the function
CREATE FUNCTION dbo.TestFunc()
RETURNS XML
AS
BEGIN
declare #xml as xml;
select #xml = (
select
a.c2 as "#species"
, (select l.c3 as "text()"
from t2 l where l.c2 = a.c1
for xml path('leg'), type) as "legs"
from t1 a
for xml path('animal'));
declare #resultXML XML;
;with XmlNamespaces( 'uri:animal' as an)
select #ResultXML= (SELECT #xml for xml path('') , root('zoo'),TYPE);
return #resultXML;
END
GO
--the call
SELECT dbo.TestFunc();
GO
--clean up
drop function dbo.TestFunc;
drop table t1
drop table t2
The result
<zoo xmlns:an="uri:animal">
<animal species="Mouse">
<legs>
<leg>Front Right</leg>
<leg>Front Left</leg>
<leg>Back Right</leg>
<leg>Back Left</leg>
</legs>
</animal>
<animal species="Chicken">
<legs>
<leg>Right</leg>
<leg>Left</leg>
</legs>
</animal>
<animal species="Snake" />
</zoo>
I am trying to create a xml ouput for my table using this approach but it seems to duplicate multiple nodes for TopLevelItem .Ideally i was hoping the format would be like this :
<TopLevelItems>
<TopLevelItem field1="1">
<LowLevelItem fieldA="a" />
<LowLevelItem fieldA="b" />
<LowLevelItem fieldA="def" />
</TopLevelItem>
<TopLevelItem field1="2">
<LowLevelItem fieldA="c" />
<LowLevelItem fieldA="d" />
</TopLevelItem>
</TopLevelItems>
DECLARE #sites TABLE (username varchar(50), ID INT, Name VARCHAR(50) )
INSERT INTO #sites
VALUES ('a', 1, 'a' ),
('a', 1, 'b' ),
('a', 2, 'c' ),
('a', 2, 'd' ),
('b', 1, 'def' )
select
T.ID as '#field1',
((select
L.Name as '#fieldA'
from #sites as L
where T.ID = L.ID
for xml path('LowLevelItem'), type))
from #sites as T
for xml path('TopLevelItem'), root('TopLevelItems')
Let me know if i am missing anything in my query or the approach that i am using is not right.
Thanks in advance.
SELECT T.ID AS '#field1'
, ( (SELECT L.Name AS '#fieldA'
FROM #sites AS L
WHERE T.ID = L.ID
FOR
XML PATH('LowLevelItem')
, TYPE)
)
FROM (SELECT DISTINCT ID FROM #sites) AS T
FOR XML PATH('TopLevelItem')
, ROOT('TopLevelItems')
I have a database with a table 'costumers' of columns id,name. I have a separate database with custom tables names that are in format table_customer_id_1, table_customer_id_2, ...n and columns id,price,datetime (the name of each table contains the id of each row in Table 'costumers')
What is the fastest way to query the table 'costumers' and get the customer name from table costumers and the latest order date of each table_customer_id_X?
Sorry about my English are not very good and I dont know if you can understand what i m asking for! :)
Try Something like this
select
a.name,
b.DateTime,
c.datetime
from customer a
inner join tbl_customer_id1 b on b.id=a.id
inner join tbl_customer_id2 c on c.id=a.id
where a.name like '%Customer name%'
order by b.datetime , c.datetime
CREATE TABLE #Customers (id int, name varchar(10))
CREATE TABLE #Table_Customer_id_1 (id int, price decimal(18,2), datetimefield datetime)
CREATE TABLE #Table_Customer_id_2 (id int, price decimal(18,2), datetimefield datetime)
CREATE TABLE #Table_Customer_id_3 (id int, price decimal(18,2), datetimefield datetime)
INSERT INTO #Customers VALUES (1, 'Test1'), (2, 'Test2'), (3, 'Test3')
INSERT INTO #Table_Customer_id_1 VALUES
(1, 1.1, '2013-01-01'),
(2, 1.2, '2013-01-02'),
(3, 1.3, '2013-01-03'),
(4, 1.4, '2013-01-04')
INSERT INTO #Table_Customer_id_2 VALUES
(1, 2.1, '2013-02-01'),
(2, 2.2, '2013-02-02'),
(3, 2.3, '2013-02-03'),
(4, 2.4, '2013-02-04')
INSERT INTO #Table_Customer_id_3 VALUES
(1, 3.1, '2013-03-01'),
(2, 3.2, '2013-03-02'),
(3, 3.3, '2013-03-03'),
(4, 3.4, '2013-03-04')
DECLARE #SQL nvarchar(max)
;WITH CTE AS(
SELECT 'SELECT name, LastOrder FROM #Customers INNER JOIN (SELECT Max(datetimefield) AS
LastOrder, ' + CONVERT(varchar(5),id) + ' AS CustID FROM #Table_Customer_id_' +
CONVERT(varchar(5),id) + ') Tab ON #Customers.id = Tab.CustID WHERE id = ' +
CONVERT(varchar(5),id) + '' AS SqlStatement FROM #Customers
)
SELECT #SQL = STUFF((SELECT ' UNION ALL ' + SqlStatement FROM CTE FOR XML PATH('')),1,11,'')
EXEC sp_executesql #SQL
--DROP TABLE #Customers
--DROP TABLE #Table_Customer_id_1
--DROP TABLE #Table_Customer_id_2
--DROP TABLE #Table_Customer_id_3
I've been using XML to import large amounts of data into SQL for a while now, but I am wondering if its possible to import data across multiple tables from a single XML file that has child nodes?
Given this example:
DECLARE #tbl_makes TABLE (ID int IDENTITY(1,1), makeName nvarchar(100))
INSERT INTO #tbl_makes (makeName) VALUES ('Ford')
INSERT INTO #tbl_makes (makeName) VALUES ('Jaguar')
DECLARE #tbl_models TABLE (ID int IDENTITY(1,1), makeID int, modelName nvarchar(100))
INSERT INTO #tbl_models (makeID, modelName) VALUES (1, 'Escort')
INSERT INTO #tbl_models (makeID, modelName) VALUES (1, 'Sierra')
INSERT INTO #tbl_models (makeID, modelName) VALUES (2, 'XK')
INSERT INTO #tbl_models (makeID, modelName) VALUES (2, 'XJS')
SELECT * FROM #tbl_makes m INNER JOIN #tbl_models md ON m.ID = md.makeID
DECLARE #xml XML = '
<cars>
<make name="Ford">
<model name="Mustang" />
<model name="Taurus" />
<model name="F350" />
</make>
<make name="Aston Martin">
<model name="Vanquish" />
<model name="DB7" />
<model name="Lagonda" />
</make>
</cars>'
I appreciate that the make names would need to be inserted/looked-up first before related data could be inserted. I've searched online for answers to this, but examples only use a single table. I'm guessing it's not possible without using various temporary tables, but here goes...
What about such solution?
INSERT INTO #tbl_makes (makeName)
SELECT i.i.value('#name', 'nvarchar(100)')
FROM #xml.nodes('/cars[1]/make')i(i)
LEFT JOIN #tbl_makes MA on i.i.value('#name', 'nvarchar(100)') = MA.makeName
WHERE MA.ID IS NULL;
INSERT INTO #tbl_models (makeID, modelName)
SELECT MA.ID, j.j.value('#name', 'nvarchar(100)')
FROM #xml.nodes('/cars[1]/make')i(i)
INNER JOIN #tbl_makes MA ON i.i.value('#name', 'nvarchar(100)') = MA.makeName
CROSS APPLY i.i.nodes('model')j(j)
LEFT JOIN #tbl_models MO on j.j.value('#name', 'nvarchar(100)') = MO.modelName
WHERE MO.ID IS NULL;