Update XML Clob node - sql

I have a table Transaction , in which I have a clob xclob for which ,
I want to update the "property" node's "record_start_dll_date" value to record_start_date(i.e I want to remove the _dll part )
and "record_dll_end_date" to record_end_date .
I am using oracle database . How can I modify these node values??
<?xml version ="1.0"?>
<properties>
<property name ="record_start_dll_date">
<value>1/1/2021</value>
</property>
<property name ="record_dll_end_date">
<value>21/12/2021</value>
</property>
</properties>

You can use an XMLQuery update, either with the specific attribute values:
xmlquery ('copy $i := $p1 modify (
(for $j in $i/properties/property[#name="record_start_dll_date"]/#name
return replace value of node $j with $p2),
(for $j in $i/properties/property[#name="record_dll_end_date"]/#name
return replace value of node $j with $p3)
) return $i'
passing xmltype(xclob) as "p1",
'record_start_date' as "p2",
'record_end_date' as "p3"
returning content)
or to strip any _dll:
xmlquery ('copy $i := $p1 modify (
for $j in $i/properties/property[contains(#name, "_dll")]/#name
return replace value of node $j with replace($j, "_dll", "")
) return $i'
passing xmltype(xclob) as "p1"
returning content)
Either way you can incorporate that into an update statement, with a matching XMLExists clause to avoid unnecessary updates:
update transaction
set xclob = xmlquery ('copy $i := $p1 modify (
for $j in $i/properties/property[contains(#name, "_dll")]/#name
return replace value of node $j with replace($j, "_dll", "")
) return $i'
passing xmltype(xclob) as "p1"
returning content).getclobval()
where xmlexists('$p1/properties/property[contains(#name, "_dll")]'
passing xmltype(xclob) as "p1")
db<>fiddle

Related

Create JSON from XML - JSON_AGG OUTPUT PROBLEM

I have a problem with converting XML content to JSON format (with plain oracle select statement), where more then 1 sub level of data is present in the original XML - with my code the result of level 2+ is presented as string and not as JSON_OBJECT. Please, could someone tell me, where is fault in my code or what I'm doing wrong:
source:
<envelope>
<sender>
<name>IZS</name>
<country>SU</country>
<address>LOCATION 10B</address>
<address>1000 CITY</address>
<sender_identifier>SU46794093</sender_identifier>
<sender_address>
<sender_agent>SKWWSI20XXX</sender_agent>
<sender_mailbox>SI56031098765414228</sender_mailbox>
</sender_address>
</sender>
</envelope>
transformation select statement:
WITH SAMPLE AS (SELECT XMLTYPE ('
<envelope>
<sender>
<name>IZS</name>
<country>SU</country>
<address>LOCATION 10B</address>
<address>1000 CITY</address>
<sender_identifier>SU46794093</sender_identifier>
<sender_address>
<sender_agent>SKWWSI20XXX</sender_agent>
<sender_mailbox>SI56031098765414228</sender_mailbox>
</sender_address>
</sender>
</envelope>') XMLDOC FROM DUAL)
SELECT JSON_SERIALIZE (
JSON_OBJECT (
KEY 'envelope' VALUE
JSON_OBJECTAGG (
KEY ID_LEVEL1 VALUE
CASE ID_LEVEL1
WHEN 'sender' THEN
( SELECT JSON_OBJECTAGG (
KEY ID_LEVEL2 VALUE
CASE ID_LEVEL2
WHEN 'sender_address' THEN
( SELECT JSON_OBJECTagg (KEY ID_LEVEL22 VALUE TEXT_LEVEL22)
FROM XMLTABLE ('/sender/sender_address/*'
PASSING XML_LEVEL2
COLUMNS ID_LEVEL22 VARCHAR2 (128) PATH './name()',
TEXT_LEVEL22 VARCHAR2 (128) PATH './text()'
)
)
ELSE
TEXT_LEVEL2
END)
FROM XMLTABLE ('/sender/*'
PASSING XML_LEVEL2
COLUMNS ID_LEVEL2 VARCHAR2 (1024) PATH './name()',
TEXT_LEVEL2 VARCHAR2 (1024) PATH './text()'
)
)
ELSE
'"' || TEXT_LEVEL1 || '"'
END FORMAT JSON)
) PRETTY
)JSON_DOC
FROM SAMPLE, XMLTABLE ('/envelope/*'
PASSING XMLDOC
COLUMNS ID_LEVEL1 VARCHAR2 (1024) PATH './name()',
TEXT_LEVEL1 VARCHAR2 (1024) PATH './text()',
XML_LEVEL2 XMLTYPE PATH '.'
);
wrong result:
{
"envelope" :
{
"sender" :
{
"name" : "IZS",
"country" : "SU",
"address" : "LOCATION 10B",
"address" : "1000 CITY",
"sender_identifier" : "SU46794093",
"sender_address" : "{\"sender_agent\":\"SKWWSI20XXX\",\"sender_mailbox\":\"SI56031098765414228\"}"
}
}
}
wrong part:
***"sender_address" : "{\"sender_agent\":\"SKWWSI20XXX\",\"sender_mailbox\":\"SI56031098765414228\"}"***
For the level 1 text you're wrapping the value in double-quotes and specifying format json; you aren't doing that for level 2. If you change:
ELSE
TEXT_LEVEL2
END
to:
ELSE
'"' || TEXT_LEVEL2 || '"'
END FORMAT JSON)
then the result is:
{
  "envelope" :
  {
    "sender" :
    {
      "name" : "IZS",
      "country" : "SU",
      "address" : "LOCATION 10B",
      "address" : "1000 CITY",
      "sender_identifier" : "SU46794093",
      "sender_address" :
      {
        "sender_agent" : "SKWWSI20XXX",
        "sender_mailbox" : "SI56031098765414228"
      }
    }
  }
}
fiddle
The problem is that you need kind of conditional "FORMAT JSON" in the "SELECT JSON_OBJECTAGG ( KEY ID_LEVEL2 VALUECASE ID_LEVEL2": when the ID_LEVEL2 is 'sender_address' but not in the ELSE part, but the syntax requires you put after the END of CASE, and of course this fails for the "ELSE TEXT_LEVEL2" part.

How to update XML attribute in clob Oracle using XMLQuery

Oracle table name: SR_DATA;
Table field name: XMLDATA type CLOB;
Field value:
<module xmlns="http://www.mytest.com/2008/FMSchema">
<tmEsObjective modelCodeScheme="A" modelCodeSchemeVersion="01" modelCodeValue="ES_A"></tmEsObjective>
</module>
I need to update the value of the attribute "modelCodeValue" into ES_B.
This is the code:
UPDATE SR_DATA
SET XMLDATA =
XMLQuery('copy $i := $p1 modify
((for $j in $i/module/tmEsObjective/#modelCodeValue
return replace value of node $j with $p2))
)
return $i'
PASSING XMLType(REPLACE(xmldata, 'xmlns="http://www.mytest.com/2008/FMSchema"', '')) AS "p1",
'ES_B' AS "p2"
RETURNING CONTENT);
This code returns the error code: ORA-00932: inconsistent datatypes: expected CLOB got -
Use getclobval() like this:
UPDATE SR_DATA
SET XMLDATA =
XMLTYPE.GETCLOBVAL(XMLQuery('copy $i := $p1 modify
((for $j in $i/module/tmEsObjective/#modelCodeValue
return replace value of node $j with $p2))
return $i'
PASSING XMLType(REPLACE(xmldata, 'xmlns="http://www.mytest.com/2008/FMSchema"', '')) AS "p1",
'ES_B' AS "p2"
RETURNING CONTENT ));

Unable to fetch data using extractvalue function

I have a table having a XML Type column in Oracle DB. I am trying to use the extractvalue and updatexml functions on that table.
XML
<?xml version = '1.0' encoding = 'UTF-8'?>
<custominfo>
<singlerecord ZIP="51100"/>
<multiplerecord type="ONE_TIME_CHARGES_LIST">
<record DESCRIPTION="Device Payment" RATE="1000000" TAX="0"/>
<record DESCRIPTION="Plan Payment" RATE="480000" TAX="0"/>
<record DESCRIPTION="Deposit" RATE="1000000" TAX="0"/>
</multiplerecord>
</custominfo>
Query
select EXTRACTVALUE(XMLCONTENT,'//record[#DESCRIPTION="Plan Payment"]') from TABLE_XML X;
Can anyone suggest what I am doing wrong here?
After sorting out the extract I want to replace the DESCRIPTION from "Plan Payment" to "Pre Plan Payment".
What you are doing is working - it's giving you the text value of the record node with that attribute value. But that node is empty:
<record DESCRIPTION="Plan Payment" RATE="480000" TAX="0"/>
has three attributes but the node itself has no content - it's an empty tag. So your query is returning null, which is correct. If you wanted to see that you'd found the node you could get an attribute value instead:
select EXTRACTVALUE(XMLCONTENT,'//record[#DESCRIPTION="Plan Payment"]/#DESCRIPTION')
from TABLE_XML X;
However, the extractvalue() function has long been deprecated, so you shouldn't really be using it anyway.
You can use an XMLQuery to view the whole matching record node; as the node has no content there isn't much point in getting its content, as your current query does:
select XMLQuery('$x//record[#DESCRIPTION="Plan Payment"]'
passing xmlcontent as "x"
returning content
) as result
from table_xml;
RESULT
--------------------------------------------------------------------------------
<record DESCRIPTION="Plan Payment" RATE="480000" TAX="0"/>
The updatexml() function is deprecated as well. You can use an XMLQuery to modify the attribute name too:
select XMLQuery('copy $i := $x modify
(for $j in $i//record[#DESCRIPTION="Plan Payment"]/#DESCRIPTION
return replace value of node $j with $r)
return $i'
passing xmlcontent as "x", 'Pre Plan Payment' as "r"
returning content
) as result
from table_xml;
Using a CTE for your sample XML document, and wrapping it in an XMLSerialize call to retain the formatting for readability:
with table_xml (xmlcontent) as (
select xmltype(q'[<?xml version = '1.0' encoding = 'UTF-8'?>
<custominfo>
<singlerecord ZIP="51100"/>
<multiplerecord type="ONE_TIME_CHARGES_LIST">
<record DESCRIPTION="Device Payment" RATE="1000000" TAX="0"/>
<record DESCRIPTION="Plan Payment" RATE="480000" TAX="0"/>
<record DESCRIPTION="Deposit" RATE="1000000" TAX="0"/>
</multiplerecord>
</custominfo>]'
) from dual
)
select XMLSerialize(
document
XMLQuery('copy $i := $x modify
(for $j in $i//record[#DESCRIPTION="Plan Payment"]/#DESCRIPTION
return replace value of node $j with $r)
return $i'
passing xmlcontent as "x", 'Pre Plan Payment' as "r"
returning content
)
indent
) as result
from table_xml;
RESULT
--------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<custominfo>
<singlerecord ZIP="51100"/>
<multiplerecord type="ONE_TIME_CHARGES_LIST">
<record DESCRIPTION="Device Payment" RATE="1000000" TAX="0"/>
<record DESCRIPTION="Pre Plan Payment" RATE="480000" TAX="0"/>
<record DESCRIPTION="Deposit" RATE="1000000" TAX="0"/>
</multiplerecord>
</custominfo>
If you want to update the value in the table you can use the same query as part of an update statement, filtered on rows that match. Read more.

xQuery: test for two specific children with 'and'

Assume I have the following snippet
<persName>
<forename>Gaius</forename>
<surname>Iulius</surname>
<addName>Caesar</addName>
</persName>
I need a result like [surname], [forename] where the comma should only be present if necessary.
In XSLT, I'd simply use
<xsl:value-of select="surname" />
<xsl:if test="surname and forename"><xsl:text>, </xsl:text></xsl:if>
<xsl:value-of select="forename" />
Now I naively thought I could transfer this to XQuery – and failed.
I was slightly puzzled that
if ($node/forename) then "1" else "0"
if ($node/surname) then "1" else "0"
if ($node/surname and $node/forename) then "1" else "0"
will give 1, 1, 0, respectively.
I worked around this by counting the number of children in one of these cases but I'm still puzzled why this is the way it is.
Tanks for any input!
Edit: here's the code I've been using:
declare function habe:shortName($node) {
<span>{
if ($node/tei:name) then $node/tei:name
else
let $tr := if ($node/tei:surname and count($node/tei:forename)>0) then ", " else ""
return concat($node/tei:surname, $tr, $node/tei:forename)
}</span>
};
which, when given the above snippet, returned IuliusGaius.
I then tried the three tests above and got the stated result.
I'm doing this on eXist – maybe their implementation is faulty?
Edit: Thanks to #MichaelKay and #joewiz for hinting at misleading typos!
The following code returns the expected results, (1, 1, 1), using eXide on http://exist-db.org/exist/apps/eXide/index.html:
xquery version "3.0";
let $node :=
<persName>
<forename>Gaius</forename>
<surname>Iulius</surname>
<addName>Caesar</addName>
</persName>
return
(
if ($node/forename) then "1" else "0",
if ($node/surname) then "1" else "0",
if ($node/surname and $node/forename) then "1" else "0"
)
One solution to your original problem (adding a ', ' if both forename and surname exist) is to use string-join($strs as xs:string*, $joiner as xs:string) instead of concat($string as xs:string[...]):
let $name :=
<persName>
<forename>Gaius</forename>
<surname>Iulius</surname>
<addName>Caesar</addName>
</persName>
return string-join(($name/surname, $name/forename), ', ')
This returns Iulius, Gaius.
You can also check for presence of nodes directly in boolean expressions:
let $name :=
<persName>
<forename>Gaius</forename>
<surname>Iulius</surname>
<addName>Caesar</addName>
</persName>
return (
$name/surname and $name/forename,
$name/surname and $name/foo
)
This returns true false.

How can I tell if I'm at the last result when using WHILE so that I can omit a comma from my output?

I know I can do what I need to do by getting a total records count and if I'm at the last record, don't display a comma but there has to be a better way.
I'm trying to build an SQL statement programatically using values from MySQL.
The code:
$fql="SELECT ";
$result = mysql_query("SELECT field FROM fb_aa_fields WHERE fql_table = '$query'", $conn);
while ($row = mysql_fetch_array($result)){
$get_field = "".$row{'field'}."";
$fql = $fql."$get_field, ";
}
$fql = $fql."FROM ".$query." WHERE owner=".$get_uid."";
It outputs this:
SELECT aid, can_upload, cover_object_id, cover_pid, created, description, edit_link, link, location, modified, modified_major, name, object_id, owner, photo_count, size, type, video_count, visible, FROM album WHERE owner=522862206
The problem is the last comma between "visible" and "FROM". How would you suggest is the best way to make that comma go away?
It's less of a pain to detect whether you're at the first element than the last. You could do like
$i = 0;
while($row =...) {
if ($i++) $fql .= ',';
$fql .= $row['field'];
}
Or, possibly better, defer tacking on fields to the string til the end. There's a built-in function called implode, that you can use to insert the commas between them.
$fields = array();
while($row =...) {
$fields[] = $row['field'];
}
$fql .= implode(',', $fields);