数乘以常数得到负数
Posted
技术标签:
【中文标题】数乘以常数得到负数【英文标题】: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;
【问题讨论】:
【参考方案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
【讨论】:
以上是关于数乘以常数得到负数的主要内容,如果未能解决你的问题,请参考以下文章