Parsing Non-Standard XML in TSQL - sql-server

I need help parsing this XML in TSQL:
<Settings>
<Setting ERName="CAPTURE_MODE_ID" Value="9" />
<Setting ERName="VEHICLE_TYPE" Value="7" />
</Settings>
I would like to return the values as such:
Capture_Mode_Id Vehicle_Type
9 7

SQL Fiddle
declare #XML xml
set #XML = '
<Settings>
<Setting ERName="CAPTURE_MODE_ID" Value="9" />
<Setting ERName="VEHICLE_TYPE" Value="7" />
</Settings>'
select #XML.value('(/Settings/Setting[#ERName = "CAPTURE_MODE_ID"]/#Value)[1]', 'int') as CAPTURE_MODE_ID,
#XML.value('(/Settings/Setting[#ERName = "VEHICLE_TYPE"]/#Value)[1]', 'int') as VEHICLE_TYPE
Results:
| CAPTURE_MODE_ID | VEHICLE_TYPE |
|-----------------|--------------|
| 9 | 7 |

You need to perform two logical steps as best I can tell:
Parse the XML - i.e. get a result set from it.
Pivot the result set.
To get a result set from the XML, use the OPENXML function - something like the following...
DECLARE #idoc int, #doc varchar(1000);
-- the XML document
SET #doc ='
<ROOT>
<Settings>
<Setting ERName="CAPTURE_MODE_ID" Value="9" />
<Setting ERName="VEHICLE_TYPE" Value="7" />
</Settings>
</ROOT>';
-- Create an internal representation of the XML document.
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc;
-- Execute a SELECT statement that uses the OPENXML rowset provider.
SELECT *
FROM OPENXML (#idoc, '/ROOT/Settings/Setting', 1)
WITH (ERName varchar(20), Value varchar(20));
..., which yields the following results:
ERName Value
--------------- -----
CAPTURE_MODE_ID 9
VEHICLE_TYPE 7
To pivot the result set without aggregation, consider the ideas presented to this end in a CALSQL blog post.

Related

Can't read a property value from XML

I'm trying to get the value from the property Success but I can't, I don't know where I'm wrong.
This is my code
DECLARE #Response VARCHAR(8000) = '<?xml version="1.0" encoding="utf-8"?>
<Result xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/">
<Success>true</Success>
</Result>'
DECLARE #xml TABLE (
Content XML
)
INSERT INTO #xml
SELECT CAST(#Response AS XML)
SELECT
Content.value('(/Result/Success)[1]', 'BIT')
FROM #xml
The property Success is bool type
I'm trying with different scope types (nvarchar, varchar, bit, etc..)
This is what I expecting
or
Please try the following solution.
Notable points:
It is always better to use XML data type instead of the VARCHAR(..) for XML
data.
All XML elements are bound to the default namespace even if we don't
see it explicitly. That's why we need to specify it via
XMLNAMESPACES clause.
It is always better to use text() in the XPath expressions for XML
elements for performance reasons. Peculiarity of the MS SQL Server.
It is possible to omit XML prolog declaration completely. SQL Server
doesn't store it.
SQL
DECLARE #Response XML = '<?xml version="1.0" encoding="utf-8"?>
<Result xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/">
<Success>true</Success>
</Result>';
DECLARE #xml TABLE (Content XML);
INSERT INTO #xml
SELECT #Response;
;WITH XMLNAMESPACES (DEFAULT 'http://tempuri.org/')
SELECT result = Content.value('(/Result/Success/text())[1]', 'BIT')
FROM #xml;
Output
result
1

Read XML Value in SQL Select Statement

I'm trying to extract an XML value from a SQL Server column and put it into a SQL statement. My problem is that the documentation I've found doesn't explain how to do this if there are spaces or "" in the XML path.
I'm trying to extract the value property in the XML shown here (there is no namespace in the XML). The SQL Server column is called Settings:
<properties>
<settings hwid="stream:0.0.0">
<setting typeid="78622C19-58AE-40D4-8EEA-17351F4273B6">
<name>Codec</name>
<value>4</value>
</setting>
</settings>
</properties>
You can use OPENXML to retrieve data from xml, first create procedure like this:
CREATE PROCEDURE GetXmlValueProc
#xml NVARCHAR(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #hdoc INT;
EXEC sp_xml_preparedocument #hdoc OUTPUT, #xml;
DECLARE #Result NVARCHAR(50);
SELECT value
FROM
OPENXML(#hdoc, '/properties/settings/setting', 2)
WITH
(
value VARCHAR(100)
);
EXEC sp_xml_removedocument #hdoc;
END
GO
And call procedure in this way:
DECLARE #xml NVARCHAR(MAX)='<properties><settings hwid="stream:0.0.0"><setting typeid="78622C19-58AE-40D4-8EEA-17351F4273B6"><name>Codec</name><value>4</value></setting></settings></properties>'
EXEC dbo.GetXmlValueProc #xml
Even you can make procedure more generic and pass the xml path to get data.
I don't see any spaces in your XML. If you mean the various attributes, such as hwid, those are parsed separately from the node names. You can select those by prefacing with #.
I assume the type of the value node is int, if not you can change it below:
SELECT
t.Settings.value('(/properties/settings/setting/value)[1]', 'int'),
t.Settings.value('(/properties/settings/setting/#typeid)[1]', 'uniqueidentifier'),
t.Settings.value('(/properties/settings/#hwid)[1]', 'nvarchar(max)')
FROM myTable t
For reference, if you ever did have a node with a space in it: it would be encoded and a double-quote as "

SQL Server - XML Load Issue

'Hi, i am trying to create automated XML load into SQL and initially I need to create the table with right data set. Ultimately i am trying to get Serial Number, IP Address and Timezone with all count values for "From to "to" time stamp Here is how my XML looks like:
<response xmlns="http://www.test.com/sensor-api/v2">
<sensor-time timezone="America/New_York">2017-07-18T15:45:03-04:00</sensor-time>
<status>
<code>OK</code>
</status>
<sensor-info>
<serial-number>Q8:80:39:60:9Z:F2</serial-number>
<ip-address>192.167.130.18</ip-address>
<name>Test</name>
<group />
<device-type>PC2 - UL</device-type>
</sensor-info>
<content>
<elements>
<element>
<element-id>2</element-id>
<element-name>Conf_Lower_Zone</element-name>
<sensor-type>SINGLE_SENSOR</sensor-type>
<data-type>ZONE</data-type>
<from>2017-07-18T15:40:00-04:00</from>
<to>2017-07-18T15:45:00-04:00</to>
<resolution>ONE_MINUTE</resolution>
<measurements>
<measurement>
<from>2017-07-18T15:40:00-04:00</from>
<to>2017-07-18T15:41:00-04:00</to>
<values>
<value label="count">0</value>
</values>
</measurement>
<measurement>
<from>2017-07-18T15:41:00-04:00</from>
<to>2017-07-18T15:42:00-04:00</to>
<values>
<value label="count">0</value>
</values>
</measurement>
And Here is my SQL Part:
CREATE DATABASE OPENXMLTesting
GO
USE OPENXMLTesting
GO
CREATE TABLE XMLwithOpenXML
(
Id INT IDENTITY PRIMARY KEY,
XMLData XML,
LoadedDateTime DATETIME
)
INSERT INTO XMLwithOpenXML(XMLData, LoadedDateTime)
SELECT CONVERT(XML, BulkColumn) AS BulkColumn, GETDATE()
FROM OPENROWSET(BULK 'C:\Example\Test.xml', SINGLE_BLOB) AS x;
SELECT * FROM XMLwithOpenXML
USE OPENXMLTesting
GO
DECLARE #XML AS XML, #hDoc AS INT, #SQL NVARCHAR (MAX)
SELECT #XML = XMLData FROM XMLwithOpenXML
EXEC sp_xml_preparedocument #hDoc OUTPUT, #XML
SELECT [element-id], [element-name], [data-type], [ip-address]
FROM OPENXML(#hDoc,
'content/elements/element/measurements/measurement/values/value')
WITH
(
[element-id] varchar (100) '#ElementID',
[element-name] varchar(50) '#Element_Name',
[data-type] varchar(50) '#Data_Type',
[ip-address] varchar(50) '#IP_Address'
)
EXEC sp_xml_removedocument #hDoc
GO
The problem is that as results i am getting only headers without any data under it. See the pic below.
What am i doing wrong?
There are two issues...
First of all there is a namespace involved. As it is the default namespace you have to declare or wildcard it.
Secondly the chosen approach with FROM OPENXML together with the two stored procedures to create and to remove a document is completely outdated. You should use the modern native XML methods. Try this:
DECLARE #xml XML=
N'<response xmlns="http://www.test.com/sensor-api/v2">
<sensor-time timezone="America/New_York">2017-07-18T15:45:03-04:00</sensor-time>
<status>
<code>OK</code>
</status>
<sensor-info>
<serial-number>Q8:80:39:60:9Z:F2</serial-number>
<ip-address>192.167.130.18</ip-address>
<name>Test</name>
<group />
<device-type>PC2 - UL</device-type>
</sensor-info>
<content>
<elements>
<element>
<element-id>2</element-id>
<element-name>Conf_Lower_Zone</element-name>
<sensor-type>SINGLE_SENSOR</sensor-type>
<data-type>ZONE</data-type>
<from>2017-07-18T15:40:00-04:00</from>
<to>2017-07-18T15:45:00-04:00</to>
<resolution>ONE_MINUTE</resolution>
<measurements>
<measurement>
<from>2017-07-18T15:40:00-04:00</from>
<to>2017-07-18T15:41:00-04:00</to>
<values>
<value label="count">0</value>
</values>
</measurement>
<measurement>
<from>2017-07-18T15:41:00-04:00</from>
<to>2017-07-18T15:42:00-04:00</to>
<values>
<value label="count">0</value>
</values>
</measurement>
</measurements>
</element>
</elements>
</content>
</response>';
--The query needs to declare the namespace
--I'll use direct calls to get the singular values
--...and .nodes() to get a derived set of 1:n related elements
WITH XMLNAMESPACES(DEFAULT 'http://www.test.com/sensor-api/v2')
SELECT #xml.value('(/response/sensor-time/#timezone)[1]','nvarchar(max)') AS SensortTime_TimeZone
,#xml.value('(/response/sensor-time/text())[1]','datetime') AS SensortTime
,#xml.value('(/response/status/code/text())[1]','nvarchar(max)') AS StatusCode
,#xml.value('(/response/sensor-info/serial-number/text())[1]','nvarchar(max)') AS SerialNumber
,e.value('(element-id/text())[1]','int') AS ElementId
,e.value('(element-name/text())[1]','nvarchar(max)') AS ElementName
--more from here
,m.value('(from/text())[1]','datetime') AS Measurement_From
,m.value('(to/text())[1]','datetime') AS Measurement_To
,m.value('(values/value/#label)[1]','nvarchar(max)') AS Measurement_Label
,m.value('(values/value/text())[1]','int') AS Measurement_Label
FROM #xml.nodes('/response/content/elements/element') A(e)
OUTER APPLY e.nodes('measurements/measurement') B(m);

MS SQL Server - OpenXML - Multiple elements

XML example:
<POLICY>
<RISKS>
<RISK>
<DRV>1</DRV>
</RISK>
<RISK>
<DRV>2</DRV>
</RISK>
</RISKS>
</POLICY>
I want to select both Risk elements with this query:
SELECT RISK
FROM OPENXML(#hDOC, 'POLICY/RISKS', 2)
WITH(
RISK XML 'RISK'
) AS Z
Expected:
1. <RISK><DRV>1</DRV></RISK>
2. <RISK><DRV>2</DRV></RISK>
Result:
1. <RISK><DRV>1</DRV></RISK>
(only first element was returned)
For comparison this query returns two rows as expected:
SELECT DRV
FROM OPENXML(#hDOC, 'POLICY/RISKS/RISK', 2)
WITH(
DRV XML 'DRV'
) AS Z
Result:
1. <DRV>1</DRV>
2. <DRV>2</DRV>
So the question is how can I get two Risk-rows?
Why are you not using the native XQuery support provided by SQL Server. OpenXML is old and having lot of issues.
You can write your query like following using XQuery Support
DECLARE #hDOC xml
SET #hDOC='<POLICY>
<RISKS>
<RISK>
<DRV>1</DRV>
</RISK>
<RISK>
<DRV>2</DRV>
</RISK>
</RISKS>
</POLICY>'
SELECT T.c.query('.') AS result
FROM #hDOC.nodes('/POLICY/RISKS/RISK') T(c)
GO
You will get output as
1. <RISK><DRV>1</DRV></RISK>
2. <RISK><DRV>2</DRV></RISK>
Edit: If you still want to do with OpenXml, use query like following.
DECLARE #DocHandle int
DECLARE #hDOC VARCHAR(1000)
SET #hDOC=N'<POLICY>
<RISKS>
<RISK>
<DRV>1</DRV>
</RISK>
<RISK>
<DRV>2</DRV>
</RISK>
</RISKS>
</POLICY>'
EXEC sp_xml_preparedocument #DocHandle OUTPUT, #hDOC
SELECT RISK
FROM OPENXML(#DocHandle, 'POLICY/RISKS/RISK', 2)
WITH(
RISK XML '.'
) AS Z
EXEC sp_xml_removedocument #DocHandle
You will get the desired output.

selecting individual xml node using SQL

I have a large XML note with many nodes.
is there a way that I can select only a single node and all of its contents from the larger XML?
i am using sql 2005
You should use the query() Method if you want to get a part of your XML.
declare #XML xml
set #XML =
'
<root>
<row1>
<value>1</value>
</row1>
<row2>
<value>2</value>
</row2>
</root>
'
select #XML.query('/root/row2')
Result:
<row2>
<value>2</value>
</row2>
If you want the value from a specific node you should use value() Method.
select #XML.value('(/root/row2/value)[1]', 'int')
Result:
2
Update:
If you want to shred your XML to multiple rows you use nodes() Method.
To get values:
declare #XML xml
set #XML =
'
<root>
<row>
<value>1</value>
</row>
<row>
<value>2</value>
</row>
</root>
'
select T.N.value('value[1]', 'int')
from #XML.nodes('/root/row') as T(N)
Result:
(No column name)
1
2
To get the entire XML:
select T.N.query('.')
from #XML.nodes('/root/row') as T(N)
Result:
(No column name)
<row><value>1</value></row>
<row><value>2</value></row>

Resources