I am trying to generate a XML using FOR XML SQL Query. Structure of the XML is pretty straight forward, except the requirement calls for an extra sub-element, which has to appear only once at the top of the document between the Root Element and record element. Here is an example:
<Employees xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RecordHeader>
<CreatedBy>My Organization</CreatedBy>
<CreateDate>1900-01-01T01:01:01-06:00</CreateDate>
</RecordHeader>
<Employee>
<EmployeeID>4</EmployeeID>
<FirstName>Rob</FirstName>
<LastName>Walters</LastName>
</Employee>
<Employee>
<EmployeeID>168</EmployeeID>
<FirstName>Rob</FirstName>
<LastName>Caron</LastName>
</Employee>
</Employees>
I created a FOR XML Query but I can't seem to get it to work properly for the RecordHeader. Any suggestions? Thanks!
SELECT
(SELECT 'My Organization' as [CreatedBy],
CONVERT(VARCHAR(33), GetDate(), 126) as [CreateDate] FOR XML PATH('RecordHeader'), ELEMENTS),
EmployeeID, FirstName, LastName
FROM Employees
FOR XML PATH('Employee'), ROOT('Employees'), ELEMENTS
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
Returns
<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>
Related
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>
i want output
<CustomerName>
<Customer id='1'>
<Name>xyz</Name>
</Customer>
<Customer id='2'>
<Name>Abc</Name>
</Customer>
</CustomerName>
for this output i wrote SP
SELECT
'' AS [CustomerName],
(SELECT
Name[Customer/Name]
FOR XML PATH(''), TYPE) AS[CustomerName/Customer]
FOR XML PATH('')
not able to add Id attribute, please help me
Try this:
DECLARE #tblCust TABLE(id INT, CustomerName VARCHAR(100));
INSERT INTO #tblCust VALUES
(1,'xyz')
,(2,'Abc');
SELECT id AS [#id]
,CustomerName AS [Name]
FROM #tblCust
FOR XML PATH('Customer'),ROOT('CustomerName')
This is the result
<CustomerName>
<Customer id="1">
<Name>xyz</Name>
</Customer>
<Customer id="2">
<Name>Abc</Name>
</Customer>
</CustomerName>
SELECT [#id] = t.id, [Name] = t.name
FROM (
VALUES (1, 'name'), (2, 'name2')
) t (id, name)
FOR XML PATH('Customer'), ROOT('CustomerName')
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)
Suppose I store employee data in a xml column in my log table. Sometimes data is also updated in the xml column from a stored procedure.
Here is the sample example
DECLARE #XML1 XML
DECLARE #XML2 XML
SET #XML1 =
'<NewDataSet>
<Employee>
<EmpID>1005</EmpID>
<Name> keith </Name>
<DOB>12/02/1981</DOB>
<DeptID>ACC001</DeptID>
<Salary>10,500</Salary>
</Employee>
</NewDataSet>'
SET #XML2 =
'<NewDataSet>
<Employee>
<EmpID>1006</EmpID>
<Name> keith </Name>
<DOB>05/02/1981</DOB>
<DeptID>ACC002</DeptID>
<Salary>10,900</Salary>
</Employee>
</NewDataSet>'
There is some difference in two the xml data which I need to show like old value & new value as a output of sql
Old Value New Value
--------- ---------
1005 1006
12/02/1981 05/02/1981
ACC001 ACC002
10,500 10,900
I just need to show the difference like above. So please guide me how to compare two xml data using XQuery and show the difference only in the above fashion in SQL Server. Please guide me with code snippet. thanks
;with XML1 as
(
select T.N.value('local-name(.)', 'nvarchar(100)') as NodeName,
T.N.value('.', 'nvarchar(100)') as Value
from #XML1.nodes('/NewDataSet/Employee/*') as T(N)
),
XML2 as
(
select T.N.value('local-name(.)', 'nvarchar(100)') as NodeName,
T.N.value('.', 'nvarchar(100)') as Value
from #XML2.nodes('/NewDataSet/Employee/*') as T(N)
)
select coalesce(XML1.NodeName, XML2.NodeName) as NodeName,
XML1.Value as Value1,
XML2.Value as Value2
from XML1
full outer join XML2
on XML1.NodeName = XML2.NodeName
where coalesce(XML1.Value, '') <> coalesce(XML2.Value, '')
Result:
NodeName Value1 Value2
-------------------- -------------------- --------------------
EmpID 1005 1006
DOB 12/02/1981 05/02/1981
DeptID ACC001 ACC002
Salary 10,500 10,900
I don't have the exact output you wanted - but at least you get a good comparison of old and new values:
;WITH OldData AS
(
SELECT
#XML1.value('(/NewDataSet/Employee/EmpID)[1]', 'int') AS 'EmpID',
#XML1.value('(/NewDataSet/Employee/Name)[1]', 'varchar(50)') AS 'Name',
#XML1.value('(/NewDataSet/Employee/DOB)[1]', 'datetime') AS 'DOB',
#XML1.value('(/NewDataSet/Employee/DeptID)[1]', 'varchar(50)') AS 'DeptID',
#XML1.value('(/NewDataSet/Employee/Salary)[1]', 'varchar(25)') AS 'Salary'
),
NewData AS
(
SELECT
#XML2.value('(/NewDataSet/Employee/EmpID)[1]', 'int') AS 'EmpID',
#XML2.value('(/NewDataSet/Employee/Name)[1]', 'varchar(50)') AS 'Name',
#XML2.value('(/NewDataSet/Employee/DOB)[1]', 'datetime') AS 'DOB',
#XML2.value('(/NewDataSet/Employee/DeptID)[1]', 'varchar(50)') AS 'DeptID',
#XML2.value('(/NewDataSet/Employee/Salary)[1]', 'varchar(25)') AS 'Salary'
)
SELECT
'Old values', od.*
FROM OldData od
UNION
SELECT 'New values', nd.*
FROM NewData nd
Gives you an output of:
EmpID Name DOB DeptID Salary
Old values 1005 keith 1981-12-02 00:00:00.000 ACC001 10,500
New values 1006 keith 1981-05-02 00:00:00.000 ACC002 10,900
SQL Server is great for storing and manipulating data - but presentation like this should be done in a front-end application (like an ASP.NET application) - not in T-SQL....
I am too late here !!! However I found that if employees XML as shown above has multiple records then the JOIN query with CTE returns incorrect results.
I have below XML input
DECLARE #XML1 XML
DECLARE #XML2 XML
SET #XML1 =
'<NewDataSet>
<Employees>
<Employee>
<Name> keith </Name>
<EmpID> 1005 </EmpID>
<DOB>12/02/1981</DOB>
<DeptID>ACC001</DeptID>
<Salary>10,500</Salary>
</Employee>
<Employee>
<Name> keith </Name>
<EmpID> 1004 </EmpID>
<DOB>12/02/1981</DOB>
<DeptID>ACC001</DeptID>
<Salary>10,500</Salary>
</Employee>
</Employees>
</NewDataSet>'
SET #XML2 =
'<NewDataSet>
<Employees>
<Employee>
<Name> keith </Name>
<EmpID> 1005 </EmpID>
<DOB>12/02/1981</DOB>
<DeptID>ACC001</DeptID>
<Salary>10,500</Salary>
</Employee>
<Employee>
<Name> keith </Name>
<EmpID> 1004 </EmpID>
<DOB>12/02/1981</DOB>
<DeptID>ACC001</DeptID>
<Salary>10,501</Salary>
</Employee>
<Employee>
<Name> keith1 </Name>
<EmpID> 10040 </EmpID>
<DOB>12/02/1981</DOB>
<DeptID>ACC001</DeptID>
<Salary>10,501</Salary>
</Employee>
</Employees>
</NewDataSet>'
I will use below query to find the difference
select T.N.value('local-name(.)', 'nvarchar(100)') as NodeName,
T.N.value('.', 'nvarchar(100)') as Value
from #XML2.nodes('/NewDataSet/Employees/Employee/*') as T(N)
EXCEPT
select T.N.value('local-name(.)', 'nvarchar(100)') as NodeName,
T.N.value('.', 'nvarchar(100)') as Value
from #XML1.nodes('/NewDataSet/Employees/Employee/*') as T(N)
Hope this helps !!!