SQL XML Replacing elements - sql

Please help! Is it possible to replace elements within an xml field of an sql database with other elements. I have tried using .modify(replace value of) but I can only replace text within elements rather than nodes.
Ultimately I am trying to update an element which may or may not contain other elements, with another element (possibly of the same name) within an XML field. (I am using SQL Server 2008)
E.g:
<Root>
<Sub>
<Value1>
</Value1>
<Value2>
</Value2>
<Value3>
</Value3>
</Sub>
</Root>
Would be replaced by:
<Root>
<SubVERSION2>
<Value1>
</Value1>
<Value2>
</Value2>
<Value3>
</Value3>
</SubVERSION2>
</Root>
Any help would be very much appreciated!

You can recreate your XML:
declare #x xml = '<Root>
<Sub>
<Value1>1
</Value1>
<Value2>2
</Value2>
<Value3>3
</Value3>
</Sub>
</Root>'
select cast(('<Root>' +
cast(
(
select t.c.query('.')
from #x.nodes('Root/Sub/*') t(c)
for xml path(''), root('SubVERSION2')
) as nvarchar(max)) + '</Root>') as xml)
produces desired output:
<Root>
<SubVERSION2>
<Value1>1
</Value1>
<Value2>2
</Value2>
<Value3>3
</Value3>
</SubVERSION2>
</Root>

declare #T table(XMLCol xml)
insert into #T values ('
<Root>
<Sub>
<Value1></Value1>
<Value2></Value2>
<Value3></Value3>
</Sub>
</Root>')
update #T set
XMLCol = XMLCol.query('for $s in Root/Sub
return
<Root>
<SubVERSION2>
{ $s/* }
</SubVERSION2>
</Root>')
Result:
<Root>
<SubVERSION2>
<Value1 />
<Value2 />
<Value3 />
</SubVERSION2>
</Root>

Related

How to put an attribute on the root element, and only the root element, in FOR XML PATH?

I'm generating XML from a SQL Server table.
This is my code:
;WITH XMLNAMESPACES
(
'http://www.w3.org/2001/XMLSchema-instance' AS xsi
--,DEFAULT 'http://www.w3.org/2001/XMLSchema-instance' -- xmlns
)
SELECT
'T_Contracts' AS "#tableName",
(SELECT * FROM T_Contracts
FOR XML PATH('row'), TYPE, ELEMENTS xsinil)
FOR XML PATH('table'), TYPE, ELEMENTS xsinil
I want the result to look like this (note: attribute tableName on the root element):
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row>
<VTR_UID>779FE899-4E81-4D8C-BF9B-3F17BC1DF146</VTR_UID>
<VTR_MDT_ID>0</VTR_MDT_ID>
<VTR_VTP_UID xsi:nil="true" />
<VTR_Nr>0050/132251</VTR_Nr>
</row>
</table>
But it duplicates the XSI namespace on the row element...
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<VTR_UID>779FE899-4E81-4D8C-BF9B-3F17BC1DF146</VTR_UID>
<VTR_MDT_ID>0</VTR_MDT_ID>
<VTR_VTP_UID xsi:nil="true" />
<VTR_Nr>0050/132251</VTR_Nr>
</row>
</table>
What's the correct way to add an attribute to the root element, and only the root element ?
Note
NULL-values must be returned as <columnName xsi:nil="true" /> and not be omitted.
(And no xml.modify after the select)
Please note that this is NOT a duplicate of an existing question.
This annoying behaviour of repeated namespaces with sub-queries was a reported issue for more than 10 years on MS-Connect with thousands of votes. This platform was dismissed, so was this issue and there is no perspective that MS will ever solve this.
Just to be fair: It is not wrong to repeat the namespace declaration. It's just bloating the string-based output...
Even stranger is the the unsupported attribute on a root level node...
Well, if you need a head-ache, you might look into OPTION EXPLICIT :-)
The accepted answer by Marc Guillot will not produce xsi:nil="true" attributes as you seem to need them. It will just wrap your result with the appropriate root node.
Finally: This cannot be solved with XML methods, you can try this:
Update: Found a way, see below...
DECLARE #tbl TABLE(ID INT,SomeValue INT);
INSERT INTO #tbl VALUES(1,1),(2,NULL);
SELECT CAST(REPLACE(CAST(
(
SELECT *
FROM #tbl
FOR XML PATH('row'),ROOT('table'),TYPE, ELEMENTS XSINIL
) AS nvarchar(MAX)),'<table ','<table tableName="T_Contracts" ') AS XML);
The result
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row>
<ID>1</ID>
<SomeValue>1</SomeValue>
</row>
<row>
<ID>2</ID>
<SomeValue xsi:nil="true" />
</row>
</table>
The idea in short:
We create the XML without a sub-query and add the attribute with a string method into the casted XML.
As the position of an attribute is not important, we can add it everywhere.
alternatively you might search for the first closing > and use STUFF() there...
UPDATE
Heureka, I just found a way, to create this without swithing to string, but it's clumsy :-)
DECLARE #tbl TABLE(ID INT,SomeValue INT);
INSERT INTO #tbl VALUES(1,1),(2,NULL);
SELECT
(
SELECT 'T_Contracts' AS [#tableName]
,(
SELECT 'SomeRowAttr' AS [#testAttr] --added this to test row-level attributes
,*
FROM #tbl
FOR XML PATH('row'),TYPE, ELEMENTS XSINIL
)
FOR XML PATH('table'),TYPE, ELEMENTS XSINIL
).query('<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">{/table/#*}
{
for $nd in /table/row
return
<row>{$nd/#*}
{
$nd/*
}
</row>
}
</table>');
The result
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row testAttr="SomeRowAttr">
<ID>1</ID>
<SomeValue>1</SomeValue>
</row>
<row testAttr="SomeRowAttr">
<ID>2</ID>
<SomeValue xsi:nil="true" />
</row>
</table>
Why don't you build manually the root element ?
Example:
with CTE as (
select (select * from T_Contracts for xml path('row')) as MyXML
)
select '<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">' +
MyXML +
'</table>'
from CTE
Unfortunately you cannot do this with the SQL Server out of the box nor exists an elegant way to do that. To alleviate the issue, you can replace NULLs with empty strings. This will remove xmlns, but you have to define your select list explicitly as follows. Moreover, this works only with character string data types as you cannot assign an empty string ('' in ISNULL function) to-for example-an integer.
;WITH XMLNAMESPACES
(
'http://www.w3.org/2001/XMLSchema-instance' AS xsi
--,DEFAULT 'http://www.w3.org/2001/XMLSchema-instance' -- xmlns
)
SELECT 'T_Contracts' AS "#tableName",
(
SELECT
ISNULL(VTR_UID, '') 'row/VTR_UID'
,ISNULL(VTR_MDT_ID, '') 'row/VTR_MDT_ID'
,ISNULL(VTR_VTP_UID, '') 'row/VTR_VTP_UID'
,ISNULL(VTR_Nr, '') 'row/VTR_Nr'
FROM T_Contracts
FOR XML PATH(''), TYPE
)
FOR XML PATH('table'), TYPE, ELEMENTS xsinil
The result will be like below:
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" tableName="T_Contracts">
<row>
<VTR_UID>779FE899-4E81-4D8C-BF9B-3F17BC1DF146</VTR_UID>
<VTR_MDT_ID>0</VTR_MDT_ID>
<VTR_VTP_UID />
<VTR_Nr>0050/132251</VTR_Nr>
</row>
</table>

OpenXML returning NULL

I am trying to import xml into my database with the following query using OpenXML in Microsoft SQL Server:
DECLARE #xml XML;
DECLARE #y INT;
SET #xml
= '<ArrayOfArticle xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Article>
<ScriptId xmlns="https://test.com/">5135399</ScriptId>
<Title xmlns="https://test.com/">Stocks divided into two corners</Title>
<Mediatype xmlns="https://test.com/">News papeer</Mediatype>
<Abstract xmlns="https://test.com/">Foreign capital doubled this year.</Abstract>
<ScriptDate xmlns="https://test.com/">2017-12-30T00:00:00</ScriptDate>
<ScriptTypeId xmlns="https://test.com/">1</ScriptTypeId>
<ScriptType xmlns="https://test.com/">News general</ScriptType>
<Media xmlns="https://test.com/">Times</Media>
<ArticleUrl xmlns="https://test.com/">http://test.com</ArticleUrl>
<AnalysisResult xmlns="https://test.com/">
<Analysis>
<Regno>111</Regno>
<Name>New York Times</Name>
<Result>1</Result>
<ResultName>Positive</ResultName>
</Analysis>
<Analysis>
<Regno>222</Regno>
<Name>Washington Post</Name>
<Result>1</Result>
<ResultName>Negative</ResultName>
</Analysis>
</AnalysisResult>
<FacebookStats xmlns="https://test.com/">
<ShareCount xsi:nil="true" />
<LikeCount xsi:nil="true" />
<CommentCount xsi:nil="true" />
<TotalCount xsi:nil="true" />
</FacebookStats>
<MediaScore xmlns="https://test.com/">
<MediaScore>
<Regno>111</Regno>
<CompanyName>New York Times</CompanyName>
<MediaScoreID>2</MediaScoreID>
<Name>Neither</Name>
</MediaScore>
<MediaScore>
<Regno>222</Regno>
<CompanyName>Washington Post</CompanyName>
<MediaScoreID>2</MediaScoreID>
<Name>Neither</Name>
</MediaScore>
</MediaScore>
<Page xmlns="https://test.com/">26</Page>
<ProgramId xmlns="https://test.com/">0</ProgramId>
<ProgramTime xmlns="https://test.com/" xsi:nil="true" />
<ProgramLength xmlns="https://test.com/">0</ProgramLength>
<ProgramOrder xmlns="https://test.com/">0</ProgramOrder>
</Article>
</ArrayOfArticle>';
EXEC sp_xml_preparedocument #y OUTPUT, #xml;
SELECT *
FROM
OPENXML(#y, '/ArrayOfArticle/Article', 1)
WITH
(
ScriptId VARCHAR(20),
Title VARCHAR(30),
Mediatype VARCHAR(30)
);
The query however only returns NULL values. What am I missing here? Would it be optimal to import the XML using SSIS instead. Not sure how much more details I can give at the given hour.
Do not use FROM OPENXML. This approach (together with the corresponding SPs to prepare and to remove a document) is outdated and should not be used any more.
Try the XML type's native methods, in this case .value():
Your XML is rather weird - concerning namespaces. If its creation is under your control you should try to clean this namespace mess. The unusual thing is, that your XML declares default namespaces over and over.
You can use the deep search with // together with a namespace wildcard *:
--GetItEasyCheesy (not recommended)
SELECT #xml.value(N'(//*:ScriptId)[1]',N'int') AS ScriptId
,#xml.value(N'(//*:Title)[1]',N'nvarchar(max)') AS Title
,#xml.value(N'(//*:Mediatype )[1]',N'nvarchar(max)') AS Mediatype ;
You can declare the namespace as default, but in this case you must wildcard the outer elements, as they are not part of this namespace:
--Use a default namespace
WITH XMLNAMESPACES(DEFAULT 'https://test.com/')
SELECT #xml.value(N'(/*:ArrayOfArticle/*:Article/ScriptId/text())[1]',N'int') AS ScriptId
,#xml.value(N'(/*:ArrayOfArticle/*:Article/Title/text())[1]',N'nvarchar(max)') AS Title
,#xml.value(N'(/*:ArrayOfArticle/*:Article/Mediatype/text())[1]',N'nvarchar(max)') AS Mediatype;
The recommended approach is to bind the inner namespace to a prefix and use this
--Recommended
WITH XMLNAMESPACES('https://test.com/' AS ns)
SELECT #xml.value(N'(/ArrayOfArticle/Article/ns:ScriptId/text())[1]',N'int') AS ScriptId
,#xml.value(N'(/ArrayOfArticle/Article/ns:Title/text())[1]',N'nvarchar(max)') AS Title
,#xml.value(N'(/ArrayOfArticle/Article/ns:Mediatype/text())[1]',N'nvarchar(max)') AS Mediatype;
If your <ArrayOfArticles> contains more than one <Article> you can use .nodes() to get alle of them as derived table. In this case the query is
WITH XMLNAMESPACES('https://test.com/' AS ns)
SELECT art.value(N'(ns:ScriptId/text())[1]',N'int') AS Recommended
,art.value(N'(ns:Title/text())[1]',N'nvarchar(max)') AS Title
,art.value(N'(ns:Mediatype/text())[1]',N'nvarchar(max)') AS Mediatype
FROM #xml.nodes(N'/ArrayOfArticle/Article') AS A(art);
your XML contains namespaces, I'd use xquery in order to extract the data from your XML
UPDATE with additional elements extract
DECLARE #xml XML;
SET #xml
= '<ArrayOfArticle xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Article>
<ScriptId xmlns="https://test.com/">5135399</ScriptId>
<Title xmlns="https://test.com/">Stocks divided into two corners</Title>
<Mediatype xmlns="https://test.com/">News papeer</Mediatype>
<Abstract xmlns="https://test.com/">Foreign capital doubled this year.</Abstract>
<ScriptDate xmlns="https://test.com/">2017-12-30T00:00:00</ScriptDate>
<ScriptTypeId xmlns="https://test.com/">1</ScriptTypeId>
<ScriptType xmlns="https://test.com/">News general</ScriptType>
<Media xmlns="https://test.com/">Times</Media>
<ArticleUrl xmlns="https://test.com/">http://test.com</ArticleUrl>
<AnalysisResult xmlns="https://test.com/">
<Analysis>
<Regno>111</Regno>
<Name>New York Times</Name>
<Result>1</Result>
<ResultName>Positive</ResultName>
</Analysis>
<Analysis>
<Regno>222</Regno>
<Name>Washington Post</Name>
<Result>1</Result>
<ResultName>Negative</ResultName>
</Analysis>
</AnalysisResult>
<FacebookStats xmlns="https://test.com/">
<ShareCount xsi:nil="true" />
<LikeCount xsi:nil="true" />
<CommentCount xsi:nil="true" />
<TotalCount xsi:nil="true" />
</FacebookStats>
<MediaScore xmlns="https://test.com/">
<MediaScore>
<Regno>111</Regno>
<CompanyName>New York Times</CompanyName>
<MediaScoreID>2</MediaScoreID>
<Name>Neither</Name>
</MediaScore>
<MediaScore>
<Regno>222</Regno>
<CompanyName>Washington Post</CompanyName>
<MediaScoreID>2</MediaScoreID>
<Name>Neither</Name>
</MediaScore>
</MediaScore>
<Page xmlns="https://test.com/">26</Page>
<ProgramId xmlns="https://test.com/">0</ProgramId>
<ProgramTime xmlns="https://test.com/" xsi:nil="true" />
<ProgramLength xmlns="https://test.com/">0</ProgramLength>
<ProgramOrder xmlns="https://test.com/">0</ProgramOrder>
</Article>
</ArrayOfArticle>'
DECLARE #T TABLE (XmlCol XML)
INSERT INTO #T
SELECT #xml
;WITH XMLNAMESPACES ('https://test.com/' as p1)
SELECT z.t.value ('../../p1:ScriptId[1]',' varchar(100)') ScriptId,
z.t.value ('../../p1:Title[1]',' varchar(100)') Title,
z.t.value ('../../p1:Mediatype[1]',' varchar(100)') Mediatype,
z.t.value ('p1:CompanyName[1]', 'varchar(100)') CompanyName
FROM #T t
CROSS APPLY XmlCol.nodes ('/ArrayOfArticle/Article/p1:MediaScore/p1:MediaScore') z(t)
DECLARE #y INT
EXEC sp_xml_preparedocument #y OUTPUT, #xml,
'<ns xmlns:x="https://test.com/"/>'
SELECT *
FROM
OPENXML(#y, '/ArrayOfArticle/Article', 2)
WITH
(
[ScriptId] VARCHAR(20) 'x:ScriptId', --<< and so on
[Title] VARCHAR(30),
Mediatype VARCHAR(30)
)
EXEC sp_xml_removedocument #y --<< lost in your code

Save XML with attribute to Table in SQL Server

Hi I have XML data with attribute as input for SQL, i need this to be inserted in my table.
XML Data is
<?xml version="1.0" encoding="ISO-8859-1"?>
<MESSAGEACK>
<GUID GUID="kfafb30" SUBMITDATE="2015-10-15 11:30:29" ID="1">
<ERROR SEQ="1" CODE="28681" />
</GUID>
<GUID GUID="kfafb3" SUBMITDATE="2015-10-15 11:30:29" ID="1">
<ERROR SEQ="2" CODE="286381" />
</GUID>
</MESSAGEACK>
I want this to be inserted in below Format
GUID SUBMIT DATE ID ERROR SEQ CODE
kfafb3 2015-10-15 11:30:29 1 1 28681
kfafb3 2015-10-15 11:30:29 1 1 2868
please help.
Look into XPath and xml Data Type Methods in MSDN. This is one possible way :
declare #xml As XML = '...you XML string here...'
INSERT INTO YourTable
SELECT
guid.value('#GUID', 'varchar(100)') as 'GUID'
,guid.value('#SUBMITDATE', 'datetime') as 'SUBMIT DATE'
,guid.value('#ID', 'int') as 'ID'
,guid.value('ERROR[1]/#SEQ', 'int') as 'SEQ'
,guid.value('ERROR[1]/#CODE', 'int') as 'CODE'
FROM #xml.nodes('/MESSAGEACK/GUID') as x(guid)
Result :
just paste this into an empty query window and execute. Adapt to your needs:
DECLARE #xml XML=
'<?xml version="1.0" encoding="ISO-8859-1"?>
<MESSAGEACK>
<GUID GUID="kfafb30" SUBMITDATE="2015-10-15 11:30:29" ID="1">
<ERROR SEQ="1" CODE="28681" />
</GUID>
<GUID GUID="kfafb3" SUBMITDATE="2015-10-15 11:30:29" ID="1">
<ERROR SEQ="2" CODE="286381" />
</GUID>
</MESSAGEACK>';
SELECT Msg.Node.value('#GUID','varchar(max)') AS [GUID] --The value is no GUID, if the original values are, you could use uniqueidentifier instead of varchar(max)
,Msg.Node.value('#SUBMITDATE','datetime') AS SUBMITDATE
,Msg.Node.value('#ID','int') AS ID
,Msg.Node.value('(ERROR/#SEQ)[1]','int') AS [ERROR SEQ]
,Msg.Node.value('(ERROR/#CODE)[1]','int') AS CODE
FROM #xml.nodes('/MESSAGEACK/GUID') AS Msg(Node)

Parse the XML in SQL Server

<ROOT>
<arn>arn001</arn>
<arn>arn002</arn>
</ROOT>
Tried the following code though
SELECT
ARN.value('(//arn/text())[1]','VARCHAR(100)') AS arns --TAG
FROM
#xml.nodes('/ROOT')AS TEMPTABLE(ARN)
It returns only first value
Try this way :
declare #xml xml = '<ROOT>
<arn>arn001</arn>
<arn>arn002</arn>
</ROOT>'
SELECT
X.value('.','VARCHAR(100)') AS arns
FROM
#xml.nodes('/ROOT/arn')AS T(X)
SQL Fiddle

Parse XML with multilevel nesting in SQL

I'm trying to Parse some XML having multiple nesting levels in SQL. The problem I hit into here is how to write my query generic enough for it to Parse the XML below without having to hard-code the node path e:g
EXEC sp_xml_preparedocument #handle OUTPUT, #xml
INSERT #Root(ID)
SELECT *
FROM OPENXML(#handle, '/Root')
WITH (ID VARCHAR(100))
INSERT #ConditionSet(Operator)
SELECT *
FROM OPENXML(#handle, '/Root/ConditionSet')
WITH (Operator VARCHAR(100))
INSERT #ConditionSet(Operator)
SELECT *
FROM OPENXML(#handle, '/Root/ConditionSet/ConditionSet')
WITH (Operator VARCHAR(100))
INSERT #ConditionSet(Operator)
SELECT *
FROM OPENXML(#handle, '/Root/ConditionSet/ConditionSet/ConditionSet')
WITH (Operator VARCHAR(100))
Is there a better way to Parse the following XML in SQL and represent all data in tabular form?
<?xml version="1.0"?>
-<Root ID="414141" Source="AudienceBuilder">
-<ConditionSet Operator="I">
-<Condition Operator="E" ID="74373">
-<Relationship ID="56756">
<Relationship ID="67868"/>
</Relationship>
-<Value>
<![CDATA[ABC]]>
</Value>
</Condition>
-<ConditionSet Operator="O">
-<Condition Operator="E" ID="6566">
-<Relationship ID="7658">
<Relationship ID="6547"/>
</Relationship>
-<Value>
<![CDATA[DEF]]>
</Value>
</Condition>
-<Condition Operator="E" ID="96967">
-<Relationship ID="3884">
<Relationship ID="9954"/>
</Relationship>
-<Value>
<![CDATA[GHI]]>
</Value>
</Condition>
-<ConditionSet Operator="A">
-<Condition Operator="E" ID="31654">
-<Relationship ID="57894">
<Relationship ID="8532"/>
</Relationship>
-<Value>
<![CDATA[JKL]]>
</Value>
</Condition>
-<Condition Operator="E" ID="65636">
-<Relationship ID="843">
<Relationship ID="7473"/>
</Relationship>
-<Value>
<![CDATA[MNO]]>
</Value>
</Condition>
</ConditionSet>
</ConditionSet>
</ConditionSet>
</Root>
Any inputs/recommendations are highly appreciated :)
Thank you
Zeaous
Assuming you have your XML in a SQL Server variable called #XML, you can use the native XQuery support in SQL Server 2005 and newer to do this much more elegantly and efficiently:
DECLARE #XML XML = '...(your XML here).....'
SELECT
RootID = #xml.value('(/Root/#ID)[1]', 'int'),
ConditionSetOperator = XC.value('#Operator', 'varchar(50)'),
ConditionID = XC2.value('#ID', 'int'),
ConditionOperator = XC2.value('#Operator', 'varchar(50)')
FROM
#Xml.nodes('//ConditionSet') AS XT(XC)
CROSS APPLY
xc.nodes('Condition') AS XT2(XC2)
This gives me an output of
With XQuery operators like .nodes() or .value(), you can easily "shred" an XML document into relational data and store that as needed.
The first call to #xml.nodes('//ConditionSet') will get a "pseudo" table for each matching node - so each <ConditionSet> node will be returned in the "pseudo" table XT as column XC, and then I can easily grab attributes (or XML elements) from that XML fragment using XQuery methods like .value().
Or I can even grab the list of sub-nodes <Condition> for each of those <ConditionSet> nodes - using the CROSS APPLY with a second call to .nodes()