【问题标题】:Oracle comparing day to second interval to an integerOracle 将天到秒的时间间隔与整数进行比较
【发布时间】:2019-03-30 03:28:44
【问题描述】:

我想知道当我尝试用整数比较天数和秒数间隔时,oracle 的幕后发生了什么。

以下示例。

SET SERVEROUTPUT ON;
DECLARE
    v_date1 TIMESTAMP := current_timestamp ;
    v_date2 TIMESTAMP := current_timestamp - 150;
BEGIN
    -- Wrong way. But what is happening here?
    IF v_date1 - v_date2 < 2 THEN
        DBMS_OUTPUT.PUT_LINE('YES - why?');
    ELSE
        DBMS_OUTPUT.PUT_LINE('NO');
    END IF;
    -- Correct way
    IF v_date1 < v_date2 + 2 THEN
        DBMS_OUTPUT.PUT_LINE('YES');
    ELSE
        DBMS_OUTPUT.PUT_LINE('NO - works as expected');
    END IF;
    -- Another correct way
    IF v_date1 - v_date2 < INTERVAL '2' DAY THEN
        DBMS_OUTPUT.PUT_LINE('YES');
    ELSE
        DBMS_OUTPUT.PUT_LINE('NO - works as expected as well');
    END IF;
END;

有人可以向我解释为什么第一个 IF 被评估为真吗?

【问题讨论】:

  • 有趣。在 SQL 中,WHERE INTERVAL '1' MINUTE &lt; 2 之类的条件会失败,而 PL/SQL 中的 IF INTERVAL '1' MINUTE &lt; 2 不会引发错误。即使在 18c 中也有相同的行为:dbfiddle.uk/…

标签: oracle plsql types oracle12c intervals


【解决方案1】:

您所看到的似乎是一个错误,或者至少是未记录的行为。所以不幸的是,我认为您需要向 Oracle 提出服务请求以获得解释。只有他们才能窥视引擎盖下的情况,看看它是否在做他们期望的事情,或者是他们没有想到的事情。


分解一些步骤,而不仅仅是针对您的特定问题:

v_date1 TIMESTAMP := current_timestamp ;

current_timestamp(即会话时区中的时间)转换为纯时间戳 - 丢失时区信息。

v_date2 TIMESTAMP := current_timestamp - 150;

有两个步骤; current_timestamp - 150 为您提供日期,丢失小数秒时区信息,然后将其转换回时间戳。使用- interval '150' day 会更好,但我意识到你只是在这里设置了一些虚拟数据。

IF v_date1 - v_date2 < 2 THEN

也是多个阶段:v_date1 - v_date2 给你一个间隔,例如+150 00:00:00.975444。 (请注意,这并不完全是 150 天,部分原因是两次 current_dtimestamp 调用之间经过的时间很短,但比您从这个差异中预期的要大得多 - 因为早先反弹通过日期数据类型。)

那么比较本身就是有趣的地方。看起来正在发生隐式数据转换,但是您正在尝试将间隔与数字进行比较,并且没有隐式转换允许这样做;并在普通 SQL 中尝试执行相同的比较错误:

select case when current_timestamp - (current_timestamp - 150) < 2 then 'yes' else 'no' end from dual;

ORA-00932: inconsistent datatypes: expected INTERVAL DAY TO SECOND got NUMBER

所以看起来 PL/SQL 正在执行其他一些隐式转换,但不清楚是什么,而且似乎没有在 MoS 上记录或提及。它似乎不是字符串比较,也不是原始的,或者将 2 转换为间隔类型,或者将间隔转换为数字,或者将计算中的间隔或时间戳转换为日期......

似乎没有任何数值使该比较返回错误 - 无论左侧是计算(可能会被重写为更像您的第二个版本)还是固定间隔变量。这使它看起来更像是一个错误而不是故意的。

原始代码中的最后一件事:

-- Correct way
IF v_date1 < v_date2 + 2 THEN

这也不是很正确,尽管它可能看起来很挑剔; v_date + 2 为您提供日期而不是时间戳,因此,如果您之前没有丢失小数秒精度(使用 - 150),那么此时您会丢失。现在您将时间戳与日期进行比较,这涉及到更多的隐式转换。您可以将数字 2 更改为 2 天的间隔,但这在逻辑上与您的最终(正确)比较相同。

【讨论】:

  • 我也试过 to_number(v_date1 - v_date2)CAST(2 AS INTERVAL DAY TO SECOND) 但它们都失败了,即使在 PL/SQL IF 语句中也是如此。事实上非常神秘。
  • “差异不完全是 150 天”不仅因为丢失了小数秒,还因为在不同的时间分配了 CURRENT_TIMESTAMP。它不像 SQL 那样工作,其中 CURRENT_TIMESTAMP 对于单个查询中的所有调用都是相同的。相反,每次调用都会获取一个新值。一个快速而简单的测试显示,在我的系统上,同一匿名块中的两次连续调用之间存在 0.000006 秒的差异。
  • 感谢您花时间在这个问题上。你真的没有写任何有助于回答我的问题的东西,但这篇文章很有趣,我认为不会提供更好的答案。 ;)
  • 不,同意 *8-) 第一段是唯一有帮助的部分,而且几乎没有......我开始写剩下的部分,同时假设我会弄清楚,然后想我最好还是发布它以防止其他人重复我所做的事情。
【解决方案2】:

我猜这不是错误,而是错误的源代码。

您应该将间隔视为 INTERVAL

DECLARE
    v_date1 TIMESTAMP := CURRENT_TIMESTAMP ;
    v_date2 TIMESTAMP := v_date1 - INTERVAL '150' DAY;
BEGIN
    DBMS_OUTPUT.PUT_LINE(v_date1 - v_date2);
    -- Right way.
    IF v_date1 - v_date2 < INTERVAL '2' DAY THEN
        DBMS_OUTPUT.PUT_LINE('YES - why?');
    ELSE
        DBMS_OUTPUT.PUT_LINE('NO');
    END IF;
    -- Right way
    IF v_date1 < v_date2 + INTERVAL '2' DAY THEN
        DBMS_OUTPUT.PUT_LINE('YES');
    ELSE
        DBMS_OUTPUT.PUT_LINE('NO - works as expected');
    END IF;
    -- Right correct way
    IF v_date1 - v_date2 < INTERVAL '2' DAY THEN
        DBMS_OUTPUT.PUT_LINE('YES');
    ELSE
        DBMS_OUTPUT.PUT_LINE('NO - works as expected as well');
    END IF;
END;

+000000150 00:00:00.000000000
NO
NO - works as expected
NO - works as expected as well

【讨论】:

  • 嘿,感谢您的回答,我知道我应该将间隔视为 INTERVAL。我的问题是,当我改用整数时,oracle 的底层发生了什么。 ;)
  • 太好了,..只是想指出解决用例的正确方法。干杯!
猜你喜欢
  • 1970-01-01
  • 2018-04-11
  • 1970-01-01
  • 2013-08-12
  • 1970-01-01
  • 2014-07-02
  • 2017-05-11
  • 1970-01-01
相关资源
最近更新 更多