Insert XML Node into a SQL column in a table - sql

I have an XML file that I am passing into a stored procedure.
I also have a table. The table has the columns VehicleReg | XML | ProcessedDate
My XML comes in like so:
<vehicles>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
</vehicles>
What I need to do is read the xml and insert the vehiclereg and the full vehicle xml string into each row (the dateprocessed is a getdate() so not a problem).
I was working on something like below but had no luck:
DECLARE #XmlData XML
Set #XmlData = EXAMPLE XML
SELECT T.Vehicle.value('(vehiclereg)[1]', 'NVARCHAR(10)') AS vehiclereg,
T.Vehicle.value('.', 'NVARCHAR(MAX)'),
GETDATE()
FROM #XmlData.nodes('Vehicles/Vehicle') AS T(Vehicle)
I was wondering if someone could point me in the right direction?
Regards

Full query as you want:
DECLARE #XmlData XML
Set #XmlData = '<vehicles>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
</vehicles>'
SELECT T.Vehicle.value('./vehiclereg[1]', 'NVARCHAR(10)') AS vehiclereg,
T.Vehicle.query('.'),
GETDATE()
FROM #XmlData.nodes('/vehicles/vehicle') AS T(Vehicle)

Just need to remember XML is case sensitive. You had:
FROM #XmlData.nodes('Vehicles/Vehicle') AS T(Vehicle)
but you should have had:
FROM #XmlData.nodes('/vehicles/vehicle') AS T(Vehicle)
Also as TT pointed out there was no column named Registration
This should do it:
DECLARE #XmlData XML
Set #XmlData = '<vehicles>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
</vehicles>'
SELECT Vehicle.value('(vehiclereg)[1]', 'NVARCHAR(10)') AS vehiclereg,
Vehicle.value('.', 'NVARCHAR(MAX)'),
GETDATE()
FROM #XmlData.nodes('/vehicles/vehicle') AS T(Vehicle)
Result:
This would return XML:
DECLARE #XmlData XML
Set #XmlData = '<vehicles>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
</vehicles>'
SELECT T.Vehicle.value('(vehiclereg)[1]', 'NVARCHAR(10)') AS vehiclereg,
T.Vehicle.query('.'),
GETDATE()
FROM #XmlData.nodes('vehicles/vehicle') AS T(Vehicle)
Result:

You can get the XML by XML.query(), in this case query('.') on the vehicles/vehicle node to get the inner XML.
DECLARE #x XML=
'<vehicles>
<vehicle>
<vehiclereg>AB12CBE</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
<vehicle>
<vehiclereg>AB13QQT</vehiclereg>
<anotherprop>BLAH</anotherprop>
</vehicle>
</vehicles>';
DECLARE #t TABLE(VehicleReg VARCHAR(128),[XML] XML,ProcessedDate DATETIME);
INSERT INTO #t(VehicleReg,[XML],ProcessedDate)
SELECT VehicleReg=n.v.value('(vehiclereg)[1]','VARCHAR(128)'),
[XML]=n.v.query('.'),
ProcessedDate=GETDATE()
FROM #x.nodes('vehicles/vehicle') AS n(v);
SELECT * FROM #t;
Result:
+------------+------------------------------------------------------------------------------------+-------------------------+
| VehicleReg | XML | ProcessedDate |
+------------+------------------------------------------------------------------------------------+-------------------------+
| AB12CBE | <vehicle><vehiclereg>AB12CBE</vehiclereg><anotherprop>BLAH</anotherprop></vehicle> | 2016-03-01 15:21:37.640 |
| AB13QQT | <vehicle><vehiclereg>AB13QQT</vehiclereg><anotherprop>BLAH</anotherprop></vehicle> | 2016-03-01 15:21:37.640 |
+------------+------------------------------------------------------------------------------------+-------------------------+

Related

T-SQL Parse XML Response in table format

I am struggling with parsing an XML Response I have. I need the header values to be columns and the record values to be the data inside their respective rows. Below is a sample of a return with the header values and 1 record.
Where records show xsi:nil="true" would be NULL
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
<ns2:getReportResultResponse xmlns:ns2="http://service.apiendpoint.com">
<return>
<header>
<values>
<data>CUSTOMER NAME</data>
<data>DISPOSITION GROUP A</data>
<data>DISPOSITION GROUP B</data>
<data>DISPOSITION GROUP C</data>
<data>DISPOSITION PATH</data>
<data>FIRST DISPOSITION</data>
<data>LAST DISPOSITION</data>
<data>LIST NAME</data>
</values>
</header>
<records>
<values>
<data>Mark Smith</data>
<data>12</data>
<data>19</data>
<data>23</data>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data>Tier 1</data>
</values>
</records>
</return>
</ns2:getReportResultResponse>
</env:Body>
</env:Envelope>
declare #x xml = N'
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
<ns2:getReportResultResponse xmlns:ns2="http://service.apiendpoint.com">
<return>
<header>
<values>
<data>CUSTOMER NAME</data>
<data>DISPOSITION GROUP A</data>
<data>DISPOSITION GROUP B</data>
<data>DISPOSITION GROUP C</data>
<data>DISPOSITION PATH</data>
<data>FIRST DISPOSITION</data>
<data>LAST DISPOSITION</data>
<data>LIST NAME</data>
</values>
</header>
<records>
<values>
<data>Mark Smith</data>
<data>12</data>
<data>19</data>
<data>23</data>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data>Tier 1</data>
</values>
<values>
<data>B</data>
<data>2</data>
<data>22</data>
<data>222</data>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data xsi:nil="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/><!-- ?? -->
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data>Tier 2</data>
</values>
</records>
</return>
</ns2:getReportResultResponse>
</env:Body>
</env:Envelope>
';
select #x;
declare #sql nvarchar(max) = N'';
with xmlnamespaces ('http://schemas.xmlsoap.org/soap/envelope/' as env, 'http://service.apiendpoint.com' as ns2)
select
#sql = #sql + ',r.rec.value(''data[' + cast(colid as nvarchar(10)) + '][not(#xsi:nil="true")]'', ''nvarchar(500)'') as ' + colname
from
(
select
quotename(hd.h.value('.', 'sysname')) as colname,
row_number() over(order by hd.h) as colid
from #x.nodes('/env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data') as hd(h)
) as src
order by colid;
select #sql = stuff(#sql, 1, 1, N'');
select #sql = N'with xmlnamespaces (''http://schemas.xmlsoap.org/soap/envelope/'' as env, ''http://service.apiendpoint.com'' as ns2, ''http://www.w3.org/2001/XMLSchema-instance'' as xsi)
select
' + #sql + N'
from #x.nodes(''/env:Envelope/env:Body/ns2:getReportResultResponse/return/records/values'') as r(rec)
';
exec sp_executesql #stmt = #sql, #params = N'#x xml', #x = #x;
Assuming you have your XML data in a SQL Server variable #XmlData, you could use this XQuery to get the column names ("headers"):
WITH XMLNAMESPACES ('http://schemas.xmlsoap.org/soap/envelope/' AS env, 'http://service.apiendpoint.com' AS ns2)
SELECT
XCol.value('(.)[1]', 'varchar(50)')
FROM
#XmlData.nodes('/env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data') AS XHdr(XCol);
This is fairly straightforward, since you can assume every single header is really a string (therefore you can do the .value('(.)[1]', 'varchar(50)') call and be on the safe side).
However, for the data - as #Serg already mentioned in the comment - unless you can somehow know (or find out) what the data elements' datatypes are, this is going to be trickier... Using the same approach - assuming everything is a string - would work - but then you might be losing valuable information about your data bits:
WITH XMLNAMESPACES ('http://schemas.xmlsoap.org/soap/envelope/' AS env, 'http://service.apiendpoint.com' AS ns2)
SELECT
XCol.value('(.)[1]', 'varchar(50)')
FROM
#XmlData.nodes('/env:Envelope/env:Body/ns2:getReportResultResponse/return/records/values/data') AS XData(XCol)
Here is another solution. It is very close to the #lptr's method.
It is using XQuery and FLWOR expression to construct a dynamic SELECT clause of the final SQL statement.
SQL
DECLARE #x xml = N'
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
<ns2:getReportResultResponse xmlns:ns2="http://service.apiendpoint.com">
<return>
<header>
<values>
<data>CUSTOMER NAME</data>
<data>DISPOSITION GROUP A</data>
<data>DISPOSITION GROUP B</data>
<data>DISPOSITION GROUP C</data>
<data>DISPOSITION PATH</data>
<data>FIRST DISPOSITION</data>
<data>LAST DISPOSITION</data>
<data>LIST NAME</data>
</values>
</header>
<records>
<values>
<data>Mark Smith</data>
<data>12</data>
<data>19</data>
<data>23</data>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data>Tier 1</data>
</values>
<values>
<data>B</data>
<data>2</data>
<data>22</data>
<data>222</data>
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data xsi:nil="false" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/><!-- ?? -->
<data xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<data>Tier 2</data>
</values>
</records>
</return>
</ns2:getReportResultResponse>
</env:Body>
</env:Envelope>';
DECLARE #sql NVARCHAR(MAX) = N''
, #separator CHAR(1) = ',';
WITH XMLNAMESPACES ('http://schemas.xmlsoap.org/soap/envelope/' as env, 'http://service.apiendpoint.com' as ns2)
SELECT #sql = #x.query('
for $r in /env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data
let $pos := count(env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data[. << $r]) + 1
let $line := concat("c.value(''(data[", string($pos), "]/text())[1]'', ''VARCHAR(50)'') AS [", string(($r/text())[1]),"]")
return if ($r is (/env:Envelope/env:Body/ns2:getReportResultResponse/return/header/values/data[last()])[1]) then string($line)
else concat($line, sql:variable("#separator"))
').value('.', 'NVARCHAR(MAX)');
SET #sql = N';WITH XMLNAMESPACES (''http://schemas.xmlsoap.org/soap/envelope/'' as env, ''http://service.apiendpoint.com'' as ns2)
SELECT ' + #sql + N'
FROM #x.nodes(''/env:Envelope/env:Body/ns2:getReportResultResponse/return/records/values'') AS t(c)
';
EXEC sp_executesql #stmt = #sql, #params = N'#x xml', #x = #x;
Output
+---------------+---------------------+---------------------+---------------------+------------------+-------------------+------------------+-----------+
| CUSTOMER NAME | DISPOSITION GROUP A | DISPOSITION GROUP B | DISPOSITION GROUP C | DISPOSITION PATH | FIRST DISPOSITION | LAST DISPOSITION | LIST NAME |
+---------------+---------------------+---------------------+---------------------+------------------+-------------------+------------------+-----------+
| Mark Smith | 12 | 19 | 23 | NULL | NULL | NULL | Tier 1 |
| B | 2 | 22 | 222 | NULL | NULL | NULL | Tier 2 |
+---------------+---------------------+---------------------+---------------------+------------------+-------------------+------------------+-----------+

Add XML attribute in mssql using stored procedure

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')

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)