Update XML value in stored in Clob - oracle

Does anyone know a solution how I can change a specific value within xml? XML is stored in Clob datatype.
My XML looks like:
<settings name="TEST_NAME" path="TEST_PATH">
<values>
<value param="Version">20200207</value>
</values>
<collections>
<collection name="Items">
<values>
<value param="TEST_PARAM">true</value>
</values>
<collections>
</collection>
<collection name="TEST_COL">
<values>
<value param="DockedLeft">0</value>
</values>
<collections>
<collection name="ItemLink0">
<values>
<value param="ItemName">TEST_PARAM</value>
</values>
</collection>
</collections>
</collection>
</collection>
</collections>
What I need to update is "TEST_PARAM" inside TEST_COL collection. Collection name itemlink0 can be different. Thanks for the answers!

I created some sample XML that is valid and inserted into a CLOB column. It should suffice to show how this can be done using XMLQuery and a SQL update.
CREATE table xml_tbl (xml_str CLOB)
INSERT INTO xml_tbl VALUES(
'<settings name="TEST_NAME" path="TEST_PATH">
<collections>
<collection name="TEST_COL">
<values>
<value param="DockedLeft">0</value>
</values>
<collections>
<collection name="ItemLink0">
<values>
<value param="ItemName">TEST_PARAM</value>
</values>
</collection>
</collections>
</collection>
</collections>
</settings>')
The element value can be updated by converting the CLOB to/from XMLType and utilizing XMLQuery to update the XML. The element having the dynamic attribute name can be a PL/SQL variable within the PASSING clause of the XMLQuery.
DECLARE
l_dyn_attr_name VARCHAR2(100):= 'ItemLink0';
l_element_value VARCHAR2(100):= 'new value';
BEGIN
UPDATE xml_tbl xt
SET xt.xml_str =
XMLTYPE.GETCLOBVAL(XMLQuery('copy $i := $x1 modify
(for $j in $i/settings/collections/collection[#name="TEST_COL"]/collections/collection[#name=$dynamic_attr_name]/values/value
return replace value of node $j with $new_elem_value)
return $i' PASSING XMLTYPE(xt.xml_str) AS "x1"
,l_dyn_attr_name AS "dynamic_attr_name"
,l_element_value AS "new_elem_value" RETURNING CONTENT));
COMMIT;
END;

Related

How to write a xQuery to extract two fields based on the conditions of others value?

I have an XML document:
<resultsets>
<row>
<emp_no>10001</emp_no>
<first_name>Georgi</first_name>
<last_name>Facello</last_name>
</row>
<row>
<emp_no>10002</emp_no>
<first_name>Bezalel</first_name>
<last_name>Simmel</last_name>
</row>
</resultset>
I want to write a xQuery using FLWOR to extract emp_no and first_name where emp_no is 10001 with the coressponding first name like this:
<row>
<emp_no>10001</emp_no>
<first_name>Georgi</first_name>
</row>
The code I wrote:
for $id in doc("employees.xml")//emp_no
for $first in doc("employees.xml")//first_name
where ($id/text() = '10001')
return
<row>
<id>{upper-case($id/text())}</id>
<first>{$first/text()}</first>
</row>
however, it returns a cartesian product of $id and $first,
<row>
<emp_no>10001</emp_no>
<first_name>Georgi</first_name>
</row>
<row>
<emp_no>10001</emp_no>
<first_name>Bezalel</first_name>
</row>
Do you know how to fix this using xQuery FLWOR? Thanks!
I would simply select the row and then use a where clause on the emp_no child:
for $row in /resultsets/row
where $row/emp_no = '10001'
return
<row>
<id>{$row/emp_no/data()}</id>
<first>{$row/first_name/data()}</first>
</row>
http://xqueryfiddle.liberty-development.net/bFukv89
It is not quite clear whether you want to change the element names from emp_no to id and from first_name to first, if that is not needed you can of course simply copy them:
for $row in /resultsets/row
where $row/emp_no = '10001'
return
<row>
{$row/emp_no, $row/first_name}
</row>
http://xqueryfiddle.liberty-development.net/bFukv89/1

read the XML type column using stored procedure

How to display parent tags and attributes from XML TYPE column using stored procedure?
Example: I have below data in XMLTYPE Column in one table:
<Employee>
<Employee_information >
<Employee_content>
<task>
<Employee_stem>
<Employee_stem_paragraph>fsdbnfjksdflsdj.</Employee_stem_paragraph>
<Employee_stem_paragraph>dsfsdfsdfsdf</Employee_stem_paragraph>
</Employee_stem>
<Employee_response>
<Employee_response_choices>
<Employee_choice_list>
<Employee_block_choice numeric_identifier="1">
<Employee_choice_paragraph>sdfsdfsdfsdf</Employee_choice_paragraph>
</Employee_block_choice>
</Employee_choice_list>
</Employee_response_choices>
</Employee>
Output: display all the parent tags and attributes in it.
Output example:
Employee,Employee_information,Employee_content,Employee_stem,Employee_stem_paragraph,Employee_response,Employee_response_choices,Employee_choice_list,Employee_block_choice,
numeric_identifier,Employee_choice_paragraph.
In general you have to fix you xml. All tag have to be closed.
1-st select //*/name(.) - extract only element's name.
2-en select '//*/name(#*) -extract only attributes' name.
select *
from xmltable('//*/name(.)'
passing xmltype('<Employee>
<Employee_information>
<Employee_content>
<task>
<Employee_stem>
<Employee_stem_paragraph>fsdbnfjksdflsdj.</Employee_stem_paragraph>
<Employee_stem_paragraph>dsfsdfsdfsdf</Employee_stem_paragraph>
</Employee_stem>
<Employee_response>
<Employee_response_choices>
<Employee_choice_list>
<Employee_block_choice numeric_identifier="1">
<Employee_choice_paragraph>sdfsdfsdfsdf</Employee_choice_paragraph>
</Employee_block_choice>
</Employee_choice_list>
</Employee_response_choices>
</Employee_response>
</task>
</Employee_content>
</Employee_information>
</Employee>') columns name varchar2(200) path '.' )
union all
select *
from xmltable('//*/name(#*)'
passing xmltype('<Employee>
<Employee_information>
<Employee_content>
<task>
<Employee_stem>
<Employee_stem_paragraph>fsdbnfjksdflsdj.</Employee_stem_paragraph>
<Employee_stem_paragraph>dsfsdfsdfsdf</Employee_stem_paragraph>
</Employee_stem>
<Employee_response>
<Employee_response_choices>
<Employee_choice_list>
<Employee_block_choice numeric_identifier="1">
<Employee_choice_paragraph>sdfsdfsdfsdf</Employee_choice_paragraph>
</Employee_block_choice>
</Employee_choice_list>
</Employee_response_choices>
</Employee_response>
</task>
</Employee_content>
</Employee_information>
</Employee>') columns name varchar2(200) path '.' )

Oracle, regexp_substr - how to match 'string'+regex

I have a column that gives me lines like this:
<?xml version="1.0" encoding="UTF-8"?>
<xSettings>
<systemPropertyName>BLALBLA</systemPropertyName>
<minimumAmount>198.00</minimumAmount>
<closingAmount>198.00</closingAmount>
<useThisSetting>true</useThisSetting>
<SystemStep dayAfterPrevious="0">
<System SystemService="1" minimumAmount="450.00" />
</SystemStep>
<SystemStep dayAfterPrevious="8">
<message />
</SystemStep>
<SystemStep dayAfterPrevious="3">
<block />
</SystemStep>
<SystemStep dayAfterPrevious="1">
<message />
</SystemStep>
<SystemStep dayAfterPrevious="7">
<message />
</SystemStep>
</xSettings>
All numbers in it are variable and the BLA BLA is variable too. What I want is a select that only gives me
<minimumAmount>198.00</minimumAmount>
though this would be eve better:
198.00
basically, I can't figure out how to use regexp_substr to find a specific string , then return a number just after that may be from 1 to 4 digits and has two decimals after.
Do not use regular expressions to parse XML data - use a proper XML parser:
SELECT x.minimumAmount
FROM your_table t,
XMLTable(
'/xSettings',
PASSING XMLType( t.your_column )
COLUMNS minimumAmount NUMBER(5,2) PATH './minimumAmount'
) x
Or
SELECT TO_NUMBER(
EXTRACTVALUE( XMLType( your_column ), '/xSettings/minimumAmount' )
) AS minimumAmount
FROM your_table
XML data should not be queried using regular expressions, but here:
select
regexp_substr(col, '<minimumAmount>(.*)</minimumAmount>',1,1,null,1) minimum_amount
from your_table;
One other way is to use regexp_replace like this:
select
regexp_replace(col, '.*<minimumAmount>(.*)</minimumAmount>.*','\1') minimum_amount
from your_table;

How to correctly print xml data from clob?

For example, my clob contains xml content as shown below
<?xml version="1.0"?>
<content>
<foo>bar</foo>
<bar>foo</bar>
</content>
When i print it using loop, struct of content is not well-formed.
Use XMLSerialize function:
with test_data as (
select
'<?xml version="1.0"?><content>
<foo>bar</foo><bar>foo</bar></content>' as x_data
from dual
)
select
xmlserialize(document xmltype(x_data) as clob indent size = 2) x_text
from test_data
SQLFiddle

Xpath to get an 'id' or 'counter' of which loop number I'm on for a given xpath expression

Updated based on feedback, thanks -
I have an example such as below, where while parsing the xpath I would like to keep track of which row I'm on /rows/row via a number or identifier. I'm currently trying to transfer the XML into a relational format and have each set of columns identified with the 'Row ID',row number, or row position that it came from.
<rows>
<row>
<columns>
<column>
<name>x</name>
<value>val1.x</value>
</column>
<column>
<name>y</name>
<value>val1.y</value>
</column>
<column>
<name>z</name>
<value>val1.z</value>
</column>
</columns>
</row>
<row>
<columns>
<column>
<name>x</name>
<value>val2.x</value>
</column>
<column>
<name>y</name>
<value>val2.y</value>
</column>
<column>
<name>z</name>
<value>val2.z</value>
</column>
</columns>
</row>
<row>
<column>
<name>x</name>
<value>val3.x</value>
</column>
<column>
<name>y</name>
<value>val3.y</value>
</column>
<column>
<name>z</name>
<value>val3.z</value>
</column>
</columns>
</row>
</rows>
Suppose your context node is a <value>, <name>, <column> or <columns> element and <row> elements are not in this document outside the current structure. Then the following XPath will give you the "row number"
count(ancestor::row) + count(ancestor::row[1]/preceding-sibling::row)
Indexing begins at 1. If the expression returns 0, the context node is not inside the row structure.

Resources