Область действия переменных ORACLE PL/SQL — требуется объяснение

Я только что споткнулся об ответ на проблему, с которой я столкнулся, когда переменная PL/SQL не распознавалась функцией, и я надеялся, что кто-нибудь сможет объяснить мне, почему мое решение работает и что происходит «под капотом».

Фон

В рамках проекта оптимизации я пытаюсь собрать показатели для отдельных сценариев SQL в рамках хранимой процедуры. Хранимая процедура, которую я анализирую, имеет параметр даты In-type, который мне нужно определить для запуска каждого отдельного сценария SQL:

CREATE OR REPLACE myStoredProc (DATE_IN DATE, ERROR_OUT OUT VARCHAR2)
IS
BEGIN
    --Truncate Temp Tables
    --6 Individual SQL Scripts
EXCEPTION
    --Error Handling
END;

Чтобы запускать каждый сценарий по отдельности, я решил просто поместить каждое выражение SQL в блок PL/SQL и передать параметр DATE_IN в качестве переменной:

DECLARE
    DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR'); 
BEGIN
    --Place individual script here
END;

Проблема

Этот подход отлично работал для пары запросов, которые ссылались на эту переменную DATE_IN, но один запрос со ссылкой на внешнюю функцию, которая принимает DATE_IN в качестве параметра, начал выдавать ошибку ORA-00904:

DECLARE  
    DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR'); 
BEGIN
    insert into temp_table
    SELECT table1.field1,
           table1.field2,
           table2.fieldA,
           MyFunction(table1.field1, DATE_IN) --This was the problem line
      FROM
           table1,
           table2
     WHERE EXISTS (inner query)
       AND table1.keys = table2.keys
       AND table2.date <= DATE_IN
END;   

Решение

По совету другого разработчика я смог обойти эту ошибку, добавив двоеточие (:) перед переменной DATE_IN, которую я передал в функцию, чтобы строка проблемы читалась как MyFunction(table1.field1, :DATE_IN). Как только я это сделал, моя ошибка исчезла, и я смог выполнить запрос без проблем.

Я был доволен результатом, но другой разработчик не смог объяснить, зачем это нужно, только то, что необходимо было вызывать какие-либо функции или другие хранимые процессы из оператора PL/SQL. Я предполагаю, что это как-то связано с областью действия, но я хотел бы получить лучшее объяснение того, почему это двоеточие было необходимо для того, чтобы функция могла видеть переменную.

Вопросы

Я попытался провести небольшое исследование, просматривая документацию Oracle по параметрам, переменные, привязка/объявление и константы, но в моем исследовании есть только дал мне больше вопросов:

  • Прочитав о переменных, я теперь задаюсь вопросом, является ли это правильным термином для того, что я использовал (поскольку я на самом деле не использовал команду VARIABLE и я передаю дату, которая не является допустимым типом данных). Если мой оператор DATE_IN DATE := не является переменной, то что это?
  • Почему остальные мои ссылки на DATE_IN были распознаны компилятором, но передача значения функции вышла за рамки?
  • Что именно здесь делает двоеточие (:)? Это превращает это в переменную связывания?

Заранее спасибо. Я ценю любое руководство, которое вы можете предоставить!

------------------------------------------------- РЕДАКТИРОВАТЬ------------ --------------------------

Меня попросили предоставить дополнительную информацию. Моя версия БД 11G, 11.2.0.2.0. Запрос, который мне удалось воспроизвести эту ошибку, приведен ниже.

DECLARE  
    EXTRACT_DT_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR'); 
BEGIN
    --This begins the pre-optimized query that I'm testing
    insert into AELI_COV_TMP_2_OPT
    SELECT /*+ ordered use_nl(CM MAMT) INDEX (CM CSMB_CSMB2_UK) INDEX (MAMT     (MBAM_CSMB_FK_I) */
           CM.CASE_MBR_KEY
          ,CM.pyrl_no
          ,MAMT.AMT
          ,MAMT.FREQ_CD
          ,MAMT.HOURS
          ,aeli$cov_pdtodt(CM.CASE_MBR_KEY, EXTRACT_DT_IN)
      FROM
           CASE_MEMBERS CM
          ,MEMBER_AMOUNTS MAMT
      WHERE EXISTS (select /*+ INDEX(SDEF SLRY_BCAT_FK_I) */
                      'x'
                     from SALARY_DEF SDEF
                    where SDEF.CASE_KEY = CM.CASE_KEY
                      AND SDEF.TYP_CD = '04'
                      AND SDEF.SLRY_KEY = MAMT.SLRY_KEY)
       AND CM.CASE_MBR_KEY = MAMT.CASE_MBR_KEY
       AND MAMT.STAT_CD = '00'
       AND (MAMT.xpir_dt is null or MAMT.xpir_dt > EXTRACT_DT_IN)
       AND MAMT.eff_dt <= EXTRACT_DT_IN;
    --This ends the pre-optimized query that I'm testing
END;   

Вот ошибка, с которой я сталкиваюсь при попытке запустить план объяснения для этого оператора. Я могу обойти эту ошибку, если удалю ссылку на строку 13 или добавлю двоеточие (:) к EXTRACT_DT_IN в этой строке.

Произошла ошибка в плане объяснения

----------------------РЕДАКТИРОВАТЬ 2-----

Вот сигнатура функции aeli$.cov_pdtodt. (Я сменил владельца из соображений безопасности).

CREATE OR REPLACE function __owner__.aeli$cov_pdtodt
(CASE_MBR_KEY_IN IN NUMBER, EXTRACT_EFF_DT_IN DATE)
  RETURN DATE IS
    PDTODT  DATE;

person DanK    schedule 18.07.2014    source источник
comment
Предполагая, что вы вставляете точку с запятой в конце, то, что у вас есть, должно работать. Ссылка на :date_in должна привести к ошибке (или запросу), если вы еще не объявили ее, например. с variable. Кажется, работает нормально в 11gR2; какую версию вы используете и какой клиент (и версию)? Не уверен, почему вы думаете, что это проблема области действия, поскольку у вас нет подблоков или повторяющихся имен.   -  person Alex Poole    schedule 18.07.2014
comment
Боюсь, минимальный пример в OP не демонстрирует проблему. См. раздел Как создать минимальный, полный и проверяемый пример.   -  person user272735    schedule 18.07.2014
comment
Я не могу воссоздать это поведение в версии 10.2.0.5 (SQL*Plus), 11.2.0.2 (SQL*Plus). Fiddle) или 11.2.0.3 (SQL Developer). То, что вы говорите, на самом деле не имеет смысла, и если вы не можете создать воспроизводимый пример, не совсем понятно, что мы пытаемся объяснить.   -  person Alex Poole    schedule 18.07.2014
comment
Спасибо вам обоим за ответ - я опубликую фактический запрос, который я получил, чтобы увидеть, лучше ли это показывает ошибку. Хотя без Stored Proc или Function я не знаю, сможете ли вы воссоздать проблему.   -  person DanK    schedule 18.07.2014
comment
@DanK - я не вижу, чтобы ваш реальный запрос значительно отличался от демо-запроса или от сработавшего Fiddle. Я не думаю, что это актуально, но какова подпись функции, которую вы вызываете?   -  person Alex Poole    schedule 18.07.2014
comment
Простое объяснение только этого оператора вставки SQL получит ORA-00904; вы не можете запустить план объяснения всего анонимного блока. Ваш вопрос предполагает, что вы получаете ошибку при выполнении анонимного блока, разве это не так - вы запускаете только часть вставки (или выбора) в автономном режиме, либо как команду, либо как объяснение плана?   -  person Alex Poole    schedule 18.07.2014
comment
Похоже, ошибка возникает из-за попытки выполнить план объяснения для запроса (а не из-за попытки выполнить весь анонимный блок). Поскольку план объяснения предназначен только для оператора вставки, он не имеет доступа к объявлению переменной, поэтому вы получаете ошибку. Когда вы ставите двоеточие перед именем переменной, вы превращаете его в переменную связывания, поэтому это работает.   -  person Craig    schedule 18.07.2014


Ответы (2)


Ваш анонимный блок в порядке, пока вы выполняете весь блок. Если вы попытаетесь выполнить только insert или ее select как отдельную команду, то она действительно завершится ошибкой с ORA-00904.

Это не совсем проблема области, это проблема контекста. Вы пытаетесь сослаться на переменную PL/SQL в контексте SQL, и это никогда не сработает.

В контексте PL/SQL это будет работать:

declare
  some_var dual.dummy%type := 'X';
begin
  insert into some_table
  select dummy from dual where dummy = some_var;
end;
/

... потому что вставка имеет доступ к PL/SQL some_var.

В контексте SQL это приведет к ошибке:

select * from dual where dummy = some_var;

... потому что он ищет столбец с именем SOME_VAR, а его нет.

Если вы сделаете это вместо этого:

select * from dual where dummy = :some_var;

... some_var теперь является переменной связывания, управляемой клиентом. Если вы выполните это, вам либо будет предложено ввести значение привязки, либо будет выдана ошибка, связанная не со всеми переменными, либо не объявленная переменная привязки, или что-то подобное, в зависимости от вашего клиента.

Если вы только объясните план этого, например. с участием

set auto trace traceonly explain
select * from dual where dummy = :some_var;

... тогда переменная связывания не обязательно должна быть заполнена для расчета плана. Некоторые клиенты могут по-прежнему жаловаться и требовать значения привязки, но синтаксический анализатор с этим справится — в любом случае этого достаточно для составления плана. Хотя он не может использовать преимущества просмотра переменных связывания или гистограмм и т. д.

Например, SQL Developer с радостью создаст план для вашего исходного примера запроса, если обе ссылки будут преобразованы в переменные связывания, будет выбрана только insert ... часть блока и вы нажмете «Объяснить план» (F10).

person Alex Poole    schedule 18.07.2014
comment
Спасибо за объяснение, Алекс. Я использую TOAD и, основываясь на ваших комментариях, думаю, мне нужно больше узнать о том, как план объяснения работает с этими блоками PL/SQL. Как вы упомянули, когда я запускаю приведенный выше оператор SQL сам по себе, я получаю эту ошибку ORA-00904. Однако, когда я оборачиваю его в объявление PL/SQL и определяю переменную, я могу запустить план объяснения только для инструкции SQL. Но когда я запускаю его по всему блоку, он выдает ошибку ORA-00905. - person DanK; 18.07.2014
comment
Только что разговаривал с другим разработчиком, и похоже, что та же самая ситуация, описанная Алексом выше, происходит, когда я оборачиваю свой SQL в блок PL/SQL. Несмотря на то, что я запускаю план объяснения только для одного оператора SQL, механизм знает, что нужно выйти и увидеть, что где-то существует переменная, и заполнить пробел, чтобы он мог запустить план объяснения. Спасибо всем за помощь! - person DanK; 18.07.2014

Я не уверен, что вы читали, но вы тут кое-что перепутали.

Ваша DATE_IN является переменной. Вам не нужно нигде вводить 'VARIABLE', чтобы объявить переменную, все, что вам нужно, это имя переменной и тип данных. Все перечисленные ниже являются допустимыми переменными в PL/SQL (хотя и плохо названы).

variable_1 NUMBER;
variable_2 VARCHAR2(100);
variable_3 DATE;

Трудно сказать, что вы делаете в своем коде, не видя всего этого. У вас есть две переменные DATE_IN, объявленные в одном блоке? Является ли DATE_IN именем столбца в вашей таблице?

Если у вас есть столбец с именем DATE_IN в таблице 1 или таблице 2, скорее всего, это ваша проблема. Oracle не знает, хотите ли вы использовать свою переменную или столбец, и по умолчанию всегда будет использовать имя столбца. Ваша функция будет ожидать DATE и получать столбец, следовательно, ошибка.

person Major Major    schedule 18.07.2014
comment
Вот где я путаюсь в переменных. Кажется, что они ведут себя по-разному при использовании этой команды VAR или VARIABLE: >docs.oracle.com/cd/B19306_01/server.102/b14357/ch12050.htm - person DanK; 18.07.2014
comment
@DanK - variable - это команда SQL*Plus (или SQL Developer), которая создает переменную связывания, а не команду SQL или PL/SQL. Совершенно не связан с разделом declare PL/SQL или переменными PL/SQL. - person Alex Poole; 18.07.2014
comment
Ах... теперь это имеет больше смысла. Я видел ссылки на связывание переменных в прошлом, но объяснение никогда раньше не имело смысла. Однако в качестве команды SQL Plus я вижу, как они работают. Спасибо! - person DanK; 18.07.2014