数字乘以常数结果为负数

Number multiplied by constant results in a negative number

我正在尝试计算一年前相应一周的日期。它现在似乎可以正常工作,但有一件事让我很困扰,它最初导致了我通过尝试各种方法修复的几个错误。

问题是,当我不使用 to_number&weekOfYearNow - &weekOfYearThen 并使用此结果计算 lastYearFixed 时,结果将是 -311 而不是7。为什么会这样?这两个变量 (weekOfYearNow & weekOfYearThen) 不是已经是数字了吗?

define today = to_date('30/12/20', 'dd/mm/yy');
define lastYear = add_months(&today, -12);

select &today as "Today" from sys.dual;
select &lastYear as "LastYear" from sys.dual;

define weekOfYearNow = to_number(to_char(&today, 'ww'));
define weekOfYearThen = to_number(to_char(&lastYear, 'ww'));
define weekOfYearOffset = to_number(&weekOfYearNow - &weekOfYearThen); -- <-- must use to_number - but why?

define lastYearFixed = &lastYear + (&weekOfYearOffset * 7); -- <-- this equals -311 if I don't call to_number above

define monday = add_months(sysdate - 7 - to_char(sysdate, 'd') + 2, -12);
define sunday = add_months(sysdate - 7 + to_char(sysdate, 'd'), -12);

select &weekOfYearNow as "WeekOfYearNow" from sys.dual;
select &weekOfYearThen as "WeekOfYearThen" from sys.dual;
select &weekOfYearOffset as "WeekOfYearOffset" from sys.dual;
select &lastYearFixed as "LastYearFixed" from sys.dual;

您不需要(也不应该使用)to_number();但您确实需要将第一个表达式括在括号中,如:

define weekOfYearOffset = (&weekOfYearNow - &weekOfYearThen);
define lastYearFixed = &lastYear + (&weekOfYearOffset * 7);

define weekOfYearOffset = &weekOfYearNow - &weekOfYearThen;
define lastYearFixed = &lastYear + ((&weekOfYearOffset) * 7);

如果没有其中任何一个,当替换被扩展时,你有效地得到:

define lastYearFixed = &lastYear + (&weekOfYearNow - &weekOfYearThen * 7);

(它实际上扩展了所有这些术语以显示嵌套的 to_date()to_number() 等调用,但为了简洁起见,这给出了想法;重点是它们还不是数字,并且尚未完成任何转换或计算。您可以 set verify on 查看完整的 glory/horror。)

operator precedence* 优先级高于(二进制)-,这意味着它等同于:

define lastYearFixed = &lastYear + (&weekOfYearNow - (&weekOfYearThen * 7));

所以你要加 weekOfYearNow 并减去 7 次 weekOfYearThen;而不是将它们之间的差值相加 7 倍。

数量:

select 53 - 52 * 7 as a,
       53 - (52 * 7) as b,
       (53 - 52) * 7 as c
from dual;

    A     B     C
----- ----- -----
 -311  -311     7

当您使用 to_number() 时,您将超越默认优先级,但会进行不必要的隐式转换为字符串并显式转换回数字。仅括号就足以覆盖默认行为。

我想说我打赌这是一件 SQL/Plus 事情(Alex 证明了这一点,感谢 Alex)当我将它转换为 PL/SQL 匿名块并 运行 它时在 Toad 中,它按预期工作。我不打算 post 这个,因为它不是真正的答案,但是比较语言并注意可能会咬你的陷阱有点有趣,你应该记住,所以我会把它留到为了学习。

SET serveroutput ON

DECLARE
  today DATE;
  lastyear DATE;
  weekOfYearNow NUMBER;
  weekOfYearThen NUMBER;
  weekOfYearOffset NUMBER;
  lastYearFixed DATE;
  monday DATE;
  sunday DATE;
BEGIN
 today := TO_DATE('30/12/20', 'dd/mm/yy');
 lastYear := ADD_MONTHS(today, -12);

 DBMS_OUTPUT.PUT_LINE('Today: ' || today);
 DBMS_OUTPUT.PUT_LINE('LastYear: ' || lastYear);

 weekOfYearNow    := TO_NUMBER(TO_CHAR(today, 'ww'));
 weekOfYearThen   := TO_NUMBER(TO_CHAR(lastYear, 'ww'));
 weekOfYearOffset := weekOfYearNow - weekOfYearThen; -- <-- must use to_number - but why?

 lastYearFixed := lastYear + (weekOfYearOffset * 7); -- <-- this equals -311 if I don't call to_number above

 monday := ADD_MONTHS(SYSDATE - 7 - TO_CHAR(SYSDATE, 'd') + 2, -12);
 sunday := ADD_MONTHS(SYSDATE - 7 + TO_CHAR(SYSDATE, 'd'), -12);

 DBMS_OUTPUT.PUT_LINE('WeekOfYearNow: ' || WeekOfYearNow);
 DBMS_OUTPUT.PUT_LINE('WeekOfYearThen: ' || WeekOfYearThen);
 DBMS_OUTPUT.PUT_LINE('WeekOfYearOffset: ' || WeekOfYearOffset);
 DBMS_OUTPUT.PUT_LINE('LastYearFixed: ' || LastYearFixed);

END;

Today: 30-DEC-20
LastYear: 30-DEC-19
WeekOfYearNow: 53
WeekOfYearThen: 52
WeekOfYearOffset: 1
LastYearFixed: 06-JAN-20