Oracle确定性要求和特质

时间:2018-07-01 18:11:14

标签: oracle plsql deterministic

我对周期性出现的问题缺乏理解感到困扰。

docs看来,很清楚:

  

确定功能可能没有副作用。

     

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

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

EDIT回应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

1 个答案:

答案 0 :(得分:1)

  

ORA-01722似乎违反了unhandled-exception   需求。大概是我创建的任何通过TO_NUMBER强制转换的函数   应该保持这种可能性以保持纯净。

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

  

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

它清楚地表明,它曾经强制转换TO_NUMBER,这意味着formatted numerical output of one application本身需要一个数字输入,并且在您进行如下编写时:

TO_NUMBER

您已将意外输入完全传递给INSERT INTO TO_NUMBER_IS_PURE_BUT_THROWS VALUES ('UH-OH',DEFAULT); 函数,因此它将错误TO_NUMBER作为预期行为抛出。

详细了解TO_NUMBER

第二,

  

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

ORA-01722: invalid number函数可用于有人通过变量传递实际代码段而又不希望对其进行SQL注入攻击检查的情况下使用。 这必须是不确定的,因为它只是返回我们输入到函数的内容。

我向您展示了一个示例,说明为什么必须使用DBMS_ASSERT.NOOP

假设我创建一个non-deterministic的函数years_from_today

deterministic

现在,我创建一个表并在查询中使用此功能,如下所示:

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的假设在所有情况下都不成立。