Ik denk dat je het probleem kunt vermijden door lege tekst te vervangen door een tijdelijke waarde, alle andere tekst bij te werken en vervolgens de tijdelijke waarde te vervangen door een null.
Ik begrijp XPath niet, er is waarschijnlijk een veel betere manier om dit te doen, maar dit lijkt te werken:
SELECT
--#3: Replace the temporary value with null, this keeps the start and end tag
UpdateXML(
--#2: Replace everything but the temporary value
UpdateXML(
--#1: Replace empty text with a temporary value
UpdateXML(xmlData, '/TEST/VALUE[not(text())]', '<VALUE>TEMPORARY VALUE</VALUE>')
,'/TEST/VALUE[text()!="TEMPORARY VALUE"]/text()', 'hello')
,'/TEST/VALUE[text()="TEMPORARY VALUE"]/text()', null) examle
FROM (SELECT XMLType('<TEST><VALUE>hi</VALUE><VALUE>hola</VALUE><VALUE></VALUE></TEST>') as xmlData FROM DUAL);