【问题标题】:ORACLE PL/SQL Variable Function Scope - Need ExplanationORACLE PL/SQL 变量函数作用域 - 需要说明
【发布时间】:2014-07-18 16:12:07
【问题描述】:

我刚刚解决了一个问题的答案,我遇到了一个函数无法识别 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 参数文档进行一些研究,variablesbinding/declaringconstants,但我的研究只给了我更多问题:

  • 阅读完变量后,我现在质疑这是否是我一直在使用的正确术语(因为我实际上没有使用 VARIABLE 命令并且我传递了一个日期 - 这不是一个允许的数据类型)。如果我的DATE_IN DATE := 语句不是变量,那它是什么?
  • 为什么编译器可以识别我对 DATE_IN 的其余引用,但将值传递给函数超出范围?
  • 冒号 (:) 到底是做什么的?这是把它变成一个绑定变量吗?

提前致谢。感谢您提供的任何指导!

----------------------编辑--------- -----------------------------

我被要求提供更多信息。我的 Db 版本是 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;

【问题讨论】:

  • 假设您插入的末尾有一个分号,那么您所拥有的应该可以工作。如果您尚未声明它,则引用 :date_in 应该会出错(或提示),例如与variable。似乎在 11gR2 中工作正常;您使用的是什么版本,以及哪个客户端(和版本)?不知道为什么您认为这是范围问题,因为您没有子块或重复名称。
  • 恐怕 OP 中的最小示例并不能说明问题。见How to create a Minimal, Complete, and Verifiable example
  • 我无法在 10.2.0.5 (SQL*Plus)、11.2.0.2 (SQL Fiddle) 或 11.2.0.3 (SQL Developer) 中重新创建此行为。你说的没有道理,除非你能创建一个可重现的例子,否则我们还不清楚我们要解释什么
  • 一个简单的解释就是那个 SQL 插入语句会得到 ORA-00904;你不能运行整个匿名块的解释计划。您的问题表明您在执行匿名块时遇到错误,不是这样吗 - 您只能让它独立运行插入(或选择)部分,无论是作为命令还是解释计划?
  • 看起来错误来自尝试对查询运行解释计划(而不是尝试执行整个匿名块)。由于解释计划仅适用于插入语句,因此它无法访问变量声明,这就是您收到错误的原因。当你把冒号放在变量名前面时,你就是把它变成了一个绑定变量,这就是它起作用的原因。

标签: sql oracle stored-procedures plsql stored-functions


【解决方案1】:

只要您执行整个块,您的匿名块就可以了。如果您尝试仅执行 insert 或其 select 作为独立命令,那么它确实会因 ORA-00904 而失败。

这不是一个 范围 问题,这是一个 上下文 问题。您试图在 SQL 上下文中引用 PL/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)。

【讨论】:

  • 感谢您的解释,亚历克斯。我正在使用 TOAD 并且基于您的 cmets,我想我需要更多地研究解释计划如何与这些 PL/SQL 块一起工作。正如您所提到的,当我自己运行上面的 SQL 语句时,我得到了 ORA-00904 错误。但是,当我将它包装在 PL/SQL 声明中并定义变量时,我能够仅在 SQL 语句上运行解释计划。但是当我在整个块中运行它时,它会给我一个 ORA-00905 错误。
  • 刚刚与另一位开发人员交谈过,当我将我的 SQL 包装在 PL/SQL 块中时,这听起来与 Alex 上面描述的情况相同。尽管我只在单个 SQL 语句上运行解释计划,但引擎知道要出去看看某个变量存在于某个地方,它会“填充空白”,以便它可以运行解释计划。感谢大家的帮助!
【解决方案2】:

我不确定你读到了什么,但你在这里搞混了一些事情。

您的 DATE_IN 是一个变量。你不需要在任何地方输入“VARIABLE”来声明一个变量,你只需要变量的名称和数据类型。 以下所有变量都是 PL/SQL 中的合法变量(尽管命名不佳)。

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

如果不查看全部内容,很难说出您在代码中所做的事情。您是否在同一个块中声明了两个 DATE_IN 变量? DATE_IN 是表中列的名称吗?

如果您在 table1 或 table2 中有一个名为 DATE_IN 的列,那可能是您的问题。 Oracle 不知道您是要使用变量还是列,它始终默认为列名。您的函数需要一个 DATE 并接收一个列,因此会出现错误。

【讨论】:

  • 这是我在变量上搞混的地方。在使用 VAR 或 VARIABLE 命令时,它们似乎采取了不同的行为:docs.oracle.com/cd/B19306_01/server.102/b14357/ch12050.htm
  • @DanK - variable 是创建绑定变量的 SQL*Plus(或 SQL Developer)命令,而不是 SQL 或 PL/SQL 命令。与 PL/SQL declare 部分或 PL/SQL 变量完全无关。
  • 啊..现在更有意义了。我在过去看到过对绑定变量的引用,但以前的解释从来没有意义。但是,作为 SQL Plus 命令,我了解它们是如何工作的。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-08
  • 1970-01-01
  • 2016-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多