比较 Oracle XQuery 中的日期 (XMLQuery/XMLTable)
Comparing dates in Oracle XQuery (XMLQuery/XMLTable)
在尝试使用 XQuery
在 Oracle 数据库中实施一些检查时,我需要比较两个日期是否相同,但不能以明显的方式这样做,因为转换为 xs:date
不要真正从 xs:dateTime
中删除值的时间部分。
查询本身似乎在另一个环境中运行良好(例如 http://www.xpathtester.com/xquery)。
我是否错过了一些重要的事情,或者这种情况只是一个错误,需要特殊的解决方法(转换为字符串值以进行比较,分别比较两个日期的年、月和日期等等)?
一个小例子...
假设我们有一个简单的 XML:
<root>
<date_value>2015-09-11T15:25:55</date_value>
</root>
并希望将 date_value
与固定值 xs:date('2015-09-11')
进行比较,忽略时间部分。
首先,将节点的内容转换为所需的类型,并通过将其强制转换为 xs:date
来删除时间部分:
xs:date(xs:dateTime($doc/root/date_value))
如果我们 select 这个值与 XMLQuery()
同时将上面的文档作为 $doc
传递,我们得到预期的输出:
2015-09-11+00:00
好的。似乎删除了时间部分,但比较失败:
xs:date(xs:dateTime($doc/root/date_value)) eq xs:date('2015-09-11')
returns false
,如果我们尝试查看表达式中值之间的差异而不是比较它们:
xs:date(xs:dateTime($doc/root/date_value)) - xs:date('2015-09-11')
我们看到 'PT15H25M55S'
,它们与 date_value
的时间部分完全匹配。
使用以上所有表达式进行查询以进行测试:
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') ]'
))
在此 Oracle 版本上重现的行为:
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
和
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
更新
感谢 collapsar and Alex Poole 的回答,让我对正确的解决方法有了主要的了解。但是为了解释问题的核心,我过度简化了我们的用例,其中包括一些日期算法和现实世界的解决方法看起来像下面的查询。
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
将文字日期值提升为日期时间值就可以了(从提供的日期时间值中提取正确的时间偏移):
xs:dateTime($doc/root/date_value) eq fn:dateTime(xs:date('2015-09-11'), xs:time(xs:dateTime($doc/root/date_value)))
此解决方案也适用于仅按日期词汇化的输入。
转换回dateTime
可以看出时间已经保留了;这没有帮助,但我不确定它是错误还是预期行为 - 我想后者在 Oracle 的世界中,并且在 MOS 中看不到任何对此行为的引用...
您可以改为比较 dateTime
范围:
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') ]',
q'[ xs:dateTime($doc/root/date_value) ]',
q'[ xs:dateTime(xs:date(xs:dateTime($doc/root/date_value))) ]',
q'[ xs:dateTime($doc/root/date_value) ge xs:dateTime('2015-09-11T00:00:00') ]',
q'[ xs:dateTime($doc/root/date_value) lt xs:dateTime('2015-09-12T00:00:00') ]',
q'[ xs:dateTime($doc/root/date_value) ge xs:dateTime('2015-09-11T00:00:00')
and xs:dateTime($doc/root/date_value) lt xs:dateTime('2015-09-12T00:00:00') ]'
))
/
给出:
RESULT_VALUE EXPRESSION
---------------------------------------- -------------------------------------------------------------------------------------
2015-09-11+00:00 xs:date(xs:dateTime($doc/root/date_value))
2015-09-11+00:00 xs:date('2015-09-11')
false xs:date(xs:dateTime($doc/root/date_value)) eq xs:date('2015-09-11')
PT15H25M55S xs:date(xs:dateTime($doc/root/date_value)) - xs:date('2015-09-11')
2015-09-11T15:25:55.000000+00:00 xs:dateTime($doc/root/date_value)
2015-09-11T15:25:55.000000+00:00 xs:dateTime(xs:date(xs:dateTime($doc/root/date_value)))
true xs:dateTime($doc/root/date_value) ge xs:dateTime('2015-09-11T00:00:00')
true xs:dateTime($doc/root/date_value) lt xs:dateTime('2015-09-12T00:00:00')
true xs:dateTime($doc/root/date_value) ge xs:dateTime('2015-09-11T00:00:00')
and xs:dateTime($doc/root/date_value) lt xs:dateTime('2015-09-12T00:00:00')
您需要生成两个日期来进行比较,而不是一个,这对您来说可能是问题,也可能不是问题,具体取决于这些日期的来源以及您构建真实查询的方式。可能比@collapsar 的子字符串更复杂,但也可以说更明确一些。
在尝试使用 XQuery
在 Oracle 数据库中实施一些检查时,我需要比较两个日期是否相同,但不能以明显的方式这样做,因为转换为 xs:date
不要真正从 xs:dateTime
中删除值的时间部分。
查询本身似乎在另一个环境中运行良好(例如 http://www.xpathtester.com/xquery)。
我是否错过了一些重要的事情,或者这种情况只是一个错误,需要特殊的解决方法(转换为字符串值以进行比较,分别比较两个日期的年、月和日期等等)?
一个小例子...
假设我们有一个简单的 XML:
<root>
<date_value>2015-09-11T15:25:55</date_value>
</root>
并希望将 date_value
与固定值 xs:date('2015-09-11')
进行比较,忽略时间部分。
首先,将节点的内容转换为所需的类型,并通过将其强制转换为 xs:date
来删除时间部分:
xs:date(xs:dateTime($doc/root/date_value))
如果我们 select 这个值与 XMLQuery()
同时将上面的文档作为 $doc
传递,我们得到预期的输出:
2015-09-11+00:00
好的。似乎删除了时间部分,但比较失败:
xs:date(xs:dateTime($doc/root/date_value)) eq xs:date('2015-09-11')
returns false
,如果我们尝试查看表达式中值之间的差异而不是比较它们:
xs:date(xs:dateTime($doc/root/date_value)) - xs:date('2015-09-11')
我们看到 'PT15H25M55S'
,它们与 date_value
的时间部分完全匹配。
使用以上所有表达式进行查询以进行测试:
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') ]'
))
在此 Oracle 版本上重现的行为:
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
和
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
更新
感谢 collapsar and Alex Poole 的回答,让我对正确的解决方法有了主要的了解。但是为了解释问题的核心,我过度简化了我们的用例,其中包括一些日期算法和现实世界的解决方法看起来像下面的查询。
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
将文字日期值提升为日期时间值就可以了(从提供的日期时间值中提取正确的时间偏移):
xs:dateTime($doc/root/date_value) eq fn:dateTime(xs:date('2015-09-11'), xs:time(xs:dateTime($doc/root/date_value)))
此解决方案也适用于仅按日期词汇化的输入。
转换回dateTime
可以看出时间已经保留了;这没有帮助,但我不确定它是错误还是预期行为 - 我想后者在 Oracle 的世界中,并且在 MOS 中看不到任何对此行为的引用...
您可以改为比较 dateTime
范围:
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') ]',
q'[ xs:dateTime($doc/root/date_value) ]',
q'[ xs:dateTime(xs:date(xs:dateTime($doc/root/date_value))) ]',
q'[ xs:dateTime($doc/root/date_value) ge xs:dateTime('2015-09-11T00:00:00') ]',
q'[ xs:dateTime($doc/root/date_value) lt xs:dateTime('2015-09-12T00:00:00') ]',
q'[ xs:dateTime($doc/root/date_value) ge xs:dateTime('2015-09-11T00:00:00')
and xs:dateTime($doc/root/date_value) lt xs:dateTime('2015-09-12T00:00:00') ]'
))
/
给出:
RESULT_VALUE EXPRESSION
---------------------------------------- -------------------------------------------------------------------------------------
2015-09-11+00:00 xs:date(xs:dateTime($doc/root/date_value))
2015-09-11+00:00 xs:date('2015-09-11')
false xs:date(xs:dateTime($doc/root/date_value)) eq xs:date('2015-09-11')
PT15H25M55S xs:date(xs:dateTime($doc/root/date_value)) - xs:date('2015-09-11')
2015-09-11T15:25:55.000000+00:00 xs:dateTime($doc/root/date_value)
2015-09-11T15:25:55.000000+00:00 xs:dateTime(xs:date(xs:dateTime($doc/root/date_value)))
true xs:dateTime($doc/root/date_value) ge xs:dateTime('2015-09-11T00:00:00')
true xs:dateTime($doc/root/date_value) lt xs:dateTime('2015-09-12T00:00:00')
true xs:dateTime($doc/root/date_value) ge xs:dateTime('2015-09-11T00:00:00')
and xs:dateTime($doc/root/date_value) lt xs:dateTime('2015-09-12T00:00:00')
您需要生成两个日期来进行比较,而不是一个,这对您来说可能是问题,也可能不是问题,具体取决于这些日期的来源以及您构建真实查询的方式。可能比@collapsar 的子字符串更复杂,但也可以说更明确一些。