Select 2nd row in XML Column in database using SQL - sql

Having trouble selecting a specific info from an XML Format in a column of a table in the database. I need to pull the Success message for ModuleID 959
SubmissionID
ModuleID
CreatedOn
XMLCOL
UpdatedOn
25
959
1-1-22
"see XML below"
1-1-22
26
339
2-1-22
Null
2-1-22
Below is the data inside the XML column within the database - what I want to achieve is to show the 2nd ResultType "success" in the query with SQL.
<ArrayOfActionResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ActionResult>
<ResultType>Redirected to Payment</ResultType>
<ActionName>Payment</ActionName>
<ExecutionTime></ExecutionTime>
<ConditionSet>
<Conditions />
<ExecuteCondition>Always</ExecuteCondition>
<MatchCondition>All</MatchCondition>
<ExecuteStatus>0</ExecuteStatus>
<Groups />
</ConditionSet>
<ConditionsMet>true</ConditionsMet>
<Condition />
</ActionResult>
<ActionResult>
<ResultType>Success</ResultType>
<ActionName>Payment</ActionName>
<ExecutionTime></ExecutionTime>
<ConditionSet>
<Conditions />
<ExecuteCondition>Always</ExecuteCondition>
<MatchCondition>All</MatchCondition>
<ExecuteStatus>0</ExecuteStatus>
<Groups />
</ConditionSet>
<ConditionsMet>true</ConditionsMet>
</ActionResult>
</ArrayOfActionResult>
Currently I'm trying to use the SQL below to no avail
SELECT [XMLCOL].value('/ArrayOfActionResult/ActionResult/ResultType[2]') as PaymentMessage
FROM Databasetable
where [ModuleID] = 959
Hopefully this makes sense, I found it quite difficult to explain, I am very new to SQL

Check it out below.
Assuming your db is MS SQL Server.
The XQuery .value() method has two mandatory parameters.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ModuleID INT PRIMARY KEY, XMLCOL XML);
INSERT INTO #tbl (ModuleID, XMLCOL) VALUES
(959, N'<ArrayOfActionResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ActionResult>
<ResultType>Redirected to Payment</ResultType>
<ActionName>Payment</ActionName>
<ExecutionTime></ExecutionTime>
<ConditionSet>
<Conditions/>
<ExecuteCondition>Always</ExecuteCondition>
<MatchCondition>All</MatchCondition>
<ExecuteStatus>0</ExecuteStatus>
<Groups/>
</ConditionSet>
<ConditionsMet>true</ConditionsMet>
<Condition/>
</ActionResult>
<ActionResult>
<ResultType>Success</ResultType>
<ActionName>Payment</ActionName>
<ExecutionTime></ExecutionTime>
<ConditionSet>
<Conditions/>
<ExecuteCondition>Always</ExecuteCondition>
<MatchCondition>All</MatchCondition>
<ExecuteStatus>0</ExecuteStatus>
<Groups/>
</ConditionSet>
<ConditionsMet>true</ConditionsMet>
</ActionResult>
</ArrayOfActionResult>');
-- DDL and sample data population, end
SELECT ModuleID
, XMLCOL.value('(/ArrayOfActionResult/ActionResult[2]/ResultType/text())[1]','VARCHAR(30)') as PaymentMessage
FROM #tbl
WHERE ModuleID = 959;
Output
+----------+----------------+
| ModuleID | PaymentMessage |
+----------+----------------+
| 959 | Success |
+----------+----------------+

Related

T-SQL, get value from xml in a column

I have a table with an XML column (xmlCol) and I am trying to query a value from it.
Here's the xml.
<criteria>
<factor name="Rivers" title="Rivers">
<value dataType="Int32" value="1743" description="Wilson0" />
</factor>
<factor name="OptionalAffProperties" title="Include properties">
<value dataType="String" value="FishingModel" description="Fishing Model" />
</factor>
</criteria>
Here is a select to get the column. select xmlCol from MyTable
I am trying to return the value 1743 to a column called RiverID.
Mike
This should work:
SELECT
xmlCol.value('(//factor[#name="Rivers"]/value/#value)[1]', 'int') As RiverID
FROM
MyTable
value() Method (xml Data Type) - SQL Server | Microsoft Docs
XQuery Language Reference (SQL Server) - SQL Server | Microsoft Docs
To get the attribute value, you can query it like so:
select xmlCol.value('(/criteria/factor/value/#value)[1]', 'int') RiverID
from MyTable
You provide the xml path to the record you are looking for: (/criteria/factor/value
And then the attribute you need: /#value)[1].

Can't parse XML with outer apply

I have an XML column in a table which i am trying to parse out values from to flat table structure.
I am trying to input the XML here but stackoverflow ses it as code and when i try and format as code it still won't accept it.
I can't even get data from "Header" level.
<RequestMessage xmlns="http://iec.ch/TC57/2011/schema/message" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Message.xsd">
<Header>
<Verb>created</Verb>
<Noun>MeterReadings</Noun>
<Timestamp>2021-03-08T00:57:18+01:00</Timestamp>
<Source>Ipsum Lorum</Source>
<AsyncReplyFlag>true</AsyncReplyFlag>
<AckRequired>true</AckRequired>
<MessageID>Ipsum Lorum</MessageID>
<CorrelationID />
</Header>
<Payload>
<MeterReadings xmlns:MeterReadings="http://iec.ch/TC57/2011/MeterReadings#" xmlns="http://iec.ch/TC57/2011/MeterReadings#">
<MeterReading>
<IntervalBlocks>
<IntervalReadings>
<timeStamp>2021-03-07T01:00:00+01:00</timeStamp>
<value>480.196</value>
<ReadingQualities>
<ReadingQualityType ref="3.0.0" />
</ReadingQualities>
</IntervalReadings>
<IntervalReadings>
<ReadingType ref="11.0.7.3.1.2.12.1.1.0.0.0.0.101.0.3.72.0" />
</IntervalReadings>
</IntervalBlocks>
<Meter>
<mRID>0000000000000</mRID>
<status>
<remark>Ipsum Lorum</remark>
<value>ESP</value>
</status>
</Meter>
<UsagePoint>
<mRID>73599900000000</mRID>
</UsagePoint>
</MeterReading>
</MeterReadings>
</Payload>
</RequestMessage>
I am not able to parse it and i have tried using examples from other threads. I am trying to not use OPENXML solution because requires DECLARE and executing the built in procedure for clearing the XML from memmory periodically. I am trying to use the OUTER APPLY solution.
Like Shugos solution in How to parse XML data in SQL server table or Query XML with nested nodes on Cross Apply.
It doesn't work.
It returns null for the timestamp column.
select
t.file_created_time
,c.value('(Timestamp)[1]','varchar(max)') as timestamp
from load.t t
OUTER APPLY t.xml_data.nodes('RequestMessage/Header') as m(c)
Please try the following solution.
Starting from SQL Server 2005 onwards, it is better to use XQuery language, based on the w3c standards, while dealing with the XML data type.
Microsoft proprietary OPENXML and its companions sp_xml_preparedocument and sp_xml_removedocument are kept just for backward compatibility with the obsolete SQL Server 2000. Their use is diminished just to very few fringe cases.
I had to comment out the following tag <!--<IntervalReadings>--> to make your XML well-formed.
XML Header fragment has a default namespace:
xmlns="http://iec.ch/TC57/2011/schema/message"
XML Payload fragment has its own two additional namespaces:
xmlns:MeterReadings="http://iec.ch/TC57/2011/MeterReadings#"
xmlns="http://iec.ch/TC57/2011/MeterReadings#"
Namespaces should be taken into account.
Check it out below.
SQL
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, xml_data XML);
INSERT INTO #tbl (xml_data) VALUES
(N'<RequestMessage xmlns="http://iec.ch/TC57/2011/schema/message"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Message.xsd">
<Header>
<Verb>created</Verb>
<Noun>MeterReadings</Noun>
<Timestamp>2021-03-08T00:57:18+01:00</Timestamp>
<Source>Ipsum Lorum</Source>
<AsyncReplyFlag>true</AsyncReplyFlag>
<AckRequired>true</AckRequired>
<MessageID>Ipsum Lorum</MessageID>
<CorrelationID/>
</Header>
<Payload>
<MeterReadings xmlns:MeterReadings="http://iec.ch/TC57/2011/MeterReadings#"
xmlns="http://iec.ch/TC57/2011/MeterReadings#">
<MeterReading>
<IntervalBlocks>
<IntervalReadings>
<timeStamp>2021-03-07T01:00:00+01:00</timeStamp>
<value>480.196</value>
<ReadingQualities>
<ReadingQualityType ref="3.0.0"/>
</ReadingQualities>
</IntervalReadings>
<!--<IntervalReadings>-->
<ReadingType ref="11.0.7.3.1.2.12.1.1.0.0.0.0.101.0.3.72.0"/>
</IntervalBlocks>
<Meter>
<mRID>0000000000000</mRID>
<status>
<remark>Ipsum Lorum</remark>
<value>ESP</value>
</status>
</Meter>
<UsagePoint>
<mRID>73599900000000</mRID>
</UsagePoint>
</MeterReading>
</MeterReadings>
</Payload>
</RequestMessage>');
-- DDL and sample data population, end
WITH XMLNAMESPACES(DEFAULT 'http://iec.ch/TC57/2011/schema/message')
SELECT id
, c.value('(Noun/text())[1]','VARCHAR(30)') AS Noun
, c.value('(Timestamp/text())[1]','DATETIMEOFFSET(0)') AS [timestamp]
FROM #tbl
CROSS APPLY xml_data.nodes('/RequestMessage/Header') AS t(c);
Output
+----+---------------+----------------------------+
| id | Noun | timestamp |
+----+---------------+----------------------------+
| 1 | MeterReadings | 2021-03-08 00:57:18 +01:00 |
+----+---------------+----------------------------+
You need to respect and include the XML namespace in your XML document in your XQuery!
<RequestMessage xmlns="http://iec.ch/TC57/2011/schema/message"
**********************************************
Try something like this:
WITH XMLNAMESPACES(DEFAULT N'http://iec.ch/TC57/2011/schema/message')
SELECT
t.id,
c.value('(Timestamp)[1]','varchar(max)') as timestamp
FROM
load.t t
CROSS APPLY
t.xml_data.nodes('RequestMessage/Header') AS m(c)
Also when trying to run this on my SQL Server, I get an error that the XML as shown is malformed.....
UPDATE:
If you need to also access bits in the Payload section - you need to also respect that XML namespace there:
<MeterReadings xmlns:MeterReadings="http://iec.ch/TC57/2011/MeterReadings#"
xmlns="http://iec.ch/TC57/2011/MeterReadings#">
***********************************************
Try this:
WITH XMLNAMESPACES(N'http://iec.ch/TC57/2011/schema/message' as hdr,
N'http://iec.ch/TC57/2011/MeterReadings#' as mr)
SELECT
t.id,
c.value('(hdr:Timestamp)[1]', 'varchar(50)') AS timestamp,
col.value('(mr:MeterReading/mr:IntervalBlocks/mr:IntervalReadings/mr:timeStamp)[1]', 'varchar(50)') AS MeterReadingsTimestamp
FROM
load.t t
CROSS APPLY
t.xml_data.nodes('/hdr:RequestMessage/hdr:Header') AS m(c)
CROSS APPLY
t.xml_data.nodes('/hdr:RequestMessage/hdr:Payload/mr:MeterReadings') AS mr(col)

XML to SQL table (epcis:EPCISDocument)

Could you help me here, please? I need to do a query with an XML file, however, it has something different because it has the epcis: at the beginning of the XML document.
So, if I try to do the query with epcis: and dts: the result is:
Msg 2229, Level 16, State 1, Line 38.
XQuery [nodes()]: The name "epcis" does not denote a namespace.
And if I try to do the query without epcis: and dts:, the result is in blank.
BEGIN
DECLARE #archivo XML
SET #archivo = (
'<?xml version="1.0" encoding="utf-8"?>
<epcis:EPCISDocument xmlns:dts="urn:dts:extension:xsd" schemaVersion="1.2" creationDate="2021-06-30T07:29:32.6511940Z"
xmlns:epcis="urn:epcglobal:epcis:xsd:1">
<EPCISBody>
<EventList>
<ObjectEvent>
<eventTime>2021-06-30T07:29:32</eventTime>
<eventTimeZoneOffset>+02:00</eventTimeZoneOffset>
<action>OBSERVE</action>
<bizStep>code</bizStep>
<dts:epcItemList>
<item>
<epc>123456789</epc>
<code>123456789zz</code>
</item>
<item>
<epc>9687654321</epc>
<code>9687654321zz</code>
</item>
<item>
<epc>147258369</epc>
<code>147258369zz</code>
</item>
</dts:epcItemList>
</ObjectEvent>
</EventList>
</EPCISBody>
</epcis:EPCISDocument>'
)
SELECT
patch.r.value('(epc)[1]', 'varchar(100)') as [epc],
patch.r.value('(code)[1]', 'varchar(100)') as [code]
FROM
#archivo.nodes('EPCISDocument/EPCISBody/EventList/ObjectEvent/epcItemList/item') AS patch(r)
END
GO
I need to export the information as a SQL table per item, like this:
| epc | code |
| -------- | ------------ |
| 123456789 | 123456789zz |
| 9687654321 | 9687654321zz |
enter image description here
enter image description here
Thank you so much
Juan
You need to declare your namespaces. The namespace aliases present in the XML are not relevant, you can use any alias you like (that's the bit after AS in the declaration).
Also, text() is more performant when used in .value
WITH XMLNAMESPACES (
'urn:epcglobal:epcis:xsd:1' AS epcis,
'urn:dts:extension:xsd' AS dts
)
SELECT
patch.r.value('(epc/text())[1]', 'varchar(100)') as [epc],
patch.r.value('(code/text())[1]', 'varchar(100)') as [code]
FROM #archivo.nodes('epcis:EPCISDocument/EPCISBody/EventList/ObjectEvent/dts:epcItemList/item') as patch(r);
SQL Fiddle

SQL Table result in a xml output

I have a table
Key code
1 100
1 200
1 300
1 400
2 100
2 200
2 300
I am looking for my result in one row with key and other row XML_data
Key XML_Data(XML column)
1 <sub><key>1...
2 <sub><key>2...
XML_Data example :
<sub>
<key> 1 </Key>
<list>
<code> 100 </code>
<code> 200 </code>
<code> 300 </code>
<code> 400 </code>
</list>
</sub>
Thanks
I think that this SQL Server: Two-level GROUP BY with XML output it's closer.
Posted here because i don't have reputation for comment.
Your question is quite fuzzy, but my magic crystall ball tells me, that you are looking for this:
DECLARE #tbl TABLE([Key] INT, code INT);
INSERT INTO #tbl VALUES
(1,100)
,(1,200)
,(1,300)
,(1,400)
,(2,100)
,(2,200)
,(2,300);
--The query will first find a distinct list of keys and then use nested FOR XML-selects to gather your data into the structure wanted:
WITH DistinctKeys AS
(SELECT [Key] FROM #tbl GROUP BY [Key])
SELECT dk.[Key]
,(
SELECT dk.[Key]
,(
SELECT t.code
FROM #tbl AS t
WHERE t.[Key]=dk.[Key]
FOR XML PATH(''),ROOT('list'),TYPE
)
FOR XML PATH('sub'),TYPE
) AS XML_Data
FROm DistinctKeys AS dk
The result
Key XML_Data
1 <sub><Key>1</Key><list><code>100</code><code>200</code><code>300</code><code>400</code></list></sub>
2 <sub><Key>2</Key><list><code>100</code><code>200</code><code>300</code></list></sub>
You've not specified much, as to where your XML is etc. but in general following query should help in your case.
INSERT INTO dbo.YourTable
(key, xml_data)
VALUES
(KEY, CONVERT(XML, N'YOUR_XML', 2));
You can go even further with that, by declaring XML as variable, extracing your KEY from XML, and to 2nd column adding XML based on whole variable.
DECLARE #input XML = '<sub>
<key> 1 </key>
<list>
<code> 100 </code>
<code> 200 </code>
<code> 300 </code>
<code> 400 </code>
</list>
</sub>'
INSERT INTO dbo.YourTable(key, xml_data)
SELECT
key = COALESCE(XCol.value('key[1]', 'int'),0),
xml_data = CONVERT(XML, N''''+#input+'''', 2)
FROM #input.nodes('/sub') AS XTbl(XCol)
#ATC, thank you for your comment. If really what is needed is to get table into XML format please try following - FOR XML PATH is what you're looking for
SELECT
key,
code as "list/code"
FROM dbo.YourTable
FOR XML PATH('sub')
I'm not able to test it right now unfortunately.
Here you can see similar question: How to convert records in a table to xml format using T-SQL?

How to insert NULL into SQL Server DATE field *from XML*

I've got some XML that I'm trying to insert into a Microsoft SQL Server database using their XML datatype functions.
One of the table fields is a nullable DATE column. If the node is missing, then it's inserted as NULL which is great. However, if the node is present but empty <LastDay/> when running the XPath query, it interprets the value from the empty node as an empty string '' instead of NULL. So when looking at the table results, it casts the date to 1900-01-01 by default.
I would like for empty nodes to also be inserted as NULL instead of the default empty string '' or 1900-01-01. How can I get it to insert NULL instead?
CREATE TABLE myxml
(
"id" INT,
"name" NVARCHAR(100),
"company" NVARCHAR(100),
"lastday" DATE
);
DECLARE #xml XML =
'<?xml version="1.0" encoding="UTF-8"?>
<Data xmlns="http://example.com" xmlns:dmd="http://example.com/data-metadata">
<Company dmd:name="Adventure Works Ltd.">
<Employee id="1">
<Name>John Doe</Name>
<LastDay>2016-08-01</LastDay>
</Employee>
<Employee id="2">
<Name>Jane Doe</Name>
</Employee>
</Company>
<Company dmd:name="StackUnderflow">
<Employee id="3">
<Name>Jeff Puckett</Name>
<LastDay/>
</Employee>
<Employee id="4">
<Name>Ill Gates</Name>
</Employee>
</Company>
</Data>';
WITH XMLNAMESPACES (DEFAULT 'http://example.com', 'http://example.com/data-metadata' as dmd)
INSERT INTO myxml (id,name,company,lastday)
SELECT
t.c.value('#id', 'INT' ),
t.c.value('Name[1]', 'VARCHAR(100)' ),
t.c.value('../#dmd:name','VARCHAR(100)' ),
t.c.value('LastDay[1]', 'DATE' )
FROM #xml.nodes('/Data/Company/Employee') t(c)
This produces:
id name company lastday
------------------------------------------------
1 John Doe Adventure Works Ltd. 2016-08-01
2 Jane Doe Adventure Works Ltd. NULL
3 Jeff Puckett StackUnderflow 1900-01-01
4 Ill Gates StackUnderflow NULL
I am trying to achieve:
id name company lastday
------------------------------------------------
1 John Doe Adventure Works Ltd. 2016-08-01
2 Jane Doe Adventure Works Ltd. NULL
3 Jeff Puckett StackUnderflow NULL
4 Ill Gates StackUnderflow NULL
You have to use NULLIF function to avoid default values popping out from XML selection.
Returns a null value if the two specified expressions are equal.
Your query will be changed as below:
SELECT
t.c.value('#id', 'INT' ),
t.c.value('Name[1]','VARCHAR(100)' ),
t.c.value('../#dmd:name', 'VARCHAR(100)' ),
NULLIF(t.c.value('LastDay[1]', 'DATE' ),'')
FROM #xml.nodes('/Data/Company/Employee') t(c)
For more information on NULLIF, please check this MSDN page.
Besides techspider's very good answer I'd like to show another approach:
Doing .nodes() on Company and CROSS APPLY .nodes() on Employee allows a cleaner XPath navigation and avoids the backward navigation you are using by ../#dmd.name. In your case this is just for info probably, but good to consider: If there was a company without any Employee you would skip the whole company otherwise... (My code would skip as well due to the CROSS APPLY, but you could use OUTER APPLY).
And to your actual question: Using the internal cast as xs:date will do the logic within the XQuery and should be faster...
WITH XMLNAMESPACES (DEFAULT 'http://example.com', 'http://example.com/data-metadata' as dmd)
INSERT INTO myxml (id,name,company,lastday)
SELECT
e.value('#id', 'INT' ),
e.value('Name[1]', 'VARCHAR(100)' ),
c.value('#dmd:name', 'VARCHAR(100)' ),
e.value('let $x:=LastDay[1] return $x cast as xs:date?','DATE' )
FROM #xml.nodes('/Data/Company') AS A(c)
CROSS APPLY c.nodes('Employee') AS B(e)