Shred & Concat XML into single column - sql

what is simple way to produce output concatenated into single column for all Parameter values in sample below, this xml doesn't have an ns in it and I'm using SQL Server 2012.
I try to get this output, below snippet with xml sample produce 3 columns, and I'd like to make 1.
100 | Param1
,Param22
,Param3322
XML & code:
DECLARE #xml XML = '<Parameters>
<Parameter>
<Name>Param1</Name>
</Parameter>
<Parameter>
<Name>Param22</Name>
</Parameter>
<Parameter>
<Name>Param3322</Name>
</Parameter> </Parameters> '
SELECT 100 id
,#XML xmlinfo
INTO #t -- drop table #t -- select * from #t
DECLARE #xml XML = (
SELECT xmlinfo
FROM #t
)
SELECT (
SELECT ID
FROM #t
) AS ID
,X.STockData.query('Name[1]').value('.', 'Varchar(10)') AS 'Parameter'
---CONCAT(X.STockData.query('Name[1]').value('.','Varchar(10)'),X.STockData.query('Name[2]').value('.','Varchar(10)')) AS 'Parameter'
FROM #xml.nodes('Parameters/Parameter') AS X(StockData)

You can use XQuery for loop to construct comma-separated value from XML field, for example :
SELECT
t.ID as ID
, CAST(t.xmlinfo.query('
for $p in Parameters/Parameter
return
if ($p is (Parameters/Parameter[last()])[1])
then string($p/Name[1])
else concat($p/Name[1], ", ")
')
AS VARCHAR(MAX)
) as Parameter
FROM MyTable as t
Sqlfiddle Demo
The XQuery above simply loop through Parameter elements, and return Name child element if current Parameter is the last Parameter, otherwise return Name concatenated with comma.

SELECT (
SELECT ID
FROM #t
) AS ID
,X.STockData.query('Name[1]').value('.', 'Varchar(10)')
+ ', ' + X.STockData.query('Name[2]').value('.', 'Varchar(10)')
+ ', ' + X.STockData.query('Name[3]').value('.', 'Varchar(10)')
as parameter
FROM #xml.nodes('Parameters/Parameter') AS X(StockData)
Try this

Related

Transform a SELECT * query to string

I have a query that returns a row
SELECT *
FROM table
WHERE id = 1;
I want to save the result into a nvarchar sql variable. I have seen similar questions Convert SQL Server result set into string but they only use select with the name of the columns, never with *.
select *
from table
where id = 1
for xml path ('')
However the answer is <column1>value1</column1> <column2>value2</column2> and I just want it to be value1, value2
Is there a way to achieve this? thank you!
If open to a helper function.
This will convert virtually any row, table or query to a string (delimited or not).
In the following examples I selected a PIPE delimiter with a CRLF line terminator.
Please note the usage and placement of _RN when a line terminator is required. Also note the ,ELEMENTS XSINIL ... this will included null values as empty string. If you want to exclude null values, simply omit the ,ELEMENTS XSINIL
Example as Entire Table or dbFiddle
Declare #YourTable Table (id int,[col_1] varchar(50),[col_2] varchar(50),[col_3] varchar(50),[col_n] varchar(50)) Insert Into #YourTable Values
(1,'data1','data2','data3','data4')
,(2,'data5','data6','data7','data8')
-- Entire Table
Declare #XML xml = (Select *,_RN=Row_Number() over (Order By (Select null)) From #YourTable for XML RAW,ELEMENTS XSINIL )
Select [dbo].[svf-str-Data-To-Delimited]('|',char(13)+char(10),#XML)
Returns
1|data1|data2|data3|data4
2|data5|data6|data7|data8
Example as Row Based
Select A.ID
,AsAString = [dbo].[svf-str-Data-To-Delimited]('|',char(13)+char(10),B.XMLData)
From #YourTable A
Cross Apply ( values ( (select a.* for xml RAW,ELEMENTS XSINIL )) )B(XMLData)
Returns
ID AsAString
1 1|data1|data2|data3|data4
2 2|data5|data6|data7|data8
The Function if Interested
CREATE Function [dbo].[svf-str-Data-To-Delimited] (#Delim varchar(50),#EOL varchar(50),#XML xml)
Returns varchar(max)
Begin
Return(
Select convert(nvarchar(max),(
Select case when Item='_RN' then ''
else case when nullif(lead(Item,1) over (Order by Seq),'_RN') is not null
then concat(Value,#Delim)
else concat(Value,#EOL)
end
end
From (
Select Seq = row_number() over(order by (select null))
,Item = xAttr.value('local-name(.)', 'nvarchar(100)')
,Value = xAttr.value('.','nvarchar(max)')
From #XML.nodes('/row/*') xNode(xAttr)
) A
Order By Seq
For XML Path (''),TYPE).value('.', 'nvarchar(max)') )
)
End
You can easily store the result as an XML string:
select *
from (values (1, 'x', getdate())) v(id, a, b)
where id = 1
for xml path ('');
Or as a JSON string:
select *
from (values (1, 'x', getdate())) v(id, a, b)
where id = 1
for json auto;
If you don't mind Using dynamic SQL (and INFORMATION_SCHEMA dictionary), for example, for SQL Server this works:
DECLARE #sql nvarchar(max) = '',
#result nvarchar(max),
#id int = 1
SELECT #sql += '+'',''+convert(nvarchar,' + QUOTENAME(column_name) +')' from INFORMATION_SCHEMA.columns where table_name = 'Student'
SET #sql = 'select #result=' + stuff(#sql,1,5,'') + ' from student where id = ' + CAST(#id as nvarchar)
EXECUTE sp_executesql #sql, N'#result nvarchar(max) OUTPUT', #result=#result OUTPUT
SELECT #result as MyOutput

xml.exist - sql:column to sequence

I try to create a query with xml.exist by using the sql:column-function where the value of the column should be transformed into a usable XQuery-sequence.
This is the query with a static sequence, which is working.
SELECT
FieldA
, FieldB
FROM
MyTable
WHERE
FieldC.exist('DSAuth/role[#id=("195", "267", "350")')
To get a dynamic sequence i would use a table function with returns a table with a column "IDsequence" that have all id's as a string. e.g. ' "195", "267", "350" '.
The table function should return only one row! With multiple rows it works, but i have to group the result at last, which is bad for performance.
SELECT
FieldA
, FieldB
FROM
MyTable
CROSS APPLY
dbo.MyFunc(0) AS f
WHERE
FieldC.exist('DSAuth/role[#id=sql:column("f.IDsequence")]')
Is there a way to get a usable sequence for XQuery from sql:column("f.IDsequence")?
Thanks for help.
Edit:
The problem in performance is that FieldB (and a few more fields) is a xml column, so i have to convert it to group by.
SELECT
FieldA
, CAST(CAST(FieldB AS nvarchar(max)) AS xml) AS FieldB
FROM
MyTable
CROSS APPLY
dbo.MyFunc(0) AS f
WHERE
FieldC.exist('DSAuth/role[#id=sql:column("f.IDsequence")]')
GROUP BY
FieldA
, CAST(FieldB AS nvarchar(max))
I don't know if this is the easiest approach, but you might try to include your list into the XML and use it in the predicate like here:
--A mockup-table to show the principles
DECLARE #tbl TABLE(TheXml XML)
INSERT INTO #tbl VALUES
(
N'<root>
<a>1</a>
<a>2</a>
</root>'
)
,(
N'<root>
<a>1</a>
</root>'
)
,(
N'<root>
<a>3</a>
<a>4</a>
</root>'
);
--This is easy: Just one value
SELECT * FROM #tbl WHERE TheXml.exist('/root/a[. cast as xs:int?=1]')=1;
--This is your sequence.
--But you cannot introduce the value list with sql:column() or sql:variable().
SELECT * FROM #tbl WHERE TheXml.exist('/root/a[. cast as xs:int?=(2,3)]')=1;
--But you can add the values to your XML before like in this cte
DECLARE #values TABLE(val INT);
INSERT INTO #values VALUES(2),(3);
WITH cte(NewXml) AS
(
SELECT (SELECT (SELECT val AS [#val] FROM #values FOR XML PATH('values'),TYPE)
,TheXml AS [*]
FOR XML PATH(''),TYPE
)
FROM #tbl t
)
SELECT NewXml.query('/root') TheXml
FROM cte
WHERE NewXml.exist('/root/a[. cast as xs:int?=/values/#val]')=1;
The intermediate XML looks like this:
<values val="2" />
<values val="3" />
<root>
<a>1</a>
<a>2</a>
</root>
The final .query('/root') will return the previous XML unchanged.
UPDATE
The same would work with an introduction on string base like here:
WITH cte(NewXml) AS
(
SELECT (SELECT CAST(N'<values val="2" /><values val="3" />' AS XML)
,TheXml AS [*]
FOR XML PATH(''),TYPE
)
FROM #tbl t
)
SELECT NewXml.query('/root') TheXml
FROM cte
WHERE NewXml.exist('/root/a[. cast as xs:int?=/values/#val]')=1

Query XML field in SQL Server

I am using sql server 2008 R2. I have table X with Column XXML with the following structure
<rec>
<set>
<Raw CLOrderID="GGM-30-08/24/10" Rej="Preopen" Sym="A" Tm="06:36:29.524" />
</set>
</rec>
I want to parse above column XXML and return output as below:
CLOrderID Rej Sym Tm
GGM-30-08/24/10 Preopen A 06:36:29.524
Use nodes() to shred the XML and value() to extract the attribute values.
select T.X.value('#CLOrderID', 'nvarchar(100)') as CLOrderID,
T.X.value('#Rej', 'nvarchar(100)') as Rej,
T.X.value('#Sym', 'nvarchar(100)') as Sym,
T.X.value('#Tm', 'time(3)') as Tm
from dbo.X
cross apply X.XXML.nodes('/rec/set/Raw') as T(X)
If you know for sure that you only will have one row extracted for each XML you can get the attribute values directly without shredding first.
select X.XXML.value('(/rec/set/Raw/#CLOrderID)[1]', 'nvarchar(100)') as CLOrderID,
X.XXML.value('(/rec/set/Raw/#Rej)[1]', 'nvarchar(100)') as Rej,
X.XXML.value('(/rec/set/Raw/#Sym)[1]', 'nvarchar(100)') as Sym,
X.XXML.value('(/rec/set/Raw/#Tm)[1]', 'time(3)') as Tm
from dbo.X
This can be done with a few for xml calls. This structure also remains flexible for future schema changes, provided /rec/set/Raw/#* is present. If that changes, you can always add a pipe with the new path for the attributes you're wanting to grab. Hope this helps.
declare #x table (id int identity(1,1) primary key, x xml)
insert into #x (x) values ('<rec>
<set>
<Raw CLOrderID="GGM-30-08/24/10" Rej="Preopen" Sym="A" Tm="06:36:29.524" />
</set>
</rec>')
select a.id, cast((
select (
select x.attribs.value('local-name(.)','nvarchar(20)') + ' '
from #x t
outer apply t.x.nodes('/rec/set/Raw/#*') x(attribs)
where t.id = a.id
for xml path('')
), (
select x.attribs.value('.','nvarchar(20)') + ' '
from #x t
outer apply t.x.nodes('/rec/set/Raw/#*') x(attribs)
where t.id = a.id
for xml path('')
)
for xml path('')
) as varchar(500))
from #x a

Concatenate XML variables in SQL Server

I have a query in a stored procedure retrieving some data in XML format to be returned in a variable #xml_data, like this:
SELECT #xml_data = (
SELECT * FROM (
SELECT 1 AS Tag
,0 AS Parent
.....
FROM MyTable
WHERE id = #id
UNION ALL
SELECT 2 AS Tag
,1 AS Parent
....
FROM MyTable2
WHERE id = #id
UNION ALL
SELECT 3 AS Tag
,2 AS Parent
....
FROM MyTable3
WHERE id = #id
) results
FOR XML EXPLICIT, TYPE)
This is working like the proverbial dream :)
However, I'd like to concatenate a header to this XML (e.g. <xml version="1.0" encoding="ISO-8859-1"/>) and can't figure out how to do it. I've tried converting to NVARCHAR, selecting the two variables in one statement but can't seem to get it right.
Can anyone help??
Thanks :)
Try doing like this:
DECLARE #x xml
DECLARE #strXML varchar(MAX)
SET #x=N'<Employee><Name>Luftwaffe</Name></Employee>'
set #strXML = '<xml version="1.0" encoding="ISO-8859-1"/>' + CONVERT(varchar(MAX),#x)
SELECT #strXML
Hope it helps !
You can just declare the string at the beginning and concatenate them together:
declare #xml_data nvarchar(MAX)
set #xml_data = '<xml version="1.0" encoding="ISO-8859-1"/>'
SELECT #xml_data = #xml_data + (
SELECT * FROM (
SELECT 1 AS Tag
,0 AS Parent
.....
FROM MyTable
WHERE id = #id
UNION ALL
SELECT 2 AS Tag
,1 AS Parent
....
FROM MyTable2
WHERE id = #id
UNION ALL
SELECT 3 AS Tag
,2 AS Parent
....
FROM MyTable3
WHERE id = #id
) results
FOR XML EXPLICIT, TYPE)
This is the easiest way, in my opinion:
declare #xml1 xml
declare #xml2 xml
declare #xml3 xml
select #xml1='<name>testname</name>'
select #xml2='<value>testvalue</value>'
select #xml3 =
(
select #xml1 AS xml1, #xml2 AS xml2
for xml path('')
)
select #xml3

How to Generate xml from sql for below pattern

I'm writing one stored procedure, which I have to create a xml column from db.
µ = CHAR(181) this is value separator,
¶ = CHAR(182) this is row separator
This is the statement I wrote. I know its not well formed.
SELECT #xmlString= CAST('<root><Section> ID =' + REPLACE(REPLACE ('20211µ1¶20212µ2', CHAR(182),
'</Section><Section> ID ='),CHAR(181), ' Slno=') + '</Section></root>' AS XML)
This is the pattern which I need to display like this.
<root>
<sections id="20211" slno="1" ></sections>
<sections id="20215" slno="2" ></sections>
</root>
declare #s varchar(50) = '20211µ1¶20212µ2'
declare #xmlString xml
;with C as
(
select T.N.value('value[1]', 'int') as id,
T.N.value('value[2]', 'int') as slno
from (select cast('<item><value>'+replace(replace(#s, 'µ','</value><value>'), '¶','</value></item><item><value>')+'</value></item>' as xml)) as X(XMLCol)
cross apply X.XMLCol.nodes('item') as T(N)
)
select #xmlString =
(
select C.id as [#id] ,
C.slno as [#slno]
from C
for xml path('sections'), root('root'), type
)
select #xmlString
Result:
<root>
<sections id="20211" slno="1" />
<sections id="20212" slno="2" />
</root>