【问题标题】:Number multiplied by constant results in a negative number数乘以常数得到负数
【发布时间】:2021-02-17 15:26:49
【问题描述】:

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

问题是当我不使用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;

【问题讨论】:

    标签: sql oracle oracle-sqldeveloper operator-precedence


    【解决方案1】:

    你不需要(也不应该使用)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查看完整的荣耀/恐怖。)

    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() 时,您将覆盖默认优先级,但会不必要地隐式转换为字符串并显式转换回数字。只需括号就足以覆盖默认行为。

    【讨论】:

    • 我永远不会想到这些表达式并没有立即解决,而是作为一个整体被替换了。这完全有道理!同时也很可怕。
    • 是的,我想这是与 Gary 的 PL/SQL 版本的主要区别——它们在分配时被评估,但这不是替换变量的工作方式。 (当然,您仍然需要小心precedence!)如果您在会话中使用set verify on,那么您可以在替换前后看到旧/新查询。它们实际上比我展示的要扩展,但您仍然可以看到问题。
    【解决方案2】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-20
      • 1970-01-01
      • 2013-11-07
      • 2017-04-01
      • 1970-01-01
      • 2018-12-26
      相关资源
      最近更新 更多