Rename xml element with child elements in MS SQL - sql-server

I try to rename element <Visible> to <IsVisible>, but this SELECT returns element Visible without child elements, how can I get Visible with UserId and RoleId elements?
DECLARE #xml XML =
N'<Root xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<FieldId>2200</FieldId>
<Visible xsi:type="UserRole">
<UserId xsi:type="CurrentUserId" />
<RoleId>26</RoleId>
</Visible>
</Root>';
SELECT #xml.query(N'let $nd:=(//*[local-name()="Visible"])[1]
return
<IsVisible> {$nd/#*}
{$nd/text()}
</IsVisible>
')

Try this.
DECLARE #xml XML =
N'<Root xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<FieldId>2200</FieldId>
<Visible xsi:type="UserRole">
<UserId xsi:type="CurrentUserId" />
<RoleId>26</RoleId>
</Visible>
</Root>';
SELECT #xml.query(N'let $nd:=(//*[local-name()="Visible"])[1]
return
<IsVisible> {$nd/#*}
{$nd/*}
</IsVisible>
')

You can either use XQuery (FLWOR) or a simple replace
DECLARE #xml XML =
N'<Root xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<FieldId>2200</FieldId>
<Visible xsi:type="UserRole">
<UserId xsi:type="CurrentUserId" />
<RoleId>26</RoleId>
</Visible>
</Root>';
--Works, but will reorganise your namespace declarations
WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema' AS xsd
,'http://www.w3.org/2001/XMLSchema-instance' AS xsi)
SELECT #xml.query(N'<Root>
{
for $nd in /Root/*
return
if(local-name($nd)!="Visible") then
$nd
else
<IsVisible>{$nd/#*}
{$nd/*}
</IsVisible>
}
</Root>
');
--Might be easier here
SELECT CAST(
REPLACE(REPLACE(
CAST(#xml AS NVARCHAR(MAX))
,'<Visible ','<IsVisible ')
,'</Visible>','</IsVisible>')
AS XML)

Related

Relocate node with value in XML if not exists in mssql

I've got table in mssql, and one column of it contains XML. Most of XML in this column looks like this:
<AuthenticationParams xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyParams>
<Username>AmaryllisAPITest</Username>
<ApplicationId>3</ApplicationId>
</MyParams>
<AlsoParams>
<AuthBehavior>Authorization</AuthBehavior>
<SecretKey>MVHXAQA5kF4Ab9siV4vPA4aVPn1EKhbqIBrpCZx2Hg</SecretKey>
</AlsoParams>
</AuthenticationParams>
I want to relocate AuthBehavior node right after AuthenticationParams node, so it will look like this:
<AuthenticationParams xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<AuthBehavior>Authorization</AuthBehavior>
<MyParams>
<Username>AmaryllisAPITest</Username>
<ApplicationId>3</ApplicationId>
</MyParams>
<AlsoParams>
<SecretKey>MVHXAQA5kF4Ab9siV4vPA4aVPn1EKhbqIBrpCZx2Hg</SecretKey>
</AlsoParams>
</AuthenticationParams>
How can I do that? Thanks for any help.
Try this code:
DECLARE #xml xml = '<AuthenticationParams xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyParams>
<Username>AmaryllisAPITest</Username>
<ApplicationId>3</ApplicationId>
</MyParams>
<AlsoParams>
<AuthBehavior>Authorization</AuthBehavior>
<SecretKey>MVHXAQA5kF4Ab9siV4vPA4aVPn1EKhbqIBrpCZx2Hg</SecretKey>
</AlsoParams>
</AuthenticationParams>';
DECLARE #temp TABLE (XmlData xml);
INSERT #temp VALUES (#xml);
UPDATE t
SET XmlData.modify('insert /AuthenticationParams/AlsoParams/AuthBehavior
as first
into (/AuthenticationParams)[1]')
FROM #temp t;
UPDATE t
SET XmlData.modify('delete /AuthenticationParams/AlsoParams/AuthBehavior')
FROM #temp t;
SELECT * FROM #temp;

I need to set up xml tags in some order

I have xml like this:
<tt>
<cpost/>
<cpost/>
<gx2tov />
<kodp/>
<Sertif1/>
<Sertif1/>
<slmat/>
</tt>
But I need to change it to:
<tt>
<slmat/>
<cpost/>
<cpost/>
<kodp/>
<gx2tov />
<Sertif1/>
<Sertif1/>
</tt>
How can I do that using T SQL?
There is an accepted answer already but it will work with empty nodes only. In this case you could just type in the XML as you need it...
Some more approaches:
I added some id-attributes to distinguish the identical elements
DECLARE #xml XML=
'<tt>
<cpost id="1"/>
<cpost id="2"/>
<gx2tov/>
<kodp/>
<Sertif1 id="1"/>
<Sertif1 id="2"/>
<slmat/>
</tt>';
Place <slmat> at the top
You can use .query() to get a full node, which you can use AS [node()] in a SELECT ... FOR XML PATH(). This query will place <slmat> at the top, and will place all with a different name behind (original order):
SELECT #xml.query(N'/tt/slmat') AS [node()]
,#xml.query(N'/tt/*[local-name()!="slmat"]') AS [node()]
FOR XML PATH(N'tt');
That's the same as above, but as one single XQuery
SELECT #xml.query
('
<tt>
{tt/slmat}
{tt/*[local-name()!="slmat"]}
</tt>
');
Set a specific order for all nodes explicitly
If you want to set a specific order for your elements, you might place them explicitly (now I use .nodes(), but you could go without too - as above):
SELECT tt.query(N'slmat') AS [node()]
,tt.query(N'cpost') AS [node()]
,tt.query(N'kodp') AS [node()]
,tt.query(N'gx2tov') AS [node()]
,tt.query(N'Sertif1') AS [node()]
FROM #xml.nodes(N'/tt') AS A(tt)
FOR XML PATH(N'tt');
As XQuery (now with a variable, but you could use the root directly - as above)
SELECT #xml.query
('
let $t:=/tt
return
<tt>
{$t/slmat}
{$t/cpost}
{$t/kodp}
{$t/gx2tov}
{$t/Sertif1}
</tt>
');
All have the same result...
You can delete and insert nodes:
DECLARE #xml XML = N'<tt>
<cpost/>
<cpost/>
<gx2tov />
<kodp/>
<Sertif1/>
<Sertif1/>
<slmat/>
</tt>'
SET #xml.modify('delete tt/slmat[1]')
SET #xml.modify('insert <slmat /> as first into tt[1]')
SELECT #xml
Or, you can reorder all your nodes:
DECLARE #xml XML = N'<tt>
<cpost/>
<cpost/>
<gx2tov />
<kodp/>
<Sertif1/>
<Sertif1/>
<slmat/>
</tt>'
SELECT
CONVERT(xml, '<' +t.value('local-name(.)','nvarchar(max)') + ' />')
FROM #xml.nodes('*/*') AS t(t)
ORDER BY t.value('local-name(.)','nvarchar(max)')
FOR XML PATH(''), TYPE, ROOT('tt')

Retrieving an XML node value with TSQL?

What am I not getting here? I can't get any return except NULL...
DECLARE #xml xml
SELECT #xml = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<webregdataResponse>
<result>0</result>
<regData />
<errorFlag>99</errorFlag>
<errorResult>Not Processed</errorResult>
</webregdataResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>'
DECLARE #nodeVal int
SELECT #nodeVal = #xml.value('(errorFlag)[1]', 'int')
SELECT #nodeVal
Here is the solution:
DECLARE #xml xml
SELECT #xml = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<webregdataResponse>
<result>0</result>
<regData />
<errorFlag>99</errorFlag>
<errorResult>Not Processed</errorResult>
</webregdataResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>'
declare #table table (data xml);
insert into #table values (#xml);
WITH xmlnamespaces (
'http://schemas.xmlsoap.org/soap/envelope/' as [soap])
SELECT Data.value('(/soap:Envelope/soap:Body/webregdataResponse/errorFlag)[1]','int') AS ErrorFlag
FROM #Table ;
Running the above SQL will return 99.
Snapshot of the result is given below,
That's because errorFlag is not the root element of your XML document. You can either specify full path from root element to errorFlag, for example* :
SELECT #nodeVal = #xml.value('(/*/*/*/errorFlag)[1]', 'int')
or you can use descendant-or-self axis (//) to get element by name regardless of it's location in the XML document, for example :
SELECT #nodeVal = #xml.value('(//errorFlag)[1]', 'int')
*: I'm using * instead of actual element name just to simplify the expression. You can also use actual element names along with the namespaces, like demonstrated in the other answer.

Query XML with stylesheet/namespace included in the xml

I have tried several ways to query this data out, but have not been successful. I am on SQL Server 2012. Any help would be appreciated.
<NewDataSet>
<Table>
<_x005B_M_x005D_._x005B_SEQID_x005D_ xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:short">200</_x005B_M_x005D_._x005B_SEQID_x005D_>
<_x005B_M_x005D_._x005B_CPID_x005D_ xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">1002</_x005B_M_x005D_._x005B_CPID_x005D_>
</Table>
</NewDataSet>
It would help to have more details on what exactly you want to get out of this, but here is a start, assuming each Element of the XML represents a row:
DECLARE #SampleData XML = N'
<NewDataSet>
<Table>
<_x005B_M_x005D_._x005B_SEQID_x005D_ xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:short">200</_x005B_M_x005D_._x005B_SEQID_x005D_>
<_x005B_M_x005D_._x005B_CPID_x005D_ xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">1002</_x005B_M_x005D_._x005B_CPID_x005D_>
</Table>
</NewDataSet>
';
DECLARE #Delim VARCHAR(50) = '._x005B_';
DECLARE #DelimLen INT = LEN(#Delim);
;WITH cte AS
(
SELECT xrow.value('local-name(.)', 'VARCHAR(50)') AS [ElementName],
xrow.value('declare namespace xsi="http://www.w3.org/2001/XMLSchema-instance"; (./#xsi:type)[1]', 'VARCHAR(50)') AS [xsi:type],
xrow.value('./text()[1]', N'VARCHAR(50)') AS [ElementValue]
FROM #SampleData.nodes('NewDataSet/Table/*') t(xrow)
)
SELECT *,
SUBSTRING(
cte.ElementName,
CHARINDEX(#Delim, cte.ElementName) + #DelimLen,
CHARINDEX('_',
cte.ElementName,
CHARINDEX(#Delim, cte.ElementName) + #DelimLen + 1) -
(CHARINDEX(#Delim, cte.ElementName) + #DelimLen)
) AS [RowType]
FROM cte;
Output:
ElementName xsi:type ElementValue RowType
_x005B_M_x005D_._x005B_SEQID_x005D_ xs:short 200 SEQID
_x005B_M_x005D_._x005B_CPID_x005D_ xs:string 1002 CPID
declare #demo xml = '<NewDataSet>
<Table>
<_x005B_M_x005D_._x005B_SEQID_x005D_ xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:short">200</_x005B_M_x005D_._x005B_SEQID_x005D_>
<_x005B_M_x005D_._x005B_CPID_x005D_ xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">1002</_x005B_M_x005D_._x005B_CPID_x005D_>
</Table>
</NewDataSet>'
select t.r.value('(./*[local-name()=''_x005B_M_x005D_._x005B_SEQID_x005D_'']/text())[1]','integer') seqid
, t.r.value('(./*[local-name()=''_x005B_M_x005D_._x005B_CPID_x005D_'']/text())[1]','nvarchar(128)') cpid
from #demo.nodes('/*/*') t(r)
SQL Fiddle: http://sqlfiddle.com/#!6/d41d8/21769

Variable usage in XML query

Please check below query.
declare #xmlRoot as xml
set #xmlRoot= '<Root>
<table1 col1="2012-03-02T16:42:55.777">
<table2Array>
<Table2 col2="abc">
</Table2>
<Table2 col2="def">
</Table2>
</table2Array>
</table1>
<table1 col1="2012-03-02T17:42:55.777">
<table2Array>
<Table2 col2="abc1">
</Table2>
<Table2 col2="def1">
</Table2>
</table2Array>
</table1>
</Root>'
declare #a as varchar(1)
set #a= '1'
SELECT
col1 = item.value('./#col2', 'varchar(10)')
FROM #xmlRoot.nodes('Root/table1[1]/table2Array/Table2' ) AS T(item);
--The above query return expected output
SELECT
col1 = item.value('./#col2', 'varchar(10)')
FROM #xmlRoot.nodes('Root/table1[*[local-name()=sql:variable("#a")]]/table2Array/Table2' )
AS T(item);
--The above query doesn't return expected output
what am I doing wrong here?
Since I dont have a key value in parent node to identify child node. I have to parse through index.
This worked for me:
DECLARE #a INT; -- data type is probably important!
SET #a = 1;
SELECT col1 = item.value('./#col2', 'varchar(10)')
FROM #xmlRoot.nodes('Root/table1[sql:variable("#a")]/table2Array/Table2') AS T(item);

Resources