Get values from xml nodes within sql statement - sql

I have string data with xml format in "Input" column from which I need specific values of nodes.
As an example:
I need every "error_text_1" value from each "error id".
<error_protokoll>
<header>
<source>machine</source>
</header>
<error_list>
<error id='0'>
<error_text_1>error0</error_text_1>
</error>
<error id='1'>
<error_text_1>error1</error_text_1>
</error>
</error_list>
</error_protokoll>
The following sql statement returns only the "error_text_1" value of id=0.
SELECT top (10)
XML_INPUT.value('(error_protokoll/error_list/error/error_text_1)[1]', 'varchar(200)') error
FROM
(
convert(xml, SUBSTRING( REPLACE(INPUT, '{
"xml" : "<?xml version=''1.0''?>', ''), 0 , len(REPLACE(INPUT, '{
"xml" : "<?xml version=''1.0''?>', ''))-3)) as XML_INPUT
FROM [Storage].[ods].[table]
) a
Instead of only the first item, I'd like to have all of them.
Could you please help how to solve this?

You need to use nodes in the FROM so that you get 1 row per error element, then you can get the value of error_text_1 for each one:
DECLARE #xml xml = '<error_protokoll>
<header>
<source>machine</source>
</header>
<error_list>
<error id="0">
<error_text_1>error0</error_text_1>
</error>
<error id="1">
<error_text_1>error1</error_text_1>
</error>
</error_list>
</error_protokoll>';
SELECT X.e.value('(./error_text_1/text())[1]','varchar(200)') AS error_text_1
FROM #xml.nodes('error_protokoll/error_list/error')X(e);
If the XML is in a table, your FROM would look like:
FROM dbo.YourTable YT
CROSS APPLY YT.YourColumn.nodes('error_protokoll/error_list/error')YC(e);

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>

SQL query for XML data

I have a SQL Server database table with a column called XML that contains XML data which is structured like this:
<Item xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://test/data">
<Roots>
<Root>
<Name>Field Name</Name>
<Value>Field Value</Value>
</Root>
<Root>
<Name>Field Name</Name>
<Value>Field Value</Value>
</Root>
</Roots>
I want to use T-SQL to get the Value where Name = Total. I have tried the following but it isn't returning any data:
SELECT [XML]
FROM [BusinessAccount]
WHERE [XML].value('(/Root/Name)[13]', 'VARCHAR(MAX)') LIKE '%Total%'
If anyone could tell me where I've gone wrong?
You are missing the required WITH XMLNAMESPACES for your XML and the path is incorrect.
If you want to bring back rows where the 13th element consists of the text Total you can use the below.
WITH XMLNAMESPACES (DEFAULT 'http://test/data')
SELECT [XML]
FROM [BusinessAccount]
WHERE 1 = [XML].exist('(/Item/Roots/Root/Name)[13][text() = "Total"]')
Otherwise you can add the WITH XMLNAMESPACES to your original query and fix the path there too.
You need to specify namespaces. You can then match <Name> and <Value> pairs and extract the contents of <Value> like so:
SELECT NameNode.value('declare namespace x="http://test/data"; (../x:Value)[1]', 'varchar(100)')
FROM [BusinessAccount]
CROSS APPLY [XML].nodes('declare namespace x="http://test/data"; //x:Root/x:Name') AS n(NameNode)
WHERE NameNode.value('.', 'varchar(100)') = 'Total'
Demo on db<>fiddle

Query xml using xquery in SQL Server 2016

I have my XML in following format:
<resultset xmlns="qm_system_resultset" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<result>
<result_id>F5</result_id>
<exception>NO</exception>
<recurring_count>0</recurring_count>
<defect>NO</defect>
<unresolved>NO</unresolved>
<exception_approval />
<comments />
<exception_expiration>3000-01-01</exception_expiration>
<exception_stat_only>NO</exception_stat_only>
<result_data>
<phraseprefix>rx</phraseprefix>
<phrasenumber>0001</phrasenumber>
<languagedesc>Khmer</languagedesc>
<englishphrase> each time.</englishphrase>
<phrasedesc> គ្រាប់ក្នុងមួយដង។</phrasedesc>
<qm_translatedphrase>day.</qm_translatedphrase>
</result_data>
</result>
<result>
<result_id>26</result_id>
<exception>NO</exception>
<recurring_count>0</recurring_count>
<defect>NO</defect>
<unresolved>NO</unresolved>
<exception_approval />
<comments />
<exception_expiration>3000-01-01</exception_expiration>
<exception_stat_only>NO</exception_stat_only>
<result_data>
<phraseprefix>hold</phraseprefix>
<phrasenumber>0001</phrasenumber>
<languagedesc>Hmong</languagedesc>
<englishphrase>Hold than 160.</englishphrase>
<phrasedesc>Tsis 160.</phrasedesc>
<qm_translatedphrase>Do not use </qm_translatedphrase>
</result_data>
</result>
Using TSQL/XML query how do I achieve this RESULT
[phraseprefix][phrasenumber]
rx 0001
hold 0001
...
I tried the following query, but I got null values for both the columns:
DECLARE #input XML = (SELECT result_xml
FROM QM_Data_Audit.QM_Package.test_results
WHERE result_id = 2446338)
SELECT
resultset.value('(phraseprefix)[1]', 'varchar(max)') AS 'phrasenumber',
resultset.value('(phrasenumber)[1]', 'int') AS 'phrasenumber'
FROM #input.nodes('/resultset/result/result_data') AS List(resultset)
My apologies if the question is asked previously, I am new to querying XML.
Appreciate your help.
Your xml has a namespace declared so you need to provide this when querying it. In this example this can be acheived with the WITH XMLNAMESPACES statement:
DECLARE #input XML = (SELECT result_xml
FROM QM_Data_Audit.QM_Package.test_results
WHERE result_id = 2446338);
WITH XMLNAMESPACES(DEFAULT 'qm_system_resultset')
SELECT
resultset.value('(phraseprefix)[1]', 'varchar(max)') AS 'phrasenumber',
resultset.value('(phrasenumber)[1]', 'varchar(max)') AS 'phrasenumber'
FROM #input.nodes('/resultset/result/result_data') AS List(resultset)
You'll want to set the data type for phrasenumber to varchar as well to preserve the leading 0s if you need them.

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)

adding encoding information to the result of FOR XML [duplicate]

This question already has answers here:
SQL Server FOR XML Enclosing Element?
(2 answers)
Closed 7 years ago.
I have a Script, which returns a XML using FOR XML in SQL 2008. Is there any way to add the version and encoding information in the beginning of the output. Eventually, i am planning to save the output in a file.
For example, right now my output looks like this
<Agents>
<Agent id="1">
<Name>Mike</Name>
<Location>Sanfrancisco</Location>
</Agent>
<Agent id="2">
<Name>John</Name>
<Location>NY</Location>
</Agent>
</Agents>
I would like to append the line <?xml version="1.0" encoding="UTF-8"?> in the beginning of the Xml output
So i want the output something like
<?xml version="1.0" encoding="UTF-8"?>
<Agents>
<Agent id="1">
<Name>Mike</Name>
<Location>Sanfrancisco</Location>
</Agent>
<Agent id="2">
<Name>John</Name>
<Location>NY</Location>
</Agent>
As #gbn points out in another answer and on another question, "the XML data is stored internally as ucs-2", and SQL Server doesn't include it when producing the data. However, you can convert the XML to a string and append the XML declaration at the beginning manually. However, simply using UTF-8 in the declaration would be inaccurate. The Unicode string which SQL produces is in UCS-2. For example, this will fail:
SELECT CONVERT(xml,N'<?xml version="1.0" encoding="UTF-8"?>' + CONVERT(NVARCHAR(MAX),CONVERT(XML,N'<x>' + NCHAR(10176) + N'</x>')));
with error:
Msg 9402, Level 16, State 1, Line 1 XML parsing: line 1, character 38,
unable to switch the encoding
This, on the other hand, will work as expected:
SELECT CONVERT(xml,N'<?xml version="1.0" encoding="UCS-2"?>' + CONVERT(NVARCHAR(MAX),CONVERT(XML,N'<x>' + NCHAR(10176) + N'</x>')));
Here is code which will produce the full, declaration-laden XML string you seek for your example data:
DECLARE #Agents TABLE
(
AgentID int,
AgentName nvarchar(50),
AgentLocation nvarchar(100)
);
INSERT INTO #Agents (AgentID, AgentName, AgentLocation) VALUES (1, N'Mike', N'Sanfrancisco');
INSERT INTO #Agents (AgentID, AgentName, AgentLocation) VALUES (2, N'John', N'NY');
WITH BaseData AS
(
SELECT
(
SELECT
AgentID AS '#id',
AgentName AS 'Name',
AgentLocation AS 'Location'
FROM #Agents
FOR XML PATH('Agent'), ROOT('Agents'), TYPE
) AS AgentXML
), FullStringTable AS
(
SELECT
*,
'<?xml version="1.0" encoding="UCS-2"?>' +
CONVERT(nvarchar(max),AgentXML) AS FullString
FROM BaseData
)
SELECT
AgentXML AS OriginalXML,
FullString,
CONVERT(xml,FullString) AS FullStringConvertedToXML
FROM FullStringTable;
SQL Server internally always uses utf-16 ucs-2 so you could just append it like we did. That is, SQL Server would never generate anything with "utf-8".
Edit: after some digging:
http://www.devnewsgroups.net/group/microsoft.public.sqlserver.xml/topic60022.aspx
http://forums.asp.net/t/1455808.aspx
If you will not be attempting to manipulate the results as TSQL XML then the simplest thing to do will be create a varchar with the string you wish to append then add the XML to it using a CAST statemnt to convert the XML to varchar.
declare #testXML as XML
declare #testPrefix as varchar(255)
set #testPrefix = '<?xml version="1.0" encoding="UTF-8"?>'
set #testXML = '<Agents> <Agent id="1"> <Name>Mike</Name> <Location>Sanfrancisco</Location> </Agent> <Agent id="2"> <Name>John</Name> <Location>NY</Location> </Agent></Agents>'
select #testPrefix
Select #testXML
select #testPrefix + CAST(#testXML as varchar(max))