Concatenating xml values when selecting by XQuery in T-SQL - sql-server

This is my sample XML:
<root>
<element>
<subelement>
<value code="code1">value1</value>
<value code="code2">value2</value>
</subelement>
</element>
</root>
And this is my test query:
DECLARE #tempTable TABLE (
ValueCode nvarchar(MAX),
Value nvarchar(MAX)
)
DECLARE #xml XML
select #xml = cast(c1 as xml) from OPENROWSET (BULK 'C:\test.xml', SINGLE_BLOB) as T1(c1)
INSERT INTO #tempTable
SELECT
Tbl.Col.value('subelement[1]/#code', 'NVARCHAR(MAX)'),
Tbl.Col.value('subelement[1]', 'NVARCHAR(MAX)')
FROM #xml.nodes('//element') Tbl(Col)
SELECT * FROM #tempTable
The query, when executed, gives out a row with the ValueCode column containing NULL and the Value column containing 'value1value2'. What I would like to get would be the attributes and values concatenated with a separator. For example, I need ValueCode to contain 'code1; code2' and Value to contain 'value 1; value2'. How can I achieve that?

you can use xuery if you want concatenated strings:
SELECT
Tbl.Col.query('for $i in value return concat($i/text()[1], ";")').value('.', 'nvarchar(max)'),
Tbl.Col.query('for $i in value return concat($i/#code, ";")').value('.', 'nvarchar(max)')
FROM #xml.nodes('root/element/subelement') Tbl(Col);
if you want your values into rows:
SELECT
Tbl.Col.value('.', 'nvarchar(max)'),
Tbl.Col.value('#code', 'nvarchar(max)')
FROM #xml.nodes('root/element/subelement/value') Tbl(Col);
sql fiddle demo

Related

Query (SQL Server) to get value from node in XML column stored as varchar(max)?

I hate dealing with XML queries. I don't do it often enough to remember how to format everything, but I'm at my wits' end.
Let's say I have a table called Messages and a column inside it called Payload. Payload contains XML stored as varchar(max). The XML is formatted as such:
<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>
How do I query the table to retrieve a list of the values in the VID node?
Try this:
declare #w as xml = cast('
<NCOAPACP xmlns:i=''http://www.w3.org/2001/XMLSchema-instance''
xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>' as xml)
SELECT #w.value('declare namespace ns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM";
ns:NCOAPACP[1]/ns:VID[1]','varchar(10)') AS VID
If you have multiple VID node you can try this:
declare #w as xml = cast('
<NCOAPACP xmlns:i=''http://www.w3.org/2001/XMLSchema-instance''
xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
<VID>454545</VID>
</NCOAPACP>' as xml)
SELECT VID.value('.','varchar(10)')
from #w.nodes(
'declare namespace ns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM";
ns:NCOAPACP[1]/ns:VID'
)
AS C(VID)
If XML data are in a table column then the solution is a little more tricky. Especially if data type is varchar(max).
declare #tbl table(id int,payload varchar(max))--sample table
--sample data
insert #tbl values
(1,'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
<VID>3656184</VID>
</NCOAPACP>'),
(2,'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>123456</VID>
<VID>987654</VID>
</NCOAPACP>')
--it is convenient to define namespaces before the query
;with xmlnamespaces('uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM' as x)--we will use this x:
select id, t.vid.value('.[1]','varchar(20)') vid
-- . means self. [1] ensure single value
from
--convert varchar(max) to xml first
(select id,cast(payload as xml) payload from #tbl) tt
cross apply
--convert xml to a tabular form
tt.payload.nodes('//x:VID') t(vid)
Results:
id vid
1 3656183
1 3656184
2 123456
2 987654
U can try this
DECLARE #Messages TABLE(ID INT, Payload NVARCHAR(MAX))
INSERT INTO #Messages(ID, Payload) VALUES(1, '<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"> <VID>3656183</VID> </NCOAPACP>')
INSERT INTO #Messages(ID, Payload) VALUES(2, '<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"> <VID>3656183</VID> <VID>3656184</VID> </NCOAPACP>')
;WITH CTE(ID, Payload) AS
(
SELECT ID, CAST(Payload AS XML)
FROM #Messages
)
SELECT
ID,
x.items.value('.', 'NVARCHAR(10)')
FROM CTE
CROSS APPLY Payload.nodes('declare namespace xx="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM"; xx:NCOAPACP/xx:VID') as x(items)
The most simple way (CTE test created just to get output, your xml value converted to varchar(max) as in your table, then SELECT *.value):
;WITH test AS (
SELECT CAST(
'<NCOAPACP xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="uri://www.gen.da.hob/VC/Contract/QualityManagement/EMSM">
<VID>3656183</VID>
</NCOAPACP>' as varchar(max)) as Payload
)
SELECT CAST(Payload as xml).value('(/*/*)[1]', 'int')
FROM test
Output:
3656183

Get the Missing and Excess tags from a XML field

I have a table, Customer(Id int,Name nvarchar(100),Detail xml)
Sample Data:
1,'Abc','<ROOT> <TAG1>False</TAG1> <TAG3>value</TAG3> <TAG14>value</TAG14> </ROOT>'
2,'Pqr','<ROOT> <TAG2>False</TAG2> <TAG8>value</TAG8> <TAG11>value</TAG11> </ROOT>'
Also I have XML variable , #v_xml = '<ROOT> <TAG1>value</TAG1> <TAG2>value</TAG2> <TAG8>False</TAG8> <TAG14>False</TAG14> </ROOT>'.
Now I want get the Missing Tags and Excess Tags (in XML format) of each Customer comparing to the XML variable #v_xml (No need to consider the value, what ever it may be)
Expected Result:
Id Name Missing Excess
1,'Abc','<ROOT><TAG2>value</TAG2> <TAG8>value</TAG8> </ROOT>','<ROOT><TAG3>value</TAG3> </ROOT>'
2,'Pqr','<ROOT><TAG1>value</TAG1> <TAG14>False</TAG14> </ROOT>','<ROOT><TAG11>value</TAG11> </ROOT>'
There is no nested nodes/level in the XML. Only direct child elements under ROOT tag. But the number of child tags will vary. I am looking for a simple and common logic to resolve this (with or without SQL query).
Main idea parse tag name (local-name(.)) and concat diffs into xml
DECLARE #t TABLE (
Id INT PRIMARY KEY,
Name VARCHAR(50),
X XML
)
INSERT INTO #t
VALUES
(1, 'Abc', N'<ROOT><TAG1>False</TAG1><TAG3>value</TAG3><TAG14>value</TAG14></ROOT>'),
(2, 'Pqr', N'<ROOT><TAG2>False</TAG2><TAG8>value</TAG8><TAG11>value</TAG11></ROOT>')
DECLARE #x XML = N'<ROOT><TAG1>value</TAG1><TAG2>value</TAG2><TAG8>False</TAG8><TAG14>False</TAG14></ROOT>'
SELECT t.Id, t.Name, t2.val.query('Missing/*'), t2.val.query('Excess/*')
FROM #t t
CROSS APPLY (
SELECT
Missing = Missing.query,
Excess = Excess.query
FROM (
SELECT
query = t.c.query('.'),
tag = t.c.value('local-name(.)', 'SYSNAME')
FROM x.nodes('*/*') t(c)
) Excess
FULL JOIN (
SELECT
query = t.c.query('.'),
tag = t.c.value('local-name(.)', 'SYSNAME')
FROM #x.nodes('*/*') t(c)
) Missing ON Missing.tag = Excess.tag
WHERE Missing.tag IS NULL
OR Excess.tag IS NULL
FOR XML PATH(''), TYPE
) t2 (val)
Output -
----------- ---------- ------------------------- ------------------------------------------
1 Abc <TAG3>value</TAG3> <TAG2>value</TAG2><TAG8>False</TAG8>
2 Pqr <TAG11>value</TAG11> <TAG1>value</TAG1><TAG14>False</TAG14>

How dynamically insert data in respective tables based upon xml in Sql server

I have this type of input XML parameter pass in stored procedure, which is passed from my .net application.
Now, I need to insert records in each respective table:
<root>
<table1>
<patid>123</patid>
<name>gresh</name>
<fname>kumar</name>
</table1>
<table2>
<patid>123</patid>
<Schoolname>12345</Schoolname>
</table2>
<tablen>
<patid>123</patid>
<nfield>12345</nfield>
</tablen>
<root>
Suppose table1 will insert data in table1, table2 data in table2, tablen mean number of other tables may be there in XML.
So how it would be possible to insert in each respective tables?
You first need to get the data from the XML. Your case is pretty simple (and I am assuming you are inserting data only in one table only once, but you can solve it for multiple insertions in one table easily). So, the following statement gives me:
SELECT T.c.value('local-name(.)','nvarchar(128)') AS tableName
,T1.c1.value('local-name(.)','nvarchar(128)') AS columName
,T1.c1.value('(./node())[1]','nvarchar(max)') AS value
FROM #XML.nodes('/root/*') T(c)
CROSS APPLY T.c.nodes('*') T1(c1)
Then, you need to group the values for insertion for each table (I am using a CLR concatenate function (you can find it here, but you can search for T-SQL alternative also):
;WITH DataSource(tableName, columName, value)AS
(
SELECT T.c.value('local-name(.)','nvarchar(128)')
,T1.c1.value('local-name(.)','nvarchar(128)')
,T1.c1.value('(./node())[1]','nvarchar(max)')
FROM #XML.nodes('/root/*') T(c)
CROSS APPLY T.c.nodes('*') T1(c1)
)
SELECT tableName
,[dbo].[Concatenate] (columName)
,[dbo].[Concatenate] (value)
FROM DataSource
GROUP BY tableName
You can prepare the above code for dynamic execution further like this:.
;WITH DataSource(tableName, columName, value)AS
(
SELECT T.c.value('local-name(.)','nvarchar(128)')
,T1.c1.value('local-name(.)','nvarchar(128)')
,T1.c1.value('(./node())[1]','nvarchar(max)')
FROM #XML.nodes('/root/*') T(c)
CROSS APPLY T.c.nodes('*') T1(c1)
)
SELECT 'INSERT INTO ' + tableName + '(' + [dbo].[Concatenate] (columName) + ') VALUES(' + [dbo].[Concatenate] ('''' + value + '''') + ');'
FROM DataSource
GROUP BY tableName
Now, you simple need to build a dynamic T-SQL string and execute it using sp_executesql procedure:
DECLARE #XML XML = N'<root>
<table1>
<patid>123</patid>
<name>gresh</name>
<fname>kumar</fname>
</table1>
<table2>
<patid>123</patid>
<Schoolname>12345</Schoolname>
</table2>
<tablen>
<patid>123</patid>
<nfield>12345</nfield>
</tablen>
</root>';
DECLARE #DynamicSQLStatement NVARCHAR(MAX)
;WITH DataSource(tableName, columName, value)AS
(
SELECT T.c.value('local-name(.)','nvarchar(128)')
,T1.c1.value('local-name(.)','nvarchar(128)')
,T1.c1.value('(./node())[1]','nvarchar(max)')
FROM #XML.nodes('/root/*') T(c)
CROSS APPLY T.c.nodes('*') T1(c1)
)
SELECT #DynamicSQLStatement =
(
SELECT CHAR(10) + 'INSERT INTO ' + tableName + '(' + [dbo].[Concatenate] (columName) + ') VALUES(' + [dbo].[Concatenate] ('''' + value + '''') + ');'
FROM DataSource
GROUP BY tableName
FOR XML PATH, TYPE
).value('.', 'NVARCHAR(MAX)')
PRINT #DynamicSQLStatement
EXEC sp_executesql #DynamicSQLStatement
Note, that I am assuming that your XML structure is valid in the context of the database - the table exists, the column exists, and the values inserted in the columns are correct.
You can add more check if you need.

convert xml var to column

I shred XML by assigning an xml column value to a xml variable and I then do the following:
select s.property.value('#name[1]', 'varchar(100)') as name1
,s.property.value('#value[1]', 'varchar(100)') as value1
from #s1.nodes('//properties/property') as s(property)
where #s1 is the variable.
How can I use this syntax directly against an XML column in a table (2008 R2)?
You need to cross apply, then select out the values:
create table #xmltable (
x xml
)
insert #xmltable (x) values ('<xml><properties><property name="name1" value="value1" /><property name="name2" value="value2" /></properties></xml>')
select p.value('#name', 'varchar(100)') as name,
p.value('#value', 'varchar(100)') as value
from #xmltable xt
cross apply xt.x.nodes('//properties/property') t(p)
drop table #xmltable

Create single XML from SQL Server multiple tables query

I would like to query multiple tables from SQL Server 2005 and create a single XML document and do this in a stored procedure.
I know that I can query multiple tables within a stored procedure and get a DataSet in my .NET application that can be easily saved as XML. However, I'm trying to do something similar within the context of a stored procedure.
Essentially I want to do something like this:
declare #x xml
select #x = x.result
from (select y.* from tabley y for xml path('y')
union
select a.* from tablea a for xml path('aa')
) as x
select #x
If you want them just one after the other, you can try something like this:
SELECT
(SELECT y.* FROM dbo.TableY FOR XML PATH('y'), TYPE) AS 'YElements',
(SELECT a.* FROM dbo.TableA FOR XML PATH('aa'), TYPE) AS 'AElements'
FOR XML PATH(''), ROOT('root')
That return an XML something like:
<root>
<YElements>
<Y>
....
</Y>
<Y>
....
</Y>
......
</YElements>
<AElements>
<A>
....
</A>
<A>
....
</A>
......
</AElements>
</root>
SELECT -- Root Starts
(SELECT '1' AS ErrorCode FOR XML PATH(''), TYPE) AS 'Results', -- Level 1 Starts
(select -- Level 2 Starts
(select '1' CustomerID, -- Level 2 Detail Starts
'John' CustomerName,
'Doe' CustomerLastname,
'Y' Active
for xml path('Customers'), type) AS 'Customer' -- Level 2 Detail Ends
for xml path(''), TYPE) AS 'Response' -- Level 2 Ends
FOR XML PATH('') -- Level 1 Ends
,ROOT('BaseXML') -- Root Ends
Convert Table XML in sql server.
Declare #RESULTXML XML
Declare #SMS_REGISTER TABLE([id] VARCHAR(30),[status] VARCHAR(30))
Declare #EMAIL_REGISTER TABLE([id] VARCHAR(30),[status] VARCHAR(30))
Declare #ODP_REGISTER TABLE([id] VARCHAR(30),[status] VARCHAR(30))
Select #RESULTXML =(
SELECT (SELECT * FROM #SMS_REGISTER FOR XML PATH('sms'), TYPE) AS 'smss',
(SELECT * FROM #EMAIL_REGISTER FOR XML PATH('email'), TYPE) AS 'emails',
(SELECT * FROM #ODP_REGISTER FOR XML PATH('odp'), TYPE) AS 'odps'
FOR XML PATH('subroot'), ROOT('root') )
Return XML Like this
<root>
<subroot>
<smss>
<sms>
<id>NT0000000020</id>
<status>registered</status>
</sms>
<sms>
<id>NT0000000021</id>
<status>registered</status>
</sms>
<sms>
<id>NT0000000022</id>
<status>registered</status>
</sms>
<sms>
<id>NT0000000023</id>
<status>registered</status>
</sms>
</smss>
<emails>
<email>
<id>NT0000000024</id>
<status>registered</status>
</email>
<email>
<id>NT0000000025</id>
<status>registered</status>
</email>
</emails>
</subroot>
</root>

Resources