【问题标题】:Oracle determinism requirements and idiosyncrasiesOracle 确定性要求和特性
【发布时间】:2018-12-10 01:39:58
【问题描述】:

我一直对我对周期性出现的一个问题缺乏了解感到困扰:功能确定性。

docs看来,似乎还算清楚:

DETERMINISTIC 函数可能没有副作用。

DETERMINISTIC 函数可能不会引发未处理的异常。

由于这些是重要的核心概念,在标准包中具有健壮、集中的实现,我认为不存在错误或任何东西(错误在于我的假设和理解,而不是 Oracle)。话虽如此,这两个要求有时似乎在标准包以及 DBMS_ 和 UTL_ 包中都有一些特殊用途。

我希望发布几个 Oracle 函数示例,这些示例在我使用 DETERMINISTIC 以及这些限制中的细微差别时对我提出了一些疑问,看看是否有人可以解释它们是如何组合在一起的。我很抱歉这是一个“为什么”的问题,如果需要可以迁移,但对这个问题的回答:(Is it ok to ask a question where you've found a solution but don't know why something was behaving the way it was?) 让我认为它可能适合 SO。

在我的编码过程中,我经常不确定自己的 UDF 是否符合纯粹的条件,而在其他时候,我会使用令我惊讶的 Oracle 函数来发现它们是不纯的。如果有人可以看看并提供建议,我将不胜感激。

作为第一个示例,TO_NUMBER。这个函数看起来很纯粹,但它也会抛出异常。在这个例子中,我将在一个虚拟列中使用TO_NUMBER(这里应该需要DETERMINISTIC

CREATE TABLE TO_NUMBER_IS_PURE_BUT_THROWS (
  SOURCE_TEXT    CHARACTER VARYING(5 CHAR) ,
  NUMERICIZATION NUMBER(5 , 0) GENERATED ALWAYS AS (TO_NUMBER(SOURCE_TEXT , '99999')) ,
  CONSTRAINT POSITIVE_NUMBER CHECK (NUMERICIZATION >= 0)
);

Table TO_NUMBER_IS_PURE_BUT_THROWS created.

INSERT INTO TO_NUMBER_IS_PURE_BUT_THROWS VALUES ('0',DEFAULT);
INSERT INTO TO_NUMBER_IS_PURE_BUT_THROWS VALUES ('88088',DEFAULT);
INSERT INTO TO_NUMBER_IS_PURE_BUT_THROWS VALUES ('UH-OH',DEFAULT);

1 row inserted.
1 row inserted.
ORA-01722: invalid number

ORA-01722 似乎违反了未处理异常的要求。大概我创建的任何通过TO_NUMBER 强制转换的函数都应该处理这种可能性以保持纯净。但是在这里抛出异常似乎是合适且可靠的。关于异常是否违反引用透明性似乎存在一些争论 (Why is the raising of an exception a side effect?)

我遇到的第二种情况是系统函数,它们看起来应该 DETERMINISTIC 但不是。他们被认为是不纯的肯定有一些原因。在某些情况下,内部结构会产生副作用似乎是深不可测的。

这方面的一个极端例子可能是DBMS_ASSERT.NOOP,尽管还有很多其他例子。该函数返回未修改的输入。怎么可能是不确定的?

CREATE TABLE HOW_IS_NOOP_IMPURE (
  SOURCE_TEXT VARCHAR2(256 BYTE),
  COPY_TEXT VARCHAR2(256 BYTE) GENERATED ALWAYS AS (DBMS_ASSERT.NOOP(SOURCE_TEXT)),
  CONSTRAINT COPY_IS_NOT_NULL CHECK(COPY_TEXT IS NOT NULL)
);

产量:

ORA-30553: The function is not deterministic

大概它违反了确定性的要求,但这很难想象。我想知道我在假设这样的函数是确定性的时遗漏了什么。

编辑回应 Lukasz 关于会话设置的评论:
如果跨会话可重复性是 NOOP 不是 DETERMINISTIC 之类的函数的根本原因,我可以接受它,但 TO_CHAR 是确定性的/有资格在虚拟列等中使用。但似乎对其格式掩码中的会话设置很敏感:

ALTER SESSION SET NLS_NUMERIC_CHARACTERS = '._';
Session altered.

CREATE TABLE TO_CHAR_NLS(
  INPUT_NUMBER NUMBER(6,0),
  OUTPUT_TEXT CHARACTER VARYING(64 CHAR) GENERATED ALWAYS AS (TO_CHAR(INPUT_NUMBER,'999G999'))
);
Table TO_CHAR_NLS created.

INSERT INTO TO_CHAR_NLS VALUES (123456,DEFAULT);
INSERT INTO TO_CHAR_NLS VALUES (111222,DEFAULT);
SELECT INPUT_NUMBER, OUTPUT_TEXT FROM TO_CHAR_NLS ORDER BY 1 ASC;

1 row inserted.
1 row inserted.
  INPUT_NUMBER OUTPUT_TEXT
        111222  111_222
        123456  123_456

【问题讨论】:

  • 我猜DBMS_ASSERT.NOOP 可能因为str DBMS_ASSERT.NOOP (str VARCHAR2 CHARACTER SET ANY_CS) 不纯,它可能会因会话NLS_ 参数值而异。简而言之:执行之间的字符集可能不同。至于来自 Oracle12cR2 的 TO_NUMBERDEFAULT ON CONVERSION ERROR 子句。
  • 谢谢卢卡斯。我也想知道会话设置——日期格式、编码等,我可以接受这是确定性的要求,但似乎一些纯函数也响应 nls/会话设置。我将更新 to_char 的另一个示例
  • 在我看来,这归结为实用主义。一个函数可以被标记为确定性,即使它延伸(甚至破坏)规则,只要它足够有用,值得潜在的风险。使用 to_number 风险很低并且非常有用。有了 noop,风险可能会更低,但好处在哪里?

标签: oracle plsql deterministic


【解决方案1】:

ORA-01722 似乎违反了未处理异常 要求。大概是我通过 TO_NUMBER 创建的任何函数 应该处理这种可能性以保持纯洁。

首先,我必须感谢您提出这么好的问题。现在,当您说您使用TO_NUMBER 时,它应该将所有输入的文本转换为函数,但您应该知道TO_NUMBER 有一些限制。 根据TO_NUMBER定义:

TO_NUMBER 函数转换格式化的 TEXT 或 NTEXT 表达式 到一个号码。该函数通常用于转换 一个应用程序的格式化数字输出(包括货币符号、十进制标记、千位组标记等 第四),以便它可以用作另一个应用程序的输入。

它清楚地说,它曾经投射formatted numerical output of one application,这意味着TO_NUMBER本身期望一个数字输入,当你写如下:

INSERT INTO TO_NUMBER_IS_PURE_BUT_THROWS VALUES ('UH-OH',DEFAULT);

您将意外输入完全传递给 TO_NUMBER 函数,因此它会按预期行为抛出错误 ORA-01722: invalid number

阅读更多关于TO_NUMBER的信息。

其次,

一个极端的例子可能是 DBMS_ASSERT.NOOP 虽然有 很多其他的。该函数返回未修改的输入。怎么会这样 不确定性?

DBMS_ASSERT.NOOP 函数可用于有人通过变量传递实际代码片段并且不希望检查它是否存在 SQL 注入攻击。 这必须是不确定的,因为它只是返回我们输入到函数的内容。

我给你举个例子来说明为什么必须是non-deterministic

假设我创建了一个函数years_from_todaydeterministic

CREATE OR REPLACE FUNCTION years_from_today
 ( p_date   IN DATE )
RETURN NUMBER DETERMINISTIC IS
BEGIN
  RETURN ABS(MONTHS_BETWEEN(SYSDATE, p_date) / 12);
END years_from_today;
/

现在我创建一个表并在如下查询中使用此函数:

CREATE TABLE det_test AS
SELECT TO_DATE('01-JUL-2009', 'DD-MON-YYYY') AS date_value 
FROM   dual;

SELECT date_value, SYSDATE, years_from_today(date_value)
FROM   det_test
WHERE  years_from_today(date_value) < 2;

输出

DATE_VALU SYSDATE   YEARS_FROM_TODAY(DATE_VALUE)
--------- --------- ----------------------------
01-JUL-09 20-SEP-10                   1.21861774

然后我在新表上创建一个基于函数的索引。

CREATE INDEX det_test_fbi ON det_test (years_from_today(date_value));

现在,要了解我们选择DETERMINISTIC 的含义,请更改服务器上的日期(当然是在测试环境中)以提前一整年。即使日期已更改,再次运行查询仍会从 YEARS_FROM_TODAY 返回与之前相同的值以及同一行,因为使用的是索引而不是执行函数。

SELECT date_value, SYSDATE, years_from_today(date_value)
FROM   det_test
WHERE  years_from_today(date_value) < 2;

输出:

DATE_VALU SYSDATE   YEARS_FROM_TODAY(DATE_VALUE)
--------- --------- ----------------------------
01-JUL-09 20-SEP-11                    1.2186201 

如果没有WHERE 子句,查询应返回以下内容:

DATE_VALU SYSDATE   YEARS_FROM_TODAY(DATE_VALUE)
--------- --------- ----------------------------
01-JUL-09 20-SEP-11                   2.21867063

从错误输出中可以看出,function 永远不应被创建为确定性的,除非它会在给定相同参数的情况下 ALWAYS 返回相同的值。

因此,您对DBMS_ASSERT.NOOP 的假设并非在所有情况下都成立。

【讨论】:

  • 谢谢邢!我同意,我提供的示例为TO_NUMBER 提供了不合理的数据。从您的描述中听起来您不会认为TO_NUMBER 异常违反A DETERMINISTIC function may not raise an unhandled exception.,因为它清楚地定义了它的合同和预期的输入范围。那是对的吗?对于NOOPENQUOTE_LITERAL 等与文本相关的函数,我可以接受这样的想法,即系统配置更改可能会使它们的确定性无效,尽管为此目的拥有一组确定性的函数会很好。
猜你喜欢
  • 2019-04-25
  • 1970-01-01
  • 2017-05-28
  • 2011-11-18
  • 2015-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-26
相关资源
最近更新 更多