Concatenating Values Within SQL XML field - sql

I have a table in SQL Server 2012 which has an XML field. The field contains arrays (the number of elements is not constant) in the following format:
<values>
<value>A</value>
<value>B</value>
<value>C</value>
<value>D</value>
</values>
and I would like to turn it into a varchar like this:
'A;B;C;D'
I have tried:
SELECT myField.value('.', 'NVARCHAR(50)')
FROM myTable
which creates 'ABCD' but I don't know how to delimit it (In the real case they are not single character values).

Try this
DECLARE #myTable TABLE (id int,myField XML)
INSERT INTO #myTable(id,myField) VALUES(1,'<values>
<value>A</value>
<value>B</value>
<value>C</value>
<value>D</value>
</values>')
;WITH xmltable
AS
(
SELECT id, myField.v.value('.', 'varchar(200)') AS myField
FROM #myTable
CROSS APPLY myField.nodes('/values/value') AS myField(v)
)
SELECT STUFF((SELECT ';' + myField
FROM xmltable t2
WHERE t2.id = t1.id
FOR XML PATH('')),1,1,'') AS myField
FROM xmltable t1
GROUP BY id

I've thought of a hack to achieve this...
SELECT REPLACE(
REPLACE(
REPLACE(
CAST([myField] As NVARCHAR(MAX)),
'<values><value>',
''),
'</value></values>',
''),
'</value><value>',
';'
) As [Hacked]
FROM [myTable]
...but it makes me feel a bit dirty. There has to be a better way.

Related

SQl Server split delimited string into rows

I have a table in SQL Server 2008 database, the table has two columns,as follow:
I want to select the data as following:
This type of operation is rather painful in SQL Server, because the built-in string functions are pretty bad. The referenced question uses a while loop -- which is unnecessary. You can construct this all in one query using a recursive CTE:
with t as (
select 'ali' as col1, 'A;B;C' as col2
),
cte as (
select col1,
convert(varchar(max), left(col2, charindex(';', col2) - 1)) as val,
convert(varchar(max), stuff(col2, 1, charindex(';', col2), '') + ';') as rest
from t
union all
select col1,
convert(varchar(max), left(rest, charindex(';', rest) - 1)) as val,
convert(varchar(max), stuff(rest, 1, charindex(';', rest), '')) as rest
from cte
where rest <> ''
)
select cte.*
from cte;
I had a similar situation and I solved it using XML querying with this as my guide. I am not super proficient with XML queries, so I am hesitant to share my answer because I cannot fully explain it line by line even though it does work. What I do understand is that you replace your separator character (or string) with closing and opening XML tags with a open tag at the very beginning and a close tag at the very end which transforms this...
A;B;C
into this...
<X>A</X>
<X>B</X>
<X>C</X>
You can use XML query syntax to retrieve each of those nodes. There is nothing magical about "X" other than you have to use the same tag in the nodes() method in CROSS APPLY section.
CREATE TABLE Table1
(
Column1 VARCHAR(20)
, Column2 VARCHAR(50)
);
INSERT INTO Table1 (Column1, Column2) VALUES ('Ali', 'A;B;C');
INSERT INTO Table1 (Column1, Column2) VALUES ('Ahmed', 'D;E');
DECLARE #Separator VARCHAR(10);
SET #Separator = ';';
SELECT a.Column1
, b.SplitData
FROM (
SELECT Column1
, CAST('<X>' + REPLACE((
SELECT Column2 AS [*] FOR XML PATH('')
)
, #Separator
, '</X><X>'
) + '</X>' AS XML) xmlfilter
FROM Table1
) AS a
CROSS APPLY (
SELECT LetterIDs.Column2.value('.', 'varchar(50)') AS SplitData
FROM a.xmlfilter.nodes('X') AS LetterIDs(Column2)
) AS b;
Here is the db fiddle. I hope this helps.

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

What is the meaning of SELECT ... FOR XML PATH(' '),1,1)?

I am learning sql in one of the question and here I saw usage of this,can some body make me understand what xml path('') mean in sql? and yes,i browsed through web pages I didn't understand it quite well!
I am not getting the Stuff behind,now what does this piece of code do ?(only select part)
declare #t table
(
Id int,
Name varchar(10)
)
insert into #t
select 1,'a' union all
select 1,'b' union all
select 2,'c' union all
select 2,'d'
select ID,
stuff(
(
select ','+ [Name] from #t where Id = t.Id for XML path('')
),1,1,'')
from (select distinct ID from #t )t
There's no real technique to learn here. It's just a cute trick to concatenate multiple rows of data into a single string. It's more a quirky use of a feature than an intended use of the XML formatting feature.
SELECT ',' + ColumnName ... FOR XML PATH('')
generates a set of comma separated values, based on combining multiple rows of data from the ColumnName column. It will produce a value like ,abc,def,ghi,jkl.
STUFF(...,1,1,'')
Is then used to remove the leading comma that the previous trick generated, see STUFF for details about its parameters.
(Strangely, a lot of people tend to refer to this method of generating a comma separated set of values as "the STUFF method" despite the STUFF only being responsible for a final bit of trimming)
SQL you were referencing is used for string concatenation in MSSQL.
It concatenates rows by prepending , using for xml path to result
,a,b,c,d. Then using stuff it replaces first , for , thus removing it.
The ('') in for xml path is used to remove wrapper node, that is being automatically created. Otherwise it would look like <row>,a,b,c,d</row>.
...
stuff(
(
select ',' + CAST(t2.Value as varchar(10)) from #t t2 where t1.id = t2.id
for xml path('')
)
,1,1,'') as Value
...
more on stuff
more on for xml path

Convert multiples xml nodes data into varchar

I want to convert an xml string like this :
'<orga_label>ORG1</orga_label><orga_label>ORG2</orga_label><orga_label>ORG3</orga_label>'
into a varchar like this :
'ORG1, ORG2, ORG3'
in t-sql in one query.
Is that possible?
You can keep is very simple and avoid XML methods here...
DECLARE #foo xml = '<orga_label>ORG1</orga_label><orga_label>ORG2</orga_label><orga_label>ORG3</orga_label>';
SELECT
REPLACE(
REPLACE(
REPLACE(
CONVERT(nvarchar(4000), #foo), '</orga_label><orga_label>', ', '
),
'<orga_label>', ''
),
'</orga_label>', ''
);
Edit: this has the advantage of not invoking the XML methods and processor.
It's better you use a xml parser in script language like ruby .
require 'rexml/document'
xml =REXML::Document.new(File.open"filename/filename.XML")
xml.each_element('//(your element)') do |sdobi|
puts sdobi.attributes["orga_label"]
end
If you really want to use sql, it's a little bit comeplex:
SELECT SUBSTRING( columnname, LOCATE( '<orga_label',columnname ) +12, LOCATE( '</', tablename) ) from tablename
the if the substring not right try to change the number
declare #xml xml = '<orga_label>ORG1</orga_label><orga_label>ORG2</orga_label><orga_label>ORG3</orga_label>';
select stuff((select
',' + s from (
select
a.b.value('(.)[1]', 'varchar(50)') s
from #xml.nodes('/orga_label') a(b)
) t
for xml path('')
),1,1,'');