oracle-databaseoracle11gxqueryoracle-xml-db

Comparing dates in Oracle XQuery (XMLQuery/XMLTable)


While trying to implement some checks in Oracle database with use of XQuery I need to compare if two dates are the same, but can't do that on obvious manner because casting to xs:date don't really remove time part of value from xs:dateTime.

Query per se seems to work good in another environment (e.g. http://www.xpathtester.com/xquery ).

Is I missed something important, or this case is just a bug and need special workaround (converting to string values for compare, compare years, months and dates of both dates separately and so on)?


A small example ...

Suppose we have a simple XML:

<root>
  <date_value>2015-09-11T15:25:55</date_value>
</root> 

and want compare date_value with fixed value xs:date('2015-09-11') ignoring time part.

First, convert content of the node to desired type and remove time part by casting it to xs:date :

xs:date(xs:dateTime($doc/root/date_value))

If we select this value with XMLQuery() while passing document above as $doc, we got expected output:

2015-09-11+00:00 

Ok. Seems that time part removed, but comparison fails:

xs:date(xs:dateTime($doc/root/date_value)) eq xs:date('2015-09-11') 

returns false, and if we try to look at difference between values in expression instead of comparing them:

xs:date(xs:dateTime($doc/root/date_value)) - xs:date('2015-09-11') 

we see 'PT15H25M55S', which are exactly matches time part of date_value.

Query with all expressions above for testing:

select 
  XMLCast(
    XMLQuery( column_value
      passing 
        xmltype(q'[
          <root>
            <date_value>2015-09-11T15:25:55</date_value>
          </root> 
        ]') as "doc"
      returning content
    )
    as varchar2(4000)
  ) result_value,
  column_value  expression
from 
  table(sys.odcivarchar2list(
    q'[ xs:date(xs:dateTime($doc/root/date_value)) ]',
    q'[ xs:date('2015-09-11') ]',
    q'[ xs:date(xs:dateTime($doc/root/date_value)) eq xs:date('2015-09-11') ]',
    q'[ xs:date(xs:dateTime($doc/root/date_value)) - xs:date('2015-09-11') ]'
  ))

Behavior reproduced on this Oracle versions:

Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
PL/SQL Release 12.1.0.2.0 - Production
CORE    12.1.0.2.0  Production
TNS for IBM/AIX RISC System/6000: Version 12.1.0.2.0 - Production
NLSRTL Version 12.1.0.2.0 - Production

and

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
CORE    11.2.0.3.0  Production
TNS for IBM/AIX RISC System/6000: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production

Update

Thanks to collapsar and Alex Poole for answers which give me main idea about correct workaround. But trying to explain core of problem I oversimplified our use case which includes some date arithmetic and real-world workaround would look like a query below.

select 
  XMLCast(
    XMLQuery( 
      q'[
        let 
          $date1 := fn:dateTime( 
                      adjust-date-to-timezone(
                        xs:date(xs:dateTime($doc/root/date_value)),
                        ()
                      ),
                      adjust-time-to-timezone( xs:time('00:00'), ())
                    ),
          $date2 := fn:dateTime( 
                      adjust-date-to-timezone(
                        xs:date(xs:dateTime($doc/root/date_value2)),
                        ()
                      ),
                      adjust-time-to-timezone( xs:time('00:00'), ())
                    )
        return
          $date1 + xs:yearMonthDuration('P1Y') - xs:dayTimeDuration('P1D')
          eq
          $date2
      ]'
      passing 
        xmltype(q'[
          <root>
            <date_value>2015-09-11T01:02:03-11:00</date_value>
            <date_value2>2016-09-10T10:20:30+13:00</date_value2>
          </root> 
        ]') as "doc"
      returning content
    )
    as varchar2(4000)
  ) result_value
from 
  dual

Solution

  • Lifting the literal date value to a dateTime value does the trick (extracting the proper time-of-day offset from the supplied dateTime value ):

     xs:dateTime($doc/root/date_value) eq fn:dateTime(xs:date('2015-09-11'), xs:time(xs:dateTime($doc/root/date_value)))
    

    This solution will also work for inputs lexicalized as dates only.