Find XML tag which is present several times - sql

I am working with an Oracle database 19c.
I have a table with the blob field "MSG_BODY". This field contains XML's like that:
<Body xmlns = "http://www.finnova.ch/ZV/EHF/021">
<Auftrag>
<Auftragsinformation>
<Auftragsidentifikation>
<AUF_LNR>987987987987</AUF_LNR>
<APPL_ID>9999</APPL_ID>
</Auftragsidentifikation>
<Auftragsreferenz>
<EXT_REF>TEST-2020082109574181</EXT_REF>
<EXT_AUF_REF>BA18081508D86B28</EXT_AUF_REF>
<KD_LNR_ERF>901</KD_LNR_ERF>
</Auftragsreferenz>
</Auftragsinformation>
<Zahlungsliste>
<Zahlung>
<Identifikation>
<ZV_ZLG_SYS_LNR>987987987987</ZV_ZLG_SYS_LNR>
<ZV_ZLG_LNR>1</ZV_ZLG_LNR>
</Identifikation>
<Referenz>
<EXT_REF>ABCD654654654</EXT_REF>
<EXT_REF_AUF>XX-XXX 230/99999/1</EXT_REF_AUF>
<EXT_REF_AUF_IB>BA9999988888</EXT_REF_AUF_IB>
<ZLG_INSTR_ID>BA999988886666</ZLG_INSTR_ID>
<MeldungsRef>
<MSG_TX_ID>123123123123</MSG_TX_ID>
<CS_ZLG_TRACK_ID>d8047b9f-a8c7-4d74-b5c7-470510240b60</CS_ZLG_TRACK_ID>
<CS_SWIFTGPI_SVC_ID>001</CS_SWIFTGPI_SVC_ID>
</MeldungsRef>
<MeldungsRef>
<MSG_TX_ID_DECK>xxxxxxxxxx</MSG_TX_ID_DECK>
</MeldungsRef>
</Referenz>
<Mitteilung>
<MIT_BEGxxx</MIT_BEG>
<MIT_BEG_XML>
<Ustrd>xxx</Ustrd>
</MIT_BEG_XML>
<PURP_CD>SALA</PURP_CD>
</Mitteilung>
</Zahlung>
</Zahlungsliste>
</Auftrag>
The tag "Zahlung" can exist multiple times and that's OK, but into the the tag "Zahlung" is the
tag "MeldungsRef". This tag should exist zero or one time for every tag "Zahlung". That's a fault shown in the XML above. I now need a query to select all rows in the table, which contains an XML, where the tag "MeldungsRef" is multiple times there. How can I do that?
Thanks for helping me!
Regards,
mablaser

You're looking for a second appearance of the MeldungsRef node within a Zahlung node, so you can look directly for that. This query shows you the first and second instances of the node, using xmlquery() and specifying the appearance to find with [1] or [2]:
select id,
xmlquery(
'declare default element namespace "http://www.finnova.ch/ZV/EHF/021"; (: :)
/Body/Auftrag/Zahlungsliste/Zahlung/Referenz/MeldungsRef[1]'
passing xmltype(msg_body)
returning content
) as first,
xmlquery(
'declare default element namespace "http://www.finnova.ch/ZV/EHF/021"; (: :)
/Body/Auftrag/Zahlungsliste/Zahlung/Referenz/MeldungsRef[2]'
passing xmltype(msg_body)
returning content
) as second
from your_table;
You could look for the second being not-null, but it's easier to use the same XPath with xmlexists() to test whether a second child node exists:
select id
from your_table
where xmlexists(
'declare default element namespace "http://www.finnova.ch/ZV/EHF/021"; (: :)
/Body/Auftrag/Zahlungsliste/Zahlung/Referenz/MeldungsRef[2]'
passing xmltype(msg_body)
);
db<>fiddle with one good (single node) and one bad (multiple node) row.
i receive the following error: ORA-32512: type 'xquery external variable'
As your base column is a BLOB you need to tell it which character set it's it, e.g.:
passing xmltype(msg_body, nls_charset_id('UTF8'))
db<>fiddle.

Related

Select values from XML with multiple namespaces

I need to read a value of an attribute from an XML column. The data is an XML with multiple namespaces declared:
<sd:objectData xmlns:sd="http://sd-uri">
<sd:object sourceKey="FC5A0A51-7FB6-4C64-A13E-D4B00649E80E">
<do:properties xmlns:do="http://do-uri">
<do:property name="DECISION">
<do:propertyValues clearExistingValues="true">
<do:propertyValue action="add" valueInteger="1000142" tag="Approve" />
</do:propertyValues>
</do:property>
</do:properties>
</sd:object>
</sd:objectData>
I want to read the value of valueInteger, namely in this example 1000142. I tried with WITH XMLNAMESPACES() but I am not able to get it together to define both aliases.
Does this work for you?
DECLARE #XML xml = '
<sd:objectData xmlns:sd="http://sd-uri">
<sd:object sourceKey="FC5A0A51-7FB6-4C64-A13E-D4B00649E80E">
<do:properties xmlns:do="http://do-uri">
<do:property name="DECISION">
<do:propertyValues clearExistingValues="true">
<do:propertyValue action="add" valueInteger="1000142" tag="Approve" />
</do:propertyValues>
</do:property>
</do:properties>
</sd:object>
</sd:objectData>';
WITH XMLNAMESPACES ('http://sd-uri' AS sd,
'http://do-uri' AS do)
SELECT #XML.value('(/sd:objectData/sd:object/do:properties/do:property/do:propertyValues/do:propertyValue/#valueInteger)[1]','int') AS valueInteger;
In addition to Larnu's answer (which is the best and correct answer) just some alternative shortcuts, if you just want to get one value:
This query fetches the needed value in four different approaches
SELECT #XML.value(N'(//*/#valueInteger)[1]',N'int') AS Super_easy_with_double_wildcard
,#XML.value(N'(//*:propertyValue/#valueInteger)[1]',N'int') AS Easy_with_namespace_wildcard
,#XML.value(N'declare namespace do="http://do-uri";
(//do:propertyValue/#valueInteger)[1]',N'int') AS with_local_declaration
,#XML.value(N'declare namespace do="http://do-uri";
declare namespace sd="http://sd-uri";
(/sd:objectData/sd:object/do:properties/do:property/do:propertyValues/do:propertyValue/#valueInteger)[1]',N'int') AS with_full_local_declaration;
The general advise is: Be as specific as possible to avoid hassels. If you do no bother and you just need a readable, quick catch, you can take one of the alternatives.
UPDATE Add a predicate
With a predicate you can place a filter:
SELECT #XML.value(N'(//*:property[#name="DECISION"]//*:propertyValue/#valueInteger)[1]',N'int') AS Example_with_predicate

XQuery: Delete specific parent node based on child value

I'm trying to delete a parent node from an xml document based on the value of a child node
Here's a really simplified example of what I'm looking at
<root>
<someactivity>
<id>123456789</id>
</someactivity>
</root>
What I'd like to be able to do using SQL Server / XQuery is to delete the entire 'someactivity' node and its contents by searching for the id of '123456789' in the child node 'id'.
So far I've got something like this:-
update mytablecontainingXMLcolumns
set xmldata.modify('delete //someactivity/id[text()][contains(.,"123456789")]')
but it's not working as I expected - only seems to be deleting the 'id' node. I'm stuck at this stage. Any help / guidance would be appreciated.
Modify your XPath/XQuery to be selecting the parent someactivity element instead of id :
update mytablecontainingXMLcolumns
set xmldata.modify('delete //someactivity[contains(id,"123456789")]')
or, if there can be multiple id elements within one someactivity and you want to delete the parent if, at least, one id matched :
update mytablecontainingXMLcolumns
set xmldata.modify('delete //someactivity[id[contains(.,"123456789")]]')

SQL Server: Find records where XML is missing tag

I have table named: XMLIndex that contains a column named: XMLRec that holds the structure of an XML file and values.
Some of these records are missing a tag named: <ISO></ISO>
My question is: what type of query do I need to run in order to find all the records in the table XMLIndex, that are missing the <ISO> tag?
This is an example XMLRecord XML that contains the ISO tag:
<XMLRecord>
<pn>0042761</pn>
<SRI>4.40</SRI>
<igm>/images/images/0042761.gif</img>
<ISO>ZW</ISO>
<ListPrice>$5.50</ListPrice>
</XMLRecord>
and one with multiple ISOs (look at the tag small difference):
<XMLRecord>
<pn>0042762</pn>
<SRI>4.40</SRI>
<igm>/images/images/0042762.gif</img>
<ISOs>ZW+NZ+AU+BR</ISOs>
<ListPrice>$5.50</ListPrice>
</XMLRecord>
One record missing the ISO tag is one that the XML structure would not contain such tag.
Any examples are much appreciated.
Thank you.
You can use the XQuery exist method.
Check anywhere in the xml document:
select *
from XMLIndex
where XMLRec.exist('//ISO') = 0
Check a specific location:
where XMLRec.exist('/XMLRecord/ISO') = 0

GET url with nested element inside query string

Using Postman, I am forming a GET request query to my P21 database middleware to retrieve items with a specific value in a UserDefinedField.
I am able to query things on the top level of the item data, such as ItemID and ItemDesc like so:
http://[server]:[port]/api/inventory/parts?$query=ItemDesc eq 'CONTROL VALVE'
However, the values I would like to use in my query string are nested inside the UserDefinedFeilds element. I am specifically looking for items with:
http://[server]:[port]/api/inventory/parts?$query=UserDefinedFeilds/OnEbay eq 'Y'
But this is not the correct way to form this query string. Can anyone please explain how to specify a nested element inside a query string like this? Thanks.
In this situation, using P21 API, it is unnecessary to specify the parent field 'UserDefinedFields'. The actual ID of the column I was looking for was actually 'on_ebay', so I was able to query this user defined field simply:
http://[server]:[port]/api/inventory/parts?$query=on_ebay eq 'Y'

Modify a column, to get rid of html surrounding an ID

I have a table and one of the columns contains html for an iFrame & within it an external video, specifically it's like
<iframe src="http://host.com/videos/ID" otherattributes...></iframe>.
I need to update the current column or create a new one (doesn't matter) so what I have is just the ID of that video, I know I could use a regex for it but I'm really weak with it.
perhaps so it find the content that is within literal characters: [videos/] and the upcoming ["] which comes right after the ID but I'm unsure how.
You can use CHARINDEX() function:
update T SET
VideoID=SUBSTRING(descr,
charindex('/videos/',descr)+LEN('/videos/'),
charindex('"',descr,charindex('/videos/',descr)+LEN('/videos/'))
-(charindex('/videos/',descr)+LEN('/videos/')))
SQLFiddle demo
This should work, assuming the text videos/ doesn't appear anywhere else in the html.
update htmltable
set id = SUBSTRING(SUBSTRING(html,
CHARINDEX('videos/', html) + 7,
LEN(html)
),
0,
CHARINDEX('"', SUBSTRING(html,
CHARINDEX('videos/', html) + 7,
LEN(html)
)
)
)
This updates a field named otherfield in table htmltable where the id in the url is '123'. It's pretty ugly code, but SQL Server has limited string functions.
If you have any control over the table structure, I would suggest you make some changes. The video ID should be stored in its own column, separate from the rest of the url. Then when you need to retrieve the url, you would concatenate the two parts to get the whole url. That would be much more maintainable.