how to execute subquery without declaring XML? - sql

why this query is not executing ??
SELECT [Value] = T.c.value('.','varchar(30)') FROM (SELECT '<s>'+ REPLACE ((select tag_id+',' from tbl_container_track for xml path('')),',','</s> <s>')+ '</s>').nodes('/s') T(c)
But this one is working ?
declare #X xml
SELECT #X = (SELECT '<s>'+ REPLACE ((select tag_id+',' from tbl_container_track for xml path('')),',','</s> <s>')+ '</s>')
SELECT [Value] = T.c.value('.','varchar(30)') FROM #X.nodes('/s') T(c)
Can some one help me to simplify without declaring #X ?

Try this: CAST TO XML Datatype you missed
SELECT [Value] = T.c.value('.', 'varchar(30)')
FROM (SELECT Cast(( '<s>' + Replace ((SELECT tag_id+',' FROM tbl_container_track FOR xml path('')), ',', '</s> <s>')
+ '</s>' ) AS XML)) AS Data
CROSS APPLY Data.nodes('/s') T(c)

Related

Updating Node structure in SQL XML data

I have some Data stored as XML in SQL Server that looks as follows:
<FormSearchFilter>
.......
<IDs>
<int>1</int>
<int>2</int>
</IDs>
.......
</FormSearchFilter>
This XML is mapped to a DTO and the data type for IDs is changing from a List to a string. As a result I now need to updae all existing XML: data to look as follows:
<FormSearchFilter>
.......
<IDs>1,2</IDs>
.......
</FormSearchFilter>
Whats the best way to achieve this via an update query
Besides the hint, that this is a very bad idea! you might try something like this:
DECLARE #t TABLE(
Id INT NOT NULL IDENTITY(1,1),
xml XML)
INSERT INTO #t(xml)
VALUES
('<FormSearchFilter><IDs><int>1</int><int>2</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int><int>4</int></IDs></FormSearchFilter>');
UPDATE #t
SET [xml]= (SELECT REPLACE([xml].query('data(/FormSearchFilter/IDs/int)').value('.','nvarchar(max)'),' ',',') AS IDs
FOR XML PATH('FormSearchFilter'));
SELECT * FROM #t
Explanation:
XQuery function data() will return alle text() nodes (in your case the int values) separated by a blank. This can be replaced with a comma to get the list needed.
UPDATE: Preserve other elements (be aware, that the order changes)
INSERT INTO #t(xml)
VALUES
('<FormSearchFilter><test>x</test><IDs><int>1</int><int>2</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int></IDs><test>x</test></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int><int>4</int></IDs></FormSearchFilter>');
UPDATE #t
SET [xml]= (SELECT [xml].query('/FormSearchFilter/*[local-name()!="IDs"]') AS [*]
,REPLACE([xml].query('data(/FormSearchFilter/IDs/int)').value('.','nvarchar(max)'),' ',',') AS IDs
FOR XML PATH('FormSearchFilter'));
SELECT * FROM #t
A bit of a hack, and if you're open to a helper Table-Valued Function.
Example
Declare #XML xml = '
<FormSearchFilter>
<OtherContent>Some Content</OtherContent>
<IDs>
<int>1</int>
<int>2</int>
</IDs>
<IDs>
<int>11</int>
<int>12</int>
<int>13</int>
</IDs>
<IDs>
<int>99</int>
</IDs>
<MoreContent>Some MORE Content</MoreContent>
</FormSearchFilter>
'
Select #XML = replace(cast(#XML as varchar(max)),RetVal,NewVal)
From (
Select *
,NewVal = stuff(replace(replace(RetVal,'<int>',','),'</int>',''),1,1,'')
From [dbo].[tvf-Str-Extract](cast(#XML as varchar(max)),'<IDs>','</IDs>')
) A
Select #XML
Returns
<FormSearchFilter>
<OtherContent>Some Content</OtherContent>
<IDs>1,2</IDs>
<IDs>11,12,13</IDs>
<IDs>99</IDs>
<MoreContent>Some MORE Content</MoreContent>
</FormSearchFilter>
The TVF was created because I tired of extracting content (left,right,charindex,patindex,reverse,...). It is a modifed parse/split function which accepts two non-like delimiters. Just to illustrate, if you were to run:
Select * From [dbo].[tvf-Str-Extract](cast(#XML as varchar(max)),'<IDs>','</IDs>')
The results would be
RetSeq RetPos RetVal
1 65 <int>1</int><int>2</int>
2 100 <int>11</int><int>12</int><int>13</int>
3 150 <int>99</int>
The TVF if Interested
CREATE FUNCTION [dbo].[tvf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (
Select *,RetVal = Substring(#String, N, L)
From cte4
) A
Where charindex(#Delimiter2,RetVal)>1
)
/*
Max Length of String 1MM characters
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[tvf-Str-Extract] (#String,'[[',']]')
*/
Not particularly elegant but does end up with the required output:
DECLARE #t TABLE(
Id INT NOT NULL IDENTITY(1,1),
xml XML)
INSERT INTO #t(xml)
VALUES
('<FormSearchFilter><IDs><int>1</int><int>2</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int><int>4</int></IDs></FormSearchFilter>');
DECLARE #updates TABLE(
Id INT,
UpdatedValue XML
)
INSERT INTO #updates
SELECT
Id,
(SELECT STUFF((
SELECT
',' + c.value('.', 'varchar')
FROM #t t1
CROSS APPLY t1.xml.nodes('//IDs/int') x(c)
WHERE t1.Id = t.Id
FOR XML PATH('')
), 1, 1, '') IDs
FOR XML PATH(''))
FROM #t t
-- remove existing IDs node
UPDATE #t
SET xml.modify('delete //IDs')
-- insert updated IDs node back in
UPDATE t
SET xml.modify('insert sql:column("u.UpdatedValue") into (/FormSearchFilter)[1]')
FROM #t t
JOIN #updates u ON t.Id = u.Id

SQL Server convert xml to csv using CTE - nulls currently being ignored

I am using CTE to convert xml to csv so that it can be exported to a file, however if I have an empty xml tag, this currently gets ignored.
Here is my initial solution courtesy of this previous very helpful post:
https://stackoverflow.com/a/23785202/6260721
Here is my sql:
CREATE TABLE EXPORT_TEST
(
DATA varchar(max)
)
INSERT INTO EXPORT_TEST (DATA)
VALUES ('<EXPORT_DATA><ID>ABC123</ID><PRICE_A>5.6</PRICE_A><PRICE_B></PRICE_B><PRICE_C>8.1</PRICE_C></EXPORT_DATA>')
DECLARE #commaSeparatedValues NVARCHAR(MAX)
DECLARE #xml XML = (SELECT TOP 1 CONVERT(xml,DATA) FROM EXPORT_TEST)
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
), cols AS (
SELECT
rownr,
Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('//text()') Tbl(Col)
)
INSERT INTO EXPORT_TEST(DATA)
SELECT DISTINCT
STUFF((
SELECT ',' + IIF(ISNUMERIC(value) = 1, Value, '''' + Value + '''')
FROM cols SSF WHERE SSF.rownr = S.rownr
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)'
), 1, 1, '') as DATA
FROM cols S
SELECT * FROM EXPORT_TEST
At the moment, it is returning:
'ABC123',5.6,8.1
But I don't want it to ignore PRICE_B, I want it to return an empty string:
'ABC123',5.6,,8.1 <--extra comma required where PRICE_B should be
How can I achieve this?
Besides the possibility to shredd the full XML and re-concatenate its values (there is an answer already), you might use FLWOR-XQuery:
DECLARE #xml XML=
'<EXPORT_DATA>
<ID>ABC123</ID>
<PRICE_A>5.6</PRICE_A>
<PRICE_B />
<PRICE_C>8.1</PRICE_C>
</EXPORT_DATA>';
EDIT better to read with a variable $txt instead of ($n/text())[1]
SELECT
STUFF
(
#xml.query('
let $r:=/EXPORT_DATA
for $n in $r/*
let $txt:=($n/text())[1]
return if(empty($txt) or not(empty(number($txt)))) then
concat(",",string($txt))
else concat(",''",string($txt),"''")
').value('.','nvarchar(max)'),1,1,'');
The result
'ABC123' ,5.6 , ,8.1
This code works on a mass of records using XQUERY.
I'm assuming char(10) (Line Feed) does not appear in your data.
I'm assuming the maximum length of the concatenated text is 1000 (I don't want to use varchar(max) for no good reason)
You can change both of these assumptions if you wish
declare #separator char(1) = char(10)
select substring
(
replace
(
cast
(
cast(DATA as xml).query
(
'for $i in //*
where not($i/*)
return concat
(
sql:variable("#separator")
,if(local-name($i) = "ID") then ('''''''') else ('''')
,($i/text())[1]
,if(local-name($i) = "ID") then ('''''''') else ('''')
)'
) as nvarchar(1000)
) ,' ' + #separator ,','
) ,2 ,1000
) as csv
from EXPORT_TEST
INSERT INTO EXPORT_TEST (DATA) VALUES
('<EXPORT_DATA><ID>ABC123</ID><PRICE_A>5.6</PRICE_A><PRICE_B></PRICE_B><PRICE_C>8.1</PRICE_C></EXPORT_DATA>')
,('<EXPORT_DATA><ID>DEF456</ID><PRICE_A>6.7</PRICE_A><PRICE_B>66.77</PRICE_B><PRICE_C>7.2</PRICE_C></EXPORT_DATA>')
,('<EXPORT_DATA><ID>GHI789</ID><PRICE_A></PRICE_A><PRICE_B>88.99</PRICE_B><PRICE_C></PRICE_C></EXPORT_DATA>')
csv
'ABC123',5.6,,8.1
'DEF456',6.7,66.77,7.2
'GHI789',,88.99,
What about this:
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
), cols AS (
SELECT
rownr,
Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('EXPORT_DATA/child::node()') Tbl(Col)
)
INSERT INTO EXPORT_TEST(DATA)
SELECT DISTINCT
STUFF((
SELECT ',' + IIF(ISNUMERIC(value) = 1 OR LEN(value) = 0, Value, '''' + Value + '''')
FROM cols SSF WHERE SSF.rownr = S.rownr
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)'
), 1, 1, '') as DATA
FROM cols S
Using cte.xml.nodes('EXPORT_DATA/child::node()') in the second CTE will give as all nodes:
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
)
SELECT
rownr
,Tbl.Col.query('.')
,Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('EXPORT_DATA/child::node()') Tbl(Col)
Then, in the concatenation we need to add check for empty string:
IIF(ISNUMERIC(value) = 1 OR LEN(value) = 0, Value, '''' + Value + '''')

SQL select string between known identical characters

I have code which selects the string between the first and second '/' in a typical string which may look like this:
2014-Ceilings/Ceilings/Repair/Asbestos/Supalux
The following code correctly returns the value 'Ceilings' in the above example.
SELECT
REPLACE (LEFT(SUBSTRING(ElementPath,CHARINDEX
('/',ElementPath)+1,LEN(ElementPath)),CHARINDEX
('/',SUBSTRING(ElementPath, CHARINDEX
('/',ElementPath)+1,LEN(ElementPath)))),'/','')
FROM K2_Master.dbo.tbElement
How can I amend it to select the string between the second and third '/' to return the value 'Repair'?
Check this little trick may work :
DECLARE #String VARCHAR(100)= '2014-Ceilings/Ceilings/Repair/Asbestos/Supalux'
SET #String = '<N>' + Replace(#String, '/', '</N><N>')
+ '</N>'
SELECT c.value('/N[1]', 'varchar(30)'),
c.value('/N[2]', 'varchar(30)'),
c.value('/N[3]', 'varchar(30)')
FROM (SELECT Cast(#String AS XML)) t(c)
SELECT c1.value('.', 'varchar(30)')
FROM (SELECT CAST(#String AS XML)) t(c)
CROSS APPLY c.nodes('/N') AS t1(c1)
SELECT value
FROM (SELECT c1.value('.', 'varchar(30)') AS value,
ROW_NUMBER()
OVER(
ORDER BY (SELECT 1)) rn
FROM (SELECT Cast(#String AS XML)) t(c)
CROSS APPLY c.nodes('/N') AS t1(c1)) temp
WHERE rn = 3
In your case
SELECT c.value('/N[1]', 'varchar(30)'),
c.value('/N[2]', 'varchar(30)'),
c.value('/N[3]', 'varchar(30)')
FROM (SELECT Cast ('<N>' + Replace( Replace (ElementPath, '/', '</N><N>'),'&','&')
+ '</N>' AS XML)
FROM K2_Master.dbo.tbElement) t(c)
I chased your question, and reached a lengthy result..
DECLARE #a VARCHAR(40) = '2014-Ceilings/Ceilings/Repair/Asbestos/Supalux'
SELECT #a
,LEFT(
SUBSTRING(SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)),CHARINDEX('/',SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)),1)+1,LEN(SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)))),
CHARINDEX('/',SUBSTRING(SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)),CHARINDEX('/',SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)),1)+1,LEN(SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)))),1) - 1
)
Result:
Answer to you comment.
DECLARE #String VARCHAR(100)= '2014-Ceilings/Ceilings/Repair/Asbestos/Supalux',
#value INT = 2
DECLARE #itra INT = 1
WHILE #itra <= #value
BEGIN
SET #String = (SELECT SUBSTRING(#String,CHARINDEX('/',#String,1)+1,LEN(#String)))
--SELECT #String
SET #itra = #itra + 1
END
SELECT LEFT(#String, CHARINDEX('/',#String,1) - 1)
Give the position of '/' as #value and see the result. For the above sql, the result is Repair. If you give #value = 3, result is Asbestos

Select query from XML data

I am trying to get result from XML data but only getting a value of first node.
create table #temp(xmlString nvarchar(max))
insert into #temp (xmlString) values
('<?xml version="1.0" ?><response status = "ERROR">
<error>Error1</error>
<error>Error2</error>
</response>')
I want a result :
Error1, Error2
Please help.
Thanks
select
x.c.value('.', 'nvarchar(128)') as value
from (select cast(xmlString as xml) as data from temp) as t
outer apply t.data.nodes('/response/error') as x(c)
SQL FIDDLE EXAMPLE
correct answer
select STUFF((select ',' + x.c.value('.', 'nvarchar(max)')
from (select cast(xmlString as xml) as data from #temp)
as t outer apply t.data.nodes('/response/error')
as x(c)for xml path('')), 1, 1, '') as Errors

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>