My question is related to this question. However I am writing a clean scenario on which I need help. I am a beginner in SQL and pardon me If I'm incorrect anywhere.
I have a procedure(huge on. replicating a small sample here) that spits out bunch of XMLs. On my second procedure, based on Parametric condition I have to insert a node in the XML for every CID and do a final select. I am adding a Rextester link below:
UPDATED LINK: http://rextester.com/EFGQB11125
Current output:
<Main>
<ID>1001</ID>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
<Main>
<ID>1002</ID>
<details>
<name>Rick</name>
<age>19</age>
</details>
</Main>
<Main>
<ID>1003</ID>
<details>
<name>Diane</name>
<age>25</age>
</details>
</Main>
<Main>
<ID>1004</ID>
<details>
<name>Kippy</name>
<age>26</age>
</details>
</Main>
Desired output:
When #type = 'N'/'U' then
<Main>
<ID>1001</ID>
<type>N</type>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
<Main>
<ID>1002</ID>
<type>U</type>
<details>
<name>Rick</name>
<age>19</age>
</details>
</Main>
<Main>
<ID>1003</ID>
<type>N</type>
<details>
<name>Diane</name>
<age>25</age>
</details>
</Main>
<Main>
<ID>1004</ID>
<type>N</type>
<details>
<name>Kippy</name>
<age>26</age>
</details>
</Main>
So, for every CID, based on #type values, a node should be inserted with that value. Any help?!
(Sorry for being redundant by any chance)
The trick used in the example below uses a bit of Dynamic Sql magic.
Basically building 1 big SQL with all the updates for each ID.
declare #Sql varchar(max);
select #Sql = concat(#Sql,'UPDATE #final SET xml_data.modify(''insert <type>',ctype,'</type> after (/Main/ID)[text()="',cid,'"][1]'');',CHAR(13),CHAR(10))
from #tbl;
-- select #Sql as xml_modify_sqls;
exec(#Sql);
select * from #final;
And here's some SQL to setup the temporary tables with the sample data:
IF OBJECT_ID('tempdb..#tbl') IS NOT NULL DROP TABLE #tbl;
create table #tbl (cid int, ctype varchar(5));
IF OBJECT_ID('tempdb..#cdetails') IS NOT NULL DROP TABLE #cdetails;
create table #cdetails (cid int, name varchar(5), age int);
IF OBJECT_ID('tempdb..#final') IS NOT NULL DROP TABLE #final;
create table #final (xml_data xml);
insert into #tbl (cid, ctype) values
(1001,'N'), (1002,'U'), (1003,'N'), (1004,'N');
insert into #cdetails (cid, name, age) values
(1001,'John',12), (1002,'Rick',19), (1003,'Diane',25), (1004,'Kippy',26);
insert into #final (xml_data)
select xml_data
from (
select
cd1.cid as ID,
-- type = t.ctype as type,
details =
(
select
cd.name,
cd.age
from #cdetails cd
where cd.cid = cd1.cid
For XML Path(''), Type
)
from #cdetails cd1
join #tbl t on cd1.cid = t.cid
For XML Path('Main')
) q(xml_data);
select * from #final;
In your case it is much easier to read the data out of the XML and re-build it from scratch:
;WITH cte AS
(
select m.value(N'(ID/text())[1]','int') AS ID
,m.value(N'(details/name)[1]','nvarchar(max)') AS DetailsName
,m.value(N'(details/age)[1]','int') AS DetailsAge
,t.ctype
from #final f
CROSS APPLY f.xml_data.nodes(N'/Main') A(m)
LEFT JOIN #tbl t ON t.cid=m.value(N'(ID/text())[1]','int') --left join, because I don't know if and ID is found as CID
)
SELECT ID
,ctype AS [type]
,DetailsName AS [details/name]
,DetailsAge AS [details/age]
FROM cte
FOR XML PATH('Main')
returns
<Main>
<ID>1001</ID>
<type>N</type>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
<Main>
<ID>1002</ID>
<type>U</type>
<details>
<name>Rick</name>
<age>19</age>
</details>
</Main>
<Main>
<ID>1003</ID>
<type>N</type>
<details>
<name>Diane</name>
<age>25</age>
</details>
</Main>
<Main>
<ID>1004</ID>
<type>N</type>
<details>
<name>Kippy</name>
<age>26</age>
</details>
</Main>
Related
I have the below XML output generated using a SQL Server query (added in the rextester link):
<Main>
<ID>1001</ID>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
I want to know how to add a namespace xmlns:json="http://www.samplenamespace.com/json" to the Main node.
Desired output:
<Main xmlns:json="http://www.samplenamespace.com/json">
<ID>1001</ID>
<details>
<name>John</name>
<age>12</age>
</details>
</Main>
Rextester link: http://rextester.com/OQZH6668
Any help!?
You need to use the WITH XMLNAMESPACES clause, for example:
---Fake tables in Stored procedure1
create table #Cdetails(cid int, name varchar(5), age int)
insert into #Cdetails
values(1001,'John',12),
(1002,'Rick',19),
(1003,'Diane',25),
(1004,'Kippy',26)
--Output of Stored procedure
create table #final(xml_data xml)
insert into #final
select
XML_data =
(select ID = cd1.cid,
details =
(
select cd.name,
cd.age
from #Cdetails cd
where cd.cid = cd1.cid
For XML Path(''), Type)
from #Cdetails cd1
For XML Path('Main'));
WITH XMLNAMESPACES ('http://www.samplenamespace.com/json' as json)
select * from #final
For XML Path('Main')
drop table #Cdetails,#final
Note the extra ; that is required when using WITH statements.
Rextester link: http://rextester.com/EBLL48414
This question already has answers here:
Deleting Multiple Nodes in Single XQuery for SQL Server
(2 answers)
Closed 8 years ago.
I have an XML parameter #Xml in SQL Stored procedure like below:
<Root>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
<Data>
<Id>2</Id>
<Name>Alex</Name>
<Des>Des2</Des>
</Data>
<Data>
<Id>3</Id>
<Name>Amy</Name>
<Des>Des3</Des>
</Data>
</Root>
now, I want to delete several nodes in this xml, the filter is like this (Id = 2 AND Name = 'Alex') OR (Id = 3 AND Name = 'Amy'), I don't want to use Cursor or something like this, just using one single script to do it, I am try to my best for this, but I can't get the answer, anybody can help me ? Thanks in advance!!!
The output should be :
<Root>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
</Root>
PS: the filter is a #table, so, the actually question is , how to remove the specific records in XML which contains in #table?
Id Name
2 Alex
3 Amy
Declare #Xml XML
set #Xml = '<Root>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
<Data>
<Id>2</Id>
<Name>Alex</Name>
<Des>Des2</Des>
</Data>
<Data>
<Id>3</Id>
<Name>Amy</Name>
<Des>Des3</Des>
</Data>
</Root>'
create TABLE #tempResult
(
Id Int,
Name Varchar(10)
)
insert into #tempResult
values(2, 'Alex'), (3, 'Amy')
SET #Xml = (
SELECT Node.value('Id[1]','int') AS PId, Node.value('Name[1]','Varchar(10)') AS PName
FROM #Xml.nodes('//Data') AS T(Node)
OUTER APPLY (
SELECT tr.Id, tr.Name
FROM #tempResult tr
WHERE Node.value('Id[1]','int') = tr.Id and Node.value('Name[1]','Varchar(10)') = tr.Name
) a
WHERE a.Id IS NULL
FOR XML PATH(''), TYPE
)
select #Xml
drop table #tempResult
I got results like this
1
Kevin
But I need the the whole xml contains Root node:
<Root>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
</Root>
How can I reach this? Any help? I am an new leaner for SQL to XML, please help me !!!
I got the answer, thanks all you guys
declare #xml xml
set #xml = '<Root><Header>123</Header><Data><Id>1</Id><Name>Kevin</Name><Des>Des1</Des></Data><Data><Id>2</Id><Name>Alex</Name><Des>Des2</Des></Data><Data><Id>3</Id><Name>Amy</Name><Des>Des3</Des></Data><Tail>456</Tail></Root>'
--set #xml.modify('delete /Root/Data[(Id[.=1] and Name[.="Kevin"]) or (Id[.=2] and Name[.="Alex"])]')
select #xml
declare #parameter XML
set #parameter = (SELECT Node.value('(Id)[1]', 'Int') AS Id,
Node.value('(Name)[1]', 'Varchar(10)') AS Name
FROM #xml.nodes('Root/Data') TempXML(Node)
WHERE Node.value('(Id)[1]', 'Int') != 3
for xml path('Root'), TYPE)
--select #parameter
declare #sql nvarchar(max)
set #sql = convert(nvarchar(max),
#parameter.query('for $i in (/Root) return concat("(Id[.=",
string($i/Id[1]),
"] and Name[.=""",
string($i/Name[1]),
"""]) or")')
)
set #sql = '['+substring(#sql,0,len(#sql)-2)+']'
set #sql = 'set #xml.modify(''delete /Root/Data'+#sql+''')'
select #sql
exec sp_executesql #sql, N'#xml xml output', #xml output
select #xml
You can shred the xml on the nodes in the root node #xml.nodes('/Root/*') and rebuild it using the XML from the shredded nodes R.X.query('.').
In a where not exists clause you exclude the Data nodes that is present in the temp table.
select R.X.query('.')
from #xml.nodes('/Root/*') as R(X)
where not exists (
select *
from #tempResult as T
where T.Id = R.X.value('(Id/text())[1]', 'int') and
T.Name = R.X.value('(Name/text())[1]', 'varchar(100)') and
R.X.value('local-name(.)', 'varchar(100)') = 'Data'
)
for xml path(''), root('Root'), type
Result:
<Root>
<Header>123</Header>
<Data>
<Id>1</Id>
<Name>Kevin</Name>
<Des>Des1</Des>
</Data>
<Tail>456</Tail>
</Root>
SQL Fiddle
For my situation, I have similar XML to the following:
DECLARE #MyXML XML
SET #MyXML =
'
<Students>
<Student>
<Nickname>Cat</Nickname>
<Name>Catherine</Name>
</Student>
<Student>
<Nickname>Cat</Nickname>
<Name>Joseph</Name>
</Student>
</Students>
'
SELECT T.Students.value('Nickname[1]', 'varchar(20)')
FROM #MyXML.nodes('/Students/Student[Nickname = "Cat"]') T(Students)
JOIN dbo.MyStudents CLASS ON T.Students.value('Name[1]', 'varchar(20)') = CLASS.StudentName
I want to update the records that get returned from this SELECT statement, replacing the Nickname with CLEARED. So, if dbo.MyStudents has Joseph but not Catherine, the resulting #MyXML variable would be:
<Students>
<Student>
<Nickname>Cat</Nickname>
<Name>Catherine</Name>
</Student>
<Student>
<Nickname>CLEARED</Nickname>
<Name>Joseph</Name>
</Student>
</Students>
Is there a way to do this by modifying the XML variable directly? Or will I need to put the XML variable into a temp table and perform a .modify on the temp table?
So far, examples that I have found do not have the conditional update with .modify based on the join.
Give this example a try. For more info see Syntax of the FOR XML Clause.
-- sample #MyStudents table
declare #MyStudents table (StudentName varchar(90), NickName varchar(90));
insert #MyStudents values('Kent', '');
insert #MyStudents values('Robert', 'Bobby');
insert #MyStudents values('Lisa', 'LB');
insert #MyStudents values('Venkat', 'Big Cat');
insert #MyStudents values('Catherine', 'Cat');
insert #MyStudents values('Joseph', 'Joey');
DECLARE #MyXML XML
SET #MyXML =
'<Students>
<Student>
<Nickname>Cat</Nickname>
<Name>Catherine</Name>
</Student>
<Student>
<Nickname>Joey</Nickname>
<Name>Joseph</Name>
</Student>
<Student>
<Nickname>Bonzo</Nickname>
<Name>Clifford</Name>
</Student>
</Students>'
select #MyXML = (
select (
select * from (
-- set existing #MyStudent records to CLEARED
select
'CLEARED' as NickName,
T.Students.value('Name[1]', 'varchar(20)') as Name
from #MyXML.nodes('/Students/Student') T(Students)
join #MyStudents CLASS ON T.Students.value('Name[1]', 'varchar(20)') = CLASS.StudentName
union
-- get students that aren't in #MyStudents table
select
T.Students.value('Nickname[1]', 'varchar(20)') as NickName,
T.Students.value('Name[1]', 'varchar(20)') as Name
from #MyXML.nodes('/Students/Student') T(Students)
where not exists(select 1 from #MyStudents s where s.StudentName = T.Students.value('Name[1]', 'varchar(20)'))
) as t1
for xml path('Student'), type)
for xml path('Students'), type)
Is it possible to achieve the following table from of output using XQuery in SQL Server
Edit: A change in my requirement, Consider i have a table which stores the below xml and also UserID
DECLARE #XML xml
set #XML = '<Security>
<FiscalYear Name="2012">
<Country Id="204">
<State Id="1">
<City Id="10"></City>
</State>
<State Id="2">
<City Id="20"></City>
<City Id="30"></City>
</State>
<State Id ="3"></State>
</Country >
</FiscalYear>
</Security>'
CREATE TABLE #tmp_user
(UserID INT,SecurityXML XML)
INSERT INTO #tmp_user
( UserID, SecurityXML )
VALUES ( 1,
#XML
)
Now how can i get a o/p like
Output:
UserID StateID CityID
1 1 10
1 2 20
1 2 30
1 3 0
Is it possible to achieve?
I modified your XML a bit because it was invalid. Change the end tag </Subsidiary> to </Country>.
declare #XML xml
set #XML =
'<Security>
<FiscalYear Name="2012">
<Country Id="204">
<State Id="1">
<City Id="10"></City>
</State>
<State Id="2">
<City Id="20"></City>
<City Id="30"></City>
</State>
<State Id ="3"></State>
</Country>
</FiscalYear>
</Security>'
select S.N.value('#Id', 'int') as StateID,
coalesce(C.N.value('#Id', 'int'), 0) as CityID
from #XML.nodes('/Security/FiscalYear/Country/State') as S(N)
outer apply S.N.nodes('City') as C(N)
A version using a table instead of XML variable
select T.UserID,
S.N.value('#Id', 'int') as StateID,
coalesce(C.N.value('#Id', 'int'), 0) as CityID
from #tmp_user as T
cross apply T.SecurityXML.nodes('/Security/FiscalYear/Country/State') as S(N)
outer apply S.N.nodes('City') as C(N)
This SQL only returns the first Activity element. How do I select them all? If I remove the [1] in the query I get an error that "value() requires a singleton".
DECLARE #myDoc xml
SET #myDoc =
'<Root>
<Activities>
<Activity>This is activity one</Activity>
<Activity>This is activity two</Activity>
<Activity>This is activity three</Activity>
</Activities>
</Root>'
SELECT #myDoc.value('(/Root/Activities/Activity)[1]', 'varchar(100)' )
Thanks Ed, but I found an easier version:
SELECT T.C.value('.', 'varchar(100)') as activity
FROM #myDoc.nodes('(/Root/Activities/Activity)') as T(C)
Though from your "unnecessarily complex" example it seems worryingly simple..
This works, but seems unnecessarily complex. There may be an easier way.
DECLARE #myDoc xml
SET #myDoc =
'<Root>
<Activities>
<Activity>This is activity one</Activity>
<Activity>This is activity two</Activity>
<Activity>This is activity three</Activity>
</Activities>
</Root>'
SELECT activity.VALUE('(//Activity)[1]','varchar(100)') AS activity
FROM (
SELECT NewTable.activity.query('.') AS activity
FROM (SELECT 1 AS col1) AS t
CROSS APPLY #myDoc.nodes('(/Root/Activities/Activity)') AS NewTable(activity)
) AS x
Here is another situation and solution: I was searching for this situation where there are two elements to be selected using one query.
CREATE TABLE #Table1 (ID INT IDENTITY(1,1),XMLDoc XML)
INSERT INTO #Table1 VALUES ('
<bookstore>
<name>Bookstore1</name>
<location>Location1</location>
<book>
<title>Titile1</title>
<price>40</price>
</book>
</bookstore>')
INSERT INTO #Table1 VALUES ('
<bookstore>
<name>Bookstore2</name>
<location>Location2</location>
<book>
<title>Titile2</title>
<price>50</price>
</book>
</bookstore>')
SELECT ID,
T.c.value('title[1]','varchar(50)') AS 'BookTitile',
T.c.value('price[1]','decimal(18,2)') AS 'Price'
FROM #Table1
CROSS APPLY #Table1.XMLDoc.nodes('/bookstore/book') T(c)
DROP TABLE #Table1
You can modify this as required to include XMLNamespaces.
Solution originally found at :https://social.msdn.microsoft.com/Forums/sqlserver/en-US/35e75e32-9ffb-4a30-8637-2cc928554763/selecting-multiple-values-from-multiple-rows-of-xml?forum=sqlxml