【问题标题】:Why EXECUTE IMMEDIATE is needed here?为什么这里需要立即执行?
【发布时间】:2016-02-11 06:27:38
【问题描述】:

我是 SQL Server 用户,我有一个使用 Oracle 的小项目要做,所以我试图了解 Oracle 的一些特殊性,我认为我需要一些帮助来更好地了解以下情况:

我想在创建临时表之前测试它是否存在,所以我在这里有这段代码:

DECLARE
  table_count INTEGER;
  var_sql VARCHAR2(1000) := 'create GLOBAL TEMPORARY table TEST (
            hello varchar(1000) NOT NULL)';
BEGIN
  SELECT COUNT(*) INTO table_count FROM all_tables WHERE table_name = 'TEST';

  IF table_count = 0 THEN
    EXECUTE IMMEDIATE var_sql;
  END IF;
END;

它工作正常,所以在我执行一次之后,我在我的IF上添加了一个else语句:

ELSE
  insert into test (hello) values ('hi');

再次执行,我的测试表中添加了一行。

好的,我的代码已经准备好并且可以工作了,所以我删除了临时表并尝试再次运行整个语句,但是当我这样做时,我得到了以下错误:

ORA-06550: line 11, column 19:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 11, column 7:
PL/SQL: SQL Statement ignored
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:

然后我将我的 else 语句更改为这个,现在它又可以工作了:

ELSE
  EXECUTE IMMEDIATE 'insert into test (hello) values (''hi'')';

我的问题是为什么单独运行我可以简单地使用插入而不是 EXECUTE IMMEDIATE 以及为什么我的 SELECT 语句在 BEGIN 之后仍然有效,而其他所有似乎都需要 EXECUTE IMMEDIATE 才能正常运行?

【问题讨论】:

  • this question 和其他的可能重复;在执行表创建之前,在编译时解析静态 SQL。但是不要在运行中创建 GTT,它应该是一个永久对象......这是一个您不应该直接从 SQL Server 传输到 Oracle 的区域。另请参阅thisthisthis

标签: oracle plsql oracle12c execute-immediate


【解决方案1】:

我稍微修改了您的代码,就逻辑而言,它可以正常工作。但正如前面文章中所解释的那样,在运行时动态创建 GTT 根本不是一个好主意。

--- Firstly by dropping the table i.e NO TABLE EXISTS in the DB in AVROY 

SET serveroutput ON;
DECLARE
  table_count INTEGER;
  var_sql     VARCHAR2(1000) := 'create GLOBAL TEMPORARY table TEST (            
hello varchar(1000) NOT NULL)';
BEGIN

EXECUTE IMMEDIATE 'DROP TABLE AVROY.TEST'; --Added the line just to drop the table as per your comments

  SELECT COUNT(*)
  INTO table_count
  FROM all_tables
  WHERE table_name = 'TEST'
  AND OWNER        = 'AVROY';
  IF table_count   = 0 THEN
    EXECUTE IMMEDIATE var_sql;
    dbms_output.put_line('table created');
  ELSE
    INSERT INTO AVROY.test
      (hello
      ) VALUES
      ('hi'
      );
  END IF;
END;

--------------------OUTPUT-----------------------------------------------

anonymous block completed
table created

SELECT COUNT(*)
--  INTO table_count
  FROM all_tables
  WHERE table_name = 'TEST'
  AND OWNER        = 'AVROY';

COUNT(*)
------
1
--------

-- Second option is without DROPPING TABLE


SET serveroutput ON;
DECLARE
  table_count INTEGER;
  var_sql     VARCHAR2(1000) := 'create GLOBAL TEMPORARY table TEST (            
hello varchar(1000) NOT NULL)';
BEGIN

--EXECUTE IMMEDIATE 'DROP TABLE AVROY.TEST';

  SELECT COUNT(*)
  INTO table_count
  FROM all_tables
  WHERE table_name = 'TEST'
  AND OWNER        = 'AVROY';
  IF table_count   = 0 THEN
    EXECUTE IMMEDIATE var_sql;
    dbms_output.put_line('table created');
  ELSE
    INSERT INTO AVROY.test
      (hello
      ) VALUES
      ('hi'
      );
      dbms_output.put_line(SQL%ROWCOUNT||' Rows inserted into the table');
  END IF;
END;

-------------------------------OUTPUT-------------------------------------

anonymous block completed
1 Rows inserted into the table


---------------------------------------------------------------------------

【讨论】:

  • 如果块运行时表不存在,还是会报错。添加架构名称不会改变这一点。
  • @Alex:如果表不存在,那么逻辑将转到 GTT 创建 GTT 并在 FLY 上创建它。其次,如果存在,那么它将进入 INSERT 语句。添加所有者名称通常是一种很好的做法,因此在任何情况下都不会失败。
  • 如果表不存在则编译会失败,不会执行任何操作,也不会进行动态创建。这就是问题的重点,以及为什么当表不存在时 OP 会得到 ORA-00942。您的版本将收到相同的错误。 (尽管在 select 中指定 owner 是个好方法)。
  • 嗨,亚历克斯,我已经修改了编辑,它在所有情况下都有效,如解释的那样。请检查我指定的两个条件。
  • 您在表已经存在时运行了这两个。 首先删除表(静态地,在块外),然后尝试运行任一版本。您仍然会从他们俩的静态insert 中获得编译时 ORA-00942。如题所示。
【解决方案2】:

整个 PL/SQL 块在编译时被解析,但动态语句中的文本直到运行时才被评估。 (对于匿名块,它们接近相同,但步骤仍然不同)。

您的 if/else 也不会在运行时进行评估。编译器不知道在您插入时该表将始终存在,它只能在解析整个块时检查它是否存在。

如果表已经存在,那么没关系;编译器可以看到它,块执行,您的选择得到 1,然后您进入 else 进行插入。但如果它不存在,则插入的解析正确失败并出现 ORA-00942在编译时,并且块中的任何内容都不会被执行。

由于表的创建是动态的,因此对表的所有引用也必须是动态的 - 如您所见,您的插入也是如此,如果您随后查询它。基本上它使您的代码更难阅读并且可以隐藏语法错误 - 因为动态代码直到运行时才被解析,并且您可能会在未命中的分支中的动态语句中出错很久了。

无论如何,不​​应即时创建全局temporary tables。它们是具有临时数据的永久对象,特定于每个会话,不应作为应用程序代码的一部分创建/删除。 (一般情况下,您的应用程序不应更改架构;它们应仅限于升级/维护更改并受到控制,以避免错误、数据丢失和意外的副作用;GTT 也不例外)。

与其他一些关系数据库中的临时表不同,当您在 Oracle 数据库中创建临时表时,您会创建一个静态表定义。临时表是数据字典中描述的持久对象,但在会话将数据插入表之前显示为空。您为数据库本身创建一个临时表,而不是为每个 PL/SQL 存储过程。

创建一次 GTT 并使您的所有 PL/SQL 代码静态化。如果您想要更接近 SQL Server 本地临时表的内容,请查看 PL/SQL collections

【讨论】:

  • 非常感谢。我能够更好地理解编译器如何更好地解析查询。关于 GTT,我不打算以这种方式使用它们。但是由于我开始了解该语言,所以我想了解为什么此时我需要使用即时。只是出于好奇,我使用真实的表格进行了此操作,并且行为完全相同。不过感谢 GTT 的警告。
  • @RafaelMerlin - 是的,这种行为与它是 GTT 而不是“正常”表无关。在运行时创建任何模式对象通常会给您带来问题,应该避免。此外,select 有效,因为all_tables 视图在编译时确实存在。 (但您应该使用 user_tables,或指定所有者,正如 AvrajitRoy 建议的那样)。
【解决方案3】:

PL/SQL: ORA-00942: 表或视图不存在

这是编译时错误,即在创建 GTT 之前解析静态 SQL。

我们来看看编译时间运行时间错误的区别:

静态 SQL:

SQL> DECLARE
  2  v number;
  3  BEGIN
  4  select empno into v from a;
  5  end;
  6  /
select empno into v from a;
                         *
ERROR at line 4:
ORA-06550: line 4, column 26:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 4, column 1:
PL/SQL: SQL Statement ignored

动态 SQL:

SQL> DECLARE
  2  v number;
  3  BEGIN
  4  execute immediate 'select empno from a' into v;
  5  end;
  6  /
DECLARE
*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at line 4

在第一个 PL/SQL 块中,在编译时进行了语义检查,您可以看到 PL/SQL: ORA-00942: table or view does not exist。在第二个 PL/SQL 块中,您看不到 PL/SQL 错误。

底线,

在编译时不知道表是否存在,因为它是 仅在运行时创建。

在您的情况下,为避免这种行为,您需要使 INSERT 也是动态的并使用 EXECUTE IMMEDIATE。这样,您可以避免编译时错误并获得动态创建的表,还可以在运行时动态插入

话虽如此,基本问题是您试图动态创建 GTT,这不是一个好主意。您应该创建一次,然后按照您想要的方式使用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-03
    • 1970-01-01
    • 2021-08-27
    • 1970-01-01
    • 2010-12-15
    • 2019-01-16
    • 2021-10-22
    • 2012-09-08
    相关资源
    最近更新 更多