How to insert xml into a node in another xml using XQuery? - sql

I have two xml variable say #res, #student in a stored proc in SQL server 2005.
#res contains
<Subject>English</Subject>
<Marks>67</Marks>
<Subject>Science</Subject>
<Marks>75</Marks>
#student contains:
<Student>
<Name>XYZ</Name>
<Roll>15</Roll>
<Result />
<Attendance>50</Attendance>
</Student>
I need to insert the xml of #res into the node Result in #student variable using XQuery.
How to implement that?
Please help.

In SQL Server 2008, it's pretty easy:
DECLARE #res XML = '<Subject>English</Subject>
<Marks>67</Marks>
<Subject>Science</Subject>
<Marks>75</Marks>'
DECLARE #student XML = '<Student>
<Name>XYZ</Name>
<Roll>15</Roll>
<Result />
<Attendance>50</Attendance>
</Student>'
SET #student.modify('insert sql:variable("#res") as first into (/Student/Result)[1]')
SELECT #student
That gives me the output:
<Student>
<Name>XYZ</Name>
<Roll>15</Roll>
<Result>
<Subject>English</Subject>
<Marks>67</Marks>
<Subject>Science</Subject>
<Marks>75</Marks>
</Result>
<Attendance>50</Attendance>
</Student>
Unfortunately, the ability to call .modify() and use a sql:variable in the insert statement was introduced with SQL Server 2008 only - doesn't work in SQL Server 2005.
I don't see how you could do this in SQL Server 2005, other than resorting back to ugly string parsing and replacement:
SET #student =
CAST(REPLACE(CAST(#student AS VARCHAR(MAX)),
'<Result/>',
'<Result>' + CAST(#res AS VARCHAR(MAX)) + '</Result>') AS XML)
Marc

This will work in SQL 2005 and is mostly an xquery solution:
DECLARE #res xml
SET #res =
'<Subject>English</Subject>
<Marks>67</Marks>
<Subject>Science</Subject>
<Marks>75</Marks>'
DECLARE #student xml
SET #student =
'<Student>
<Name>XYZ</Name>
<Roll>15</Roll>
<Result />
<Attendance>50</Attendance>
</Student>'
DECLARE #final XML
SET #final = CAST(CAST(#student AS VARCHAR(MAX)) + '<test>' + CAST(#res AS VARCHAR(MAX)) + '</test>' AS XML)
SET #final.modify('insert /test/* into (/Student/Result)[1]')
SET #final.modify('delete /test')
SELECT #final
You can set your #student variable to #final at that point if you need to do that. The name of "test" for the node was just what I chose to use. You can use any name as long as it will not already appear in your XML.
You basically just throw the two XML strings together so that they are both available to xquery at once.

You may also try to go back to relational data and than back to xml; something like:
DECLARE #res xml =
'<result>
<StudentID>1</StudentID>
<Subject>English</Subject>
<Marks>67</Marks>
</result>
<result>
<StudentID>1</StudentID>
<Subject>Science</Subject>
<Marks>75</Marks>
</result>'
DECLARE #student xml =
'<Student>
<StudentID>1</StudentID>
<Name>XYZ</Name>
<Roll>15</Roll>
<Attendance>50</Attendance>
</Student>'
;
WITH cte_1
AS ( SELECT t.c.value('StudentID[1]', 'int') AS [StudentID]
,t.c.value('Subject[1]', 'varchar(50)') AS [Subject]
,t.c.value('Marks[1]', 'int') AS [Marks]
FROM #res.nodes('/result') AS t ( c )
),
cte_2
AS ( SELECT t.c.value('StudentID[1]', 'int') AS [StudentID]
,t.c.value('Name[1]', 'varchar(50)') AS [Name]
,t.c.value('Roll[1]', 'int') AS [Roll]
,t.c.value('Attendance[1]', 'int') AS [Attendance]
FROM #student.nodes('/Student') AS t ( c )
)
SELECT student.StudentID
,student.[Name]
,student.Roll
,student.Attendance
,( SELECT result.[Subject]
,result.Marks
FROM cte_1 AS result
WHERE student.StudentID = result.StudentID
FOR
XML AUTO
,TYPE
,ELEMENTS
)
FROM cte_2 AS student
FOR XML AUTO
,ELEMENTS
Returns:
<student>
<StudentID>1</StudentID>
<Name>XYZ</Name>
<Roll>15</Roll>
<Attendance>50</Attendance>
<result>
<Subject>English</Subject>
<Marks>67</Marks>
</result>
<result>
<Subject>Science</Subject>
<Marks>75</Marks>
</result>
</student>
Not exactly your example, but close.

Related

how to get the count of the xml nodes

I have the following xml nodes
<PPAProposal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Answers>
<AnswersList>
<Entry key="ac5_a_InsuredDetailsUpload" type="System.Collections.ArrayList">
<value>
<anyType xsi:type="xsd:string">C___documents__PPA_813FN0011684_190219_1003.pdf</anyType>
<anyType xsi:type="xsd:string">New Doc 2019-02-18 13.37.43 (1).pdf</anyType>
</value>
</Entry>
</Answers>
</AnswersList>
</PPAProposal>
I need to fetch the count of the "AnyType" nodes.
I have written query something like below.
SELECT
(CASE WHEN Data.value('(/*/Answers/AnswersList/Entry[#key="ac5_a_InsuredDetailsUpload"]/value)[1]', 'nvarchar(max)')!=''
THEN
(SELECT Count((ISNULL(Col.value('(value)[1]', 'nvarchar(max)'), '0'))) AS upload
FROM Data.nodes('(/*/Answers/AnswersList/Entry[#key="ac5_a_InsuredDetailsUpload"])') AS Tbl(Col))
ELSE 0
END
)
FROM TEMPTABLE
I'm able to return count 1 instead of count 2 as there is 2 nodes in the "Value" node.
You can count anyType elements directly in the XPath :
SELECT
t.Data.value(
'count(/*/Answers/AnswersList/Entry[#key="ac5_a_InsuredDetailsUpload"]/value/anyType)',
'int'
) as anytype_count
FROM TEMPTABLE t;
dbfiddle demo
declare #XMLDATA xml;
set #XMLDATA = '
<PPAProposal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AnswersList>
<Answers>
<Entry key="ac5_a_InsuredDetailsUpload" type="System.Collections.ArrayList">
<value>
<anyTypexsi:type="xsd:string">C___documents__PPA_813FN0011684_190219_1003.pdf</anyType>
<anyType xsi:type="xsd:string">New Doc 2019-02-18 13.37.43 (1).pdf</anyType>
</value></Entry></Answers></AnswersList></PPAProposal>'
DECLARE #XMLDocPointer INT;
EXEC sp_xml_preparedocument #XMLDocPointer OUTPUT,#XMLDATA;
SELECT value INTO #TEMPDATA
FROM OPENXML( #XMLDocPointer, '/PPAProposal/AnswersList/Answers/Entry/value/anyType', 2 )
WITH ( value NVARCHAR(1000), anyType NVARCHAR(1000) )
EXEC sp_xml_removedocument #XMLDocPointer;
select count(*)total from #TEMPDATA
drop table #TEMPDATA

Adding XML Schema Reference in SQL

I can't find for the life of me if it's possible to add a direct reference to the schema in the root of my XML.
Here is my code:
Declare #Employee table (EmployeeID int,First_Name varchar(50),Last_Name varchar(50))
Insert into #Employee values
(1,'John','Smith'),
(2,'Jane','Doe' )
Select (Select CreatedBy='My Organization',CreateDate=GetDate() For XML Path('RecordHeader'),Type )
,(Select * From #Employee For XML Path('Employee'),Type )
For XML Path ('Employees'),Type
Which produces the following results:
<Employees>
<RecordHeader>
<CreatedBy>My Organization</CreatedBy>
<CreateDate>2016-10-18T16:09:48.110</CreateDate>
</RecordHeader>
<Employee>
<EmployeeID>1</EmployeeID>
<First_Name>John</First_Name>
<Last_Name>Smith</Last_Name>
</Employee>
<Employee>
<EmployeeID>2</EmployeeID>
<First_Name>Jane</First_Name>
<Last_Name>Doe</Last_Name>
</Employee>
</Employees>
Is there a way to add part in my SQL to make the root tag to include the following:
<Employees xmlns="https://www.blahblah.org/blahblah/BlahSchema.xsd">
Thank you for the help!
Always had trouble with xmlns, so I use a little hack
Declare #Employee table (EmployeeID int,First_Name varchar(50),Last_Name varchar(50))
Insert into #Employee values
(1,'John','Smith'),
(2,'Jane','Doe' )
Declare #XML xml = (
Select (Select CreatedBy='My Organization',CreateDate=GetDate() For XML Path('RecordHeader'),Type )
,(Select * From #Employee For XML Path('Employee'),Type )
For XML Path ('Employees'),Type
)
set #XML.modify('insert ( attribute MyNameSpace {"https://www.blahblah.org/blahblah/BlahSchema.xsd"}) into (/Employees)[1]')
set #XML = replace(cast(#XML as nvarchar(max)),'[MyNameSpace]','xmlns')
Select #XML
Returns
<Employees xmlns="https://www.blahblah.org/blahblah/BlahSchema.xsd">
<RecordHeader>
<CreatedBy>My Organization</CreatedBy>
<CreateDate>2017-01-10T15:19:42.873</CreateDate>
</RecordHeader>
<Employee>
<EmployeeID>1</EmployeeID>
<First_Name>John</First_Name>
<Last_Name>Smith</Last_Name>
</Employee>
<Employee>
<EmployeeID>2</EmployeeID>
<First_Name>Jane</First_Name>
<Last_Name>Doe</Last_Name>
</Employee>
</Employees>
EDIT to Exclude Empty Values
NULL will be omitted by default, so if you want to exclude empty values (not null), You can convert the value to NULL via NullIf(). For example
Declare #Employee table (EmployeeID int,First_Name varchar(50),Last_Name varchar(50))
Insert into #Employee values
(1,'John','Smith'),
(2,'Jane','Doe' ),
(3,'Betty',null ), -- No element for Last_Name
(4,'Susan','' ) -- No element for Last_Name
Declare #XML xml = (
Select (Select CreatedBy='My Organization',CreateDate=GetDate() For XML Path('RecordHeader'),Type )
,(Select EmployeeID = NullIf(EmployeeID,'')
,First_Name = NullIf(First_Name,'')
,Last_Name = NullIf(Last_Name,'')
From #Employee For XML Path('Employee'),Type )
For XML Path ('Employees'),Type
)
set #XML.modify('insert ( attribute MyNameSpace {"https://www.blahblah.org/blahblah/BlahSchema.xsd"}) into (/Employees)[1]')
set #XML = replace(cast(#XML as nvarchar(max)),'[MyNameSpace]','xmlns')
Select #XML
You can use the WITH NAMESPACES keyword, but that adds the name space to all the nodes:
Declare #Employee table (EmployeeID int,First_Name varchar(50),Last_Name varchar(50))
Insert into #Employee values
(1,'John','Smith'),
(2,'Jane','Doe' );
WITH XMLNAMESPACES (DEFAULT 'https://www.blahblah.org/blahblah/BlahSchema.xsd')
Select (Select CreatedBy='My Organization',CreateDate=GetDate() For XML Path('RecordHeader'),Type )
,(Select * From #Employee For XML Path('Employee'),Type )
For XML Path ('Employees'),Type
Result:
<Employees xmlns="https://www.blahblah.org/blahblah/BlahSchema.xsd">
<RecordHeader xmlns="https://www.blahblah.org/blahblah/BlahSchema.xsd">
<CreatedBy>My Organization</CreatedBy>
<CreateDate>2017-01-10T16:36:23.450</CreateDate>
</RecordHeader>
<Employee xmlns="https://www.blahblah.org/blahblah/BlahSchema.xsd">
<EmployeeID>1</EmployeeID>
<First_Name>John</First_Name>
<Last_Name>Smith</Last_Name>
</Employee>
<Employee xmlns="https://www.blahblah.org/blahblah/BlahSchema.xsd">
<EmployeeID>2</EmployeeID>
<First_Name>Jane</First_Name>
<Last_Name>Doe</Last_Name>
</Employee>
</Employees>

How to using for loop using XQuery to delete xml nodes [duplicate]

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

SQL Server - Replace text in XML variable based on join

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)

Transform XML to Table data using XQuery in SQL Server

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)