我对周期性出现的问题缺乏理解感到困扰。
从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
答案 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
的假设在所有情况下都不成立。