Требования Oracle к детерминизму и особенности - PullRequest
0 голосов
/ 01 июля 2018

Меня беспокоит отсутствие понимания периодически возникающей проблемы: функция-детерминированность.

Из документов кажется довольно ясным:

ДЕТЕРМИНИСТИЧЕСКАЯ функция может не иметь побочных эффектов.

ДЕТЕРМИНИСТИЧЕСКАЯ функция не может вызывать необработанное исключение.

Поскольку это важные основные концепции с надежными, централизованными реализациями в стандартных пакетах, я не думаю, что есть ошибка или что-то в этом роде (ошибка заключается в моих предположениях и понимании, а не в Oracle). При этом, как представляется, оба эти требования иногда имеют свои особенности в стандартном пакете и пакетах DBMS_ и UTL_.

Я надеялся опубликовать пару примеров функций Oracle, которые вызывают у меня некоторые сомнения в моем использовании DETERMINISTIC и нюансах этих ограничений, и посмотреть, сможет ли кто-нибудь объяснить, как все сочетается друг с другом. Я извиняюсь, что это вопрос «почему», и его можно перенести, если это необходимо, но ответ на этот вопрос: ( Можно ли задать вопрос, когда вы нашли решение, но не знаете, почему что-то ведет себя так, как было? ) заставило меня подумать, что это может быть уместно для 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, должна обрабатывать эту возможность, чтобы оставаться чистой. Но выбрасывание исключения здесь представляется уместным и надежным. Кажется, что есть некоторые споры о том, нарушают ли исключения прозрачность ссылок ( Почему возбуждение исключения является побочным эффектом? )

Вторая ситуация, с которой я сталкиваюсь, это системные функции, которые выглядят так, как будто они должны быть 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

Предположительно, это нарушает требования к определенности, но это трудно себе представить. Мне было интересно, что мне не хватает в моем предположении, что подобные функции будут детерминированными.

РЕДАКТИРОВАТЬ В ответ на комментарий Лукаша о настройках сессии:
Я могу принять это, если повторяемость между сессиями является основной причиной таких функций, как 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 голосов
/ 02 июля 2018

Казалось бы, 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_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 не вернет то же значение при тех же параметрах.

И, следовательно, ваше предположение о DBMS_ASSERT.NOOP не соответствует действительности во всех случаях.

...