XML to SQL table (epcis:EPCISDocument) - sql

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

Related

Select 2nd row in XML Column in database using 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 |
+----------+----------------+

How to select values between a XML tag in SQL Query

I have a table with CLOB column storing a XML. The structure of XML is unreadable. I want to get values between few tags like <DOMAINID>; sample is shown below.
XML:
<ID>
<DOMAIN>IND<DOMAIN>
<DOMAINID>112AC<DOMAINID>
<ID>
<GROUP>
<GP>ASIA<GP>
<RSN>GOOD<RSN>
<GROUP>
I am using this:
SELECT REGEXP_REPLACE(COL,'^.*<DOMAINID>(.*)</DOMAINID>.*$','\1',1,0,'mn') col1 FROM tab;
Expected result:
112AC
Actual XML:
<?xml version="1.0" encoding="US-ASCII"?>
<GML:GMMessage
xmlns:GML="GML"
xmlns:GMLType="GML.Type"
xsi:schemaLocation="GML ../schema/gml..xsd" SchemaVersion="9.8">
<BusinessHdr>
<busHdr:BusObjectType>ABC</busHdr:BusObjectType>
<busHdr:BusObjectOwner>HDHDH</busHdr:BusObjectOwner>
<busHdr:BusObjectId>DJHDAHDAJHDA</busHdr:BusObjectId>
<busHdr:BusObjectVersion>1</busHdr:BusObjectVersion>
</BusinessHdr>
<Transaction>
<GenericEvent>NEW</GenericEvent>
<Group>
<GroupId>3424234</GroupId>
<Reason>MANUAL</Reason>
</Group>
< xsi:type="mm:MMIam">
<Id>
<Domain>ssdsgdsg</Domain>
<DomainId>123456ACC</DomainId>
<Version>1</Version>
</Id>
<Date>2021-02-01</Date>
</Transaction>
</GML:GMMessage>
Do not use regular expressions to parse XML; use a proper XML parser.
However, what you have is not properly formed XML as it is missing a root element and you are missing the / in all of the closing tags; so you first need to fix your XML and give it a root element and then you can parse it using an XML parser.
SELECT x.*
FROM table_name t
CROSS APPLY XMLTABLE(
'//root'
PASSING XMLTYPE( '<root>' || t.data || '</root>' )
COLUMNS
domain VARCHAR2(10) PATH './ID/DOMAIN',
domainid VARCHAR2(10) PATH './ID/DOMAINID',
gp VARCHAR2(50) PATH './GROUP/GP',
rsn VARCHAR2(50) PATH './GROUP/RSN'
) x
Which, for the sample data:
CREATE TABLE table_name ( data ) AS
SELECT '<ID>
<DOMAIN>IND</DOMAIN>
<DOMAINID>112AC</DOMAINID>
</ID>
<GROUP>
<GP>ASIA</GP>
<RSN>GOOD</RSN>
</GROUP>' FROM DUAL
Outputs:
DOMAIN | DOMAINID | GP | RSN
:----- | :------- | :--- | :---
IND | 112AC | ASIA | GOOD
If you just want a single value then you can use XMLQUERY:
SELECT XMLQUERY(
'/root/ID/DOMAINID/text()'
PASSING XMLTYPE( '<root>'||data||'</root>' )
RETURNING CONTENT
) AS domainid
FROM table_name
Which outputs:
| DOMAINID |
| :------- |
| 112AC |
db<>fiddle here
Update
I am going to assume that your XML also defines the xsi and busHdr namespaces (if it doesn't then Oracle will fail to parse the XML as it does not know what those namespaces are); that would give you this sample data:
CREATE TABLE table_name ( data ) AS
SELECT '<?xml version="1.0" encoding="US-ASCII"?>
<GML:GMMessage
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:GML="GML"
xmlns:busHdr="busHdr"
xmlns:GMLType="GML.Type"
xsi:schemaLocation="GML ../schema/gml.xsd busHdr ../schema/bushdr.xsd"
SchemaVersion="9.8">
<BusinessHdr>
<busHdr:BusObjectType>ABC</busHdr:BusObjectType>
<busHdr:BusObjectOwner>HDHDH</busHdr:BusObjectOwner>
<busHdr:BusObjectId>DJHDAHDAJHDA</busHdr:BusObjectId>
<busHdr:BusObjectVersion>1</busHdr:BusObjectVersion>
</BusinessHdr>
<Transaction>
<GenericEvent>NEW</GenericEvent>
<Group>
<GroupId>3424234</GroupId>
<Reason>MANUAL</Reason>
</Group>
<Id>
<Domain>ssdsgdsg</Domain>
<DomainId>123456ACC</DomainId>
<Version>1</Version>
</Id>
<Date>2021-02-01</Date>
</Transaction>
</GML:GMMessage>' FROM DUAL
Then, you just need to add the namespace that you are using and update the paths to the new (case-sensitive) locations:
SELECT x.*
FROM table_name t
CROSS APPLY XMLTABLE(
XMLNAMESPACES( 'GML' AS "GML" ),
'//GML:GMMessage/Transaction'
PASSING XMLTYPE( t.data )
COLUMNS
domain VARCHAR2(10) PATH './Id/Domain',
domainid VARCHAR2(10) PATH './Id/DomainId',
version NUMBER(3,0) PATH './Id/Version',
groupid VARCHAR2(50) PATH './Group/GroupId',
reason VARCHAR2(50) PATH './Group/Reason',
dt DATE PATH './Date'
) x
Outputs:
DOMAIN | DOMAINID | VERSION | GROUPID | REASON | DT
:------- | :-------- | ------: | :------ | :----- | :--------
ssdsgdsg | 123456ACC | 1 | 3424234 | MANUAL | 01-FEB-21
db<>fiddle here
Good to see your thinking with your approach...
Would suggest checking out this tool (if you haven't got a similar one) to help you with Regular expressions https://regexr.com/, helps me a lot.
You're SQL looks right (using the "m" and "n" flag for multiline), but not sure if it's your XML was typed in wrong, since you're regex string doesn't work on the XML you pasted, but I did get it work if it's XML.
What is you're current output from your SQL? you might need to use $1 in place of your \1.
I would also suggest
perhaps also escaping your forward slash, as that might be your culprit.
add more specificity to your capture to stop your search from being greedy.
SELECT REGEXP_REPLACE(COL,'^.*<DOMAINID>([0-9A-z]+)<\/DOMAINID>.*$','$1',1,0,'mn') col1 FROM tab;

XML to SQL Server table/column returning NULL

I have an XML stored in this format. When I use the below code it only returns NULL. Is there anything I am missing here?? Thanks in advance.
Declare #xmlData xml;
set #xmlData =
'<ProcessTask ProcessFile="Process1" TaskID="MyTsk" URL="www.google.com">
<LinkParams UserId="007" CountryId="1" Email="james.bond#gmail.com;" EmailSubject="New Mission" EndDate="2022-01-01" />
</ProcessTask>'
SELECT
n.value('(./UserId/text())[1]','int') as ArchiveUserId
, n.value('(./Email/text())[1]','Varchar(500)') as Email
FROM #xmlData.nodes('/ProcessTask/LinkParams') as a(n)
The required data is in attributes. That's why .value() method's XPath expressions should reflect it.
SQL
Declare #xmlData XML =
N'<ProcessTask ProcessFile="Process1" TaskID="MyTsk" URL="www.google.com">
<LinkParams UserId="007" CountryId="1" Email="james.bond#gmail.com;"
EmailSubject="New Mission" EndDate="2022-01-01"/>
</ProcessTask>';
SELECT n.value('#UserId','INT') as ArchiveUserId
, n.value('#Email','VARCHAR(500)') as Email
FROM #xmlData.nodes('/ProcessTask/LinkParams') as a(n);
Output
+---------------+-----------------------+
| ArchiveUserId | Email |
+---------------+-----------------------+
| 7 | james.bond#gmail.com; |
+---------------+-----------------------+

Extracting multiple values from BLOB as XML

I have an XML like this in a BLOB column:
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="urn:xyzns">
<history>
<Event>
<year>1983</year>
<Country>
<Location>Lisbon</Location>
<Type>Political</Type>
</Country>
</Event>
<Event>
<Country>
<Location>USA</Location>
<Type>Entertainment</Type>
<year>2016</year>
</Country>
</Event>
</history>
</document>
As you can see the year can be either in the event level or at country level. There can be multiple events and multiple countries per event. This whole XML is stored in a BLOB column in Oracle. I need to extract the value of the year or better check if the year is 2000 and if so return the primary key of the row.
I used EXISTSNODE to check if the year tag is present.
select pk from table where XMLType(blobdata, nls_charset_id('UTF8')).EXISTSNODE('/Document/history/Event[*]/year',
'xmlns="urn:xyzns"') = 1 and EXTRACTVALUE(XMLTYPE(blobdata, nls_charset_id('UTF8')), '/Document/history/Event[*]/year/text()',
'xmlns="urn:xyzns"') = '2000';
However this fails and the extractvalue query returns multiple nodes, so I changed the parameter to '/Document/history/Event[1]/year/text()' to check and it works. However this wouldnt be enough as it only checks the first event tag.
I looked at other questions here and one of the options was to use XMLTABLE since extractvalue is deprecated. I am having trouble understanding the parameters given inside the XMLTABLE. Could someone explain how to use XMLTABLE in this scenario? I should point out that the original datatype is BLOB and not CLOB. Thank you.
Use XMLTABLE to get values for both locations and then use COALESCE to show whichever is not NULL:
SELECT COALESCE( year, country_year ) AS year,
location,
type
FROM table_name t
CROSS APPLY XMLTABLE(
XMLNAMESPACES( DEFAULT 'urn:xyzns' ),
'/document/history/Event'
PASSING XMLTYPE(t.blobdata, nls_charset_id('UTF8'))
COLUMNS
year NUMBER(4,0) PATH './year',
country_year NUMBER(4,0) PATH './Country/year',
location VARCHAR2(200) PATH './Country/Location',
type VARCHAR2(200) PATH './Country/Type'
) x
Which, for the sample data:
CREATE TABLE table_name ( blobdata BLOB );
INSERT INTO table_name
VALUES (
UTL_RAW.CAST_TO_RAW(
'<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="urn:xyzns">
<history>
<Event>
<year>1983</year>
<Country>
<Location>Lisbon</Location>
<Type>Political</Type>
</Country>
</Event>
<Event>
<Country>
<Location>USA</Location>
<Type>Entertainment</Type>
<year>2016</year>
</Country>
</Event>
</history>
</document>'
)
);
Outputs:
YEAR | LOCATION | TYPE
---: | :------- | :------------
1983 | Lisbon | Political
2016 | USA | Entertainment
db<>fiddle here

SQL XML parsing different nodes

i would like to ask you for help with parsing XML in SQL, where my XML looks like this, where Load is parrent which can be repeated X-times. I need Column SerNr and for each row need to bound Order name
where final table will looks like this
Example of table:
<ImageHistory>
<Load targets="2" totalTime="417">
<Orders>
<Order name="20548976"/>
</Orders>
<Data>
<Disk SerNr="XXXXXX" Size_mb="228936" LoadSuccessfull="true" />
<Disk SerNr="ZZZZZ" Size_mb="228936" LoadSuccessfull="true" />
</Data>
</Load>
</ImageHistory>
sql is
with data as (SELECT CAST(MY_XML AS xml) as MY_XML FROM OPENROWSET(BULK 'addres to xml', SINGLE_BLOB) AS T(MY_XML)),
datainfo as (
SELECT
MY_XML.Blasting.value(' #name', 'BIGINT') as Size_mb,
MY_XML.Blasting.value('#SerNr', 'varchar(32)') as SerNr
FROM data CROSS APPLY MY_XML.nodes('ImageHistory/Load/Data/Disk') AS MY_XML (Blasting))
select * from datainfo
thank you for help
I saved the XML as a file on the file system: e:\Temp\DiskSerialNumbers.xml
The rest is below.
SQL
;WITH XmlFile (Contents) AS
(
SELECT CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK 'e:\Temp\DiskSerialNumbers.xml', SINGLE_BLOB) AS XmlData
)
SELECT c.value('(../../Orders/Order/#name)[1]', 'INT') AS [Name]
, c.value('#SerNr', 'VARCHAR(20)') AS [SerNr]
FROM XmlFile CROSS APPLY Contents.nodes('/ImageHistory/Load/Data/Disk') AS t(c);
Output
+----------+--------+
| Name | SerNr |
+----------+--------+
| 20548976 | XXXXXX |
| 20548976 | ZZZZZ |
+----------+--------+
You've got your answer already, but I want to point to the fact, that backward navigation (using ../../) is horribly slow with bigger structures.
I'd suggest this:
A mockup to simulate your issue:
DECLARE #yourTable TABLE(ID INT IDENTITY,YourXml XML);
INSERT INTO #yourTable VALUES
(N'<ImageHistory>
<Load targets="2" totalTime="417">
<Orders>
<Order name="20548976" />
</Orders>
<Data>
<Disk SerNr="XXXXXX" Size_mb="228936" LoadSuccessfull="true" />
<Disk SerNr="ZZZZZ" Size_mb="228936" LoadSuccessfull="true" />
</Data>
</Load>
</ImageHistory>');
--The query
SELECT t.ID
,ld.value('#targets','int') AS Load_Targets
,ld.value('#totalTime','int') AS Load_TotalTime
,ld.value('(Orders/Order/#name)[1]','int') AS Order_Name
,dsk.value('#SerNr','nvarchar(100)') AS Disk_SerNr
,dsk.value('#Size_mb','nvarchar(100)') AS Disk_Size_mb
,dsk.value('#LoadSuccessfull','bit') AS Disk_LoadSuccessfull
FROM #yourTable AS t
CROSS APPLY t.YourXml.nodes('/ImageHistory/Load') A(ld)
CROSS APPLY A.ld.nodes('Data/Disk') B(dsk);
The idea in short:
The first APPLY will dive down to <Load> and will return all <Load> elements (if there are more...
The second APPLY will use the fragment returned by the first APPLY and dive deeper down to <Disk>.
We fetch the order's name (and other values) calling .value() against the first fragment (which is the <Load> element) and we fetch the values of <Disk> calling .value() against the fragment of the second APPLY.