Jag tror att du kan undvika problemet genom att ersätta tom text med ett tillfälligt värde, uppdatera all annan text och sedan ersätta det tillfälliga värdet med en null.
Jag förstår inte XPath, det finns förmodligen ett mycket bättre sätt att göra detta på, men det här verkar fungera:
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);