为什么这里需要EXECUTE IMMEDIATE?

Why EXECUTE IMMEDIATE is needed here?

我是一个 SQL 服务器用户,我有一个使用 Oracle 的小项目,所以我想了解 Oracle 的一些特殊性,我认为我需要一些帮助才能更好地理解以下情况:

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

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');

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

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

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'')';

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

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

您的 if/else 直到 运行 时间才会被评估。编译器不知道 table 在您插入时将始终存在,它只能在解析整个块时检查它是否存在。

如果 table 已经存在,那没关系;编译器可以看到它,块执行,你的 select 得到 1,然后你进入 else 进行插入。但是,如果它不存在,那么在编译时 时,插入的正确解析将失败并显示 ORA-00942 ,并且块中的任何内容都不会执行。

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

全局 temporary tables 无论如何都不应该即时创建。它们是具有临时数据的永久对象,特定于每个会话,不应 created/dropped 作为应用程序代码的一部分。 (您的应用程序通常不应更改架构;它们应限于 upgrade/maintenance 更改并受到控制,以避免错误、数据丢失和意外副作用;GTT 也不例外)。

Unlike temporary tables in some other relational databases, when you create a temporary table in an Oracle database, you create a static table definition. The temporary table is a persistent object described in the data dictionary, but appears empty until your session inserts data into the table. You create a temporary table for the database itself, not for every PL/SQL stored procedure.

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

PL/SQL: ORA-00942: table or view does not exist

这是编译时错误,即在创建 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 错误。

底线,

At compile time it is not known if the table exists, as it is only created at run time.

在您的情况下,要避免这种行为,您需要使 INSERT 也是动态的并使用 EXECUTE IMMEDIATE。通过这种方式,您可以逃避 编译时错误 并获得动态创建的 table 并执行 在 运行 时间 .

动态插入

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

我已经稍微修改了您的代码,就逻辑而言,它可以正常工作。但是正如之前的帖子中所解释的那样,在 运行 时间动态创建 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


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