【问题标题】:Oracle: If Table ExistsOracle:如果表存在
【发布时间】:2010-12-20 10:12:41
【问题描述】:

我正在为 Oracle 数据库编写一些迁移脚本,并希望 Oracle 有类似于 MySQL 的 IF EXISTS 构造的东西。

具体来说,每当我想在 MySQL 中删除一个表时,我都会执行类似的操作

DROP TABLE IF EXISTS `table_name`;

这样,如果表不存在,DROP 不会产生错误,脚本可以继续。

Oracle 是否有类似的机制?我意识到我可以使用以下查询来检查表是否存在

SELECT * FROM dba_tables where table_name = 'table_name';

但是将其与DROP 绑定在一起的语法让我无法理解。

【问题讨论】:

    标签: sql oracle sql-drop


    【解决方案1】:

    最好和最有效的方法是捕获“table not found”异常:这样可以避免检查表是否存在两次的开销;并且不会遇到这样的问题,即如果 DROP 由于其他原因(可能很重要)失败,仍然会向调用者提出异常:

    BEGIN
       EXECUTE IMMEDIATE 'DROP TABLE ' || table_name;
    EXCEPTION
       WHEN OTHERS THEN
          IF SQLCODE != -942 THEN
             RAISE;
          END IF;
    END;
    

    附录 作为参考,以下是其他对象类型的等效块:

    顺序

    BEGIN
      EXECUTE IMMEDIATE 'DROP SEQUENCE ' || sequence_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -2289 THEN
          RAISE;
        END IF;
    END;
    

    查看

    BEGIN
      EXECUTE IMMEDIATE 'DROP VIEW ' || view_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -942 THEN
          RAISE;
        END IF;
    END;
    

    触发器

    BEGIN
      EXECUTE IMMEDIATE 'DROP TRIGGER ' || trigger_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -4080 THEN
          RAISE;
        END IF;
    END;
    

    索引

    BEGIN
      EXECUTE IMMEDIATE 'DROP INDEX ' || index_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -1418 THEN
          RAISE;
        END IF;
    END;
    

    BEGIN
      EXECUTE IMMEDIATE 'ALTER TABLE ' || table_name
                    || ' DROP COLUMN ' || column_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -904 AND SQLCODE != -942 THEN
          RAISE;
        END IF;
    END;
    

    数据库链接

    BEGIN
      EXECUTE IMMEDIATE 'DROP DATABASE LINK ' || dblink_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -2024 THEN
          RAISE;
        END IF;
    END;
    

    物化视图

    BEGIN
      EXECUTE IMMEDIATE 'DROP MATERIALIZED VIEW ' || mview_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -12003 THEN
          RAISE;
        END IF;
    END;
    

    类型

    BEGIN
      EXECUTE IMMEDIATE 'DROP TYPE ' || type_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -4043 THEN
          RAISE;
        END IF;
    END;
    

    约束

    BEGIN
      EXECUTE IMMEDIATE 'ALTER TABLE ' || table_name
                || ' DROP CONSTRAINT ' || constraint_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -2443 AND SQLCODE != -942 THEN
          RAISE;
        END IF;
    END;
    

    调度作业

    BEGIN
      DBMS_SCHEDULER.drop_job(job_name);
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -27475 THEN
          RAISE;
        END IF;
    END;
    

    用户/架构

    BEGIN
      EXECUTE IMMEDIATE 'DROP USER ' || user_name;
      /* you may or may not want to add CASCADE */
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -1918 THEN
          RAISE;
        END IF;
    END;
    

    BEGIN
      EXECUTE IMMEDIATE 'DROP PACKAGE ' || package_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -4043 THEN
          RAISE;
        END IF;
    END;
    

    程序

    BEGIN
      EXECUTE IMMEDIATE 'DROP PROCEDURE ' || procedure_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -4043 THEN
          RAISE;
        END IF;
    END;
    

    功能

    BEGIN
      EXECUTE IMMEDIATE 'DROP FUNCTION ' || function_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -4043 THEN
          RAISE;
        END IF;
    END;
    

    表空间

    BEGIN
      EXECUTE IMMEDIATE 'DROP TABLESPACE ' || tablespace_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -959 THEN
          RAISE;
        END IF;
    END;
    

    同义词

    BEGIN
      EXECUTE IMMEDIATE 'DROP SYNONYM ' || synonym_name;
    EXCEPTION
      WHEN OTHERS THEN
        IF SQLCODE != -1434 THEN
          RAISE;
        END IF;
    END;
    

    【讨论】:

    • 对于删除用户,要忽略的 SQLCODE 是 -1918。
    • 需要写一个程序来做什么?难道没有更好的方法吗?
    • 如果我添加许多 EXECUTE IMMEDIATE 'DROP TABLE mytable'; 句子(脚本中的每个表一个),我是否必须为每个句子放置一个异常处理程序,或者将所有句子包装在一个 @ 中就足够了987654339@块?
    • @jpmc26:MS SQL 的等价物是IF OBJECT_ID('TblName') IS NOT NULL DROP TABLE TblName。似乎 SQL 语言的冗长程度与价格成正比。
    • @JeffreyKemp 你不会这么认为,但我一次又一次地发现甲骨文让一切变得困难。当您平均为每个晦涩的语法错误花费一个小时,或者试图弄清楚如何在另一个数据库中做一些显而易见且容易的事情(例如有条件地删除一个元素)并且每天都会出现这类问题时,它就会加起来。快。
    【解决方案2】:
    declare
       c int;
    begin
       select count(*) into c from user_tables where table_name = upper('table_name');
       if c = 1 then
          execute immediate 'drop table table_name';
       end if;
    end;
    

    用于检查当前模式中的表是否存在。 要检查给定表是否已经存在于不同的架构中,您必须使用 all_tables 而不是 user_tables 并添加条件 all_tables.owner = upper('schema_name')

    【讨论】:

    • +1 这更好,因为不要依赖异常解码来了解要做什么。代码将更易于维护和理解
    • 同意@daitangio - 性能通常不会胜过一次性部署脚本的可维护性。
    • 我很想知道隐式提交是否在这里起作用。您可能希望 SELECT 和 DROP 在同一个事务中。 [ 显然忽略了可能执行的任何后续 DDL。 ]
    • @Matthew,DROP 是一个 DDL 命令,所以它会首先发出 COMMIT,删除表,然后发出第二次 COMMIT。当然,在这个例子中没有事务(因为它只是发出一个查询)所以它没有区别;但是如果用户之前已经发布了一些 DML,它将在执行任何 DDL 之前隐式提交。
    【解决方案3】:

    我一直在寻找相同的方法,但最终我编写了一个程序来帮助我:

    CREATE OR REPLACE PROCEDURE DelObject(ObjName varchar2,ObjType varchar2)
    IS
     v_counter number := 0;   
    begin    
      if ObjType = 'TABLE' then
        select count(*) into v_counter from user_tables where table_name = upper(ObjName);
        if v_counter > 0 then          
          execute immediate 'drop table ' || ObjName || ' cascade constraints';        
        end if;   
      end if;
      if ObjType = 'PROCEDURE' then
        select count(*) into v_counter from User_Objects where object_type = 'PROCEDURE' and OBJECT_NAME = upper(ObjName);
          if v_counter > 0 then          
            execute immediate 'DROP PROCEDURE ' || ObjName;        
          end if; 
      end if;
      if ObjType = 'FUNCTION' then
        select count(*) into v_counter from User_Objects where object_type = 'FUNCTION' and OBJECT_NAME = upper(ObjName);
          if v_counter > 0 then          
            execute immediate 'DROP FUNCTION ' || ObjName;        
          end if; 
      end if;
      if ObjType = 'TRIGGER' then
        select count(*) into v_counter from User_Triggers where TRIGGER_NAME = upper(ObjName);
          if v_counter > 0 then          
            execute immediate 'DROP TRIGGER ' || ObjName;
          end if; 
      end if;
      if ObjType = 'VIEW' then
        select count(*) into v_counter from User_Views where VIEW_NAME = upper(ObjName);
          if v_counter > 0 then          
            execute immediate 'DROP VIEW ' || ObjName;        
          end if; 
      end if;
      if ObjType = 'SEQUENCE' then
        select count(*) into v_counter from user_sequences where sequence_name = upper(ObjName);
          if v_counter > 0 then          
            execute immediate 'DROP SEQUENCE ' || ObjName;        
          end if; 
      end if;
    end;
    

    希望对你有帮助

    【讨论】:

    • 在我创建了上面的 proc 之后。 delobject,我试图调用它发出以下 SQL。但它没有用。 delobject('MyTable', 'TABLE');我收到以下错误-------------------------------- 从命令的第 1 行开始的错误:delobject('MyTable ', 'TABLE') 错误报告:未知命令
    • 使用 EXECUTE 命令 - EXECUTE DelObject ('MyTable','TABLE');
    【解决方案4】:

    只是想发布一个完整的代码,该代码将使用 Jeffrey 的代码创建一个表并删除它(向他致敬,而不是我!)。

    BEGIN
        BEGIN
             EXECUTE IMMEDIATE 'DROP TABLE tablename';
        EXCEPTION
             WHEN OTHERS THEN
                    IF SQLCODE != -942 THEN
                         RAISE;
                    END IF;
        END;
    
        EXECUTE IMMEDIATE 'CREATE TABLE tablename AS SELECT * FROM sourcetable WHERE 1=0';
    
    END;
    

    【讨论】:

    • 就我个人而言,我会将 CREATE TABLE 放在一个单独的步骤中,因为它不需要动态完成,也不需要异常处理程序。
    【解决方案5】:

    使用 SQL*PLUS,您还可以使用 WHENEVER SQLERROR 命令:

    WHENEVER SQLERROR CONTINUE NONE
    DROP TABLE TABLE_NAME;
    
    WHENEVER SQLERROR EXIT SQL.SQLCODE
    DROP TABLE TABLE_NAME;
    

    CONTINUE NONE 会报告错误,但脚本会继续执行。使用EXIT SQL.SQLCODE,脚本将在出现错误时终止。

    另请参阅:WHENEVER SQLERROR Docs

    【讨论】:

      【解决方案6】:

      我更喜欢以下经济解决方案

      BEGIN
          FOR i IN (SELECT NULL FROM USER_OBJECTS WHERE OBJECT_TYPE = 'TABLE' AND OBJECT_NAME = 'TABLE_NAME') LOOP
                  EXECUTE IMMEDIATE 'DROP TABLE TABLE_NAME';
          END LOOP;
      END;
      

      【讨论】:

        【解决方案7】:

        另一种方法是定义一个异常,然后只捕获该异常,让所有其他异常传播。

        Declare
           eTableDoesNotExist Exception;
           PRAGMA EXCEPTION_INIT(eTableDoesNotExist, -942);
        Begin
           EXECUTE IMMEDIATE ('DROP TABLE myschema.mytable');
        Exception
           When eTableDoesNotExist Then
              DBMS_Output.Put_Line('Table already does not exist.');
        End;
        

        【讨论】:

        • @Sk8erPeter “已经不存在” vs. “确实存在,但不再存在” :)
        【解决方案8】:

        一种方法是使用DBMS_ASSERT.SQL_OBJECT_NAME :

        此函数验证输入参数字符串是否为现有 SQL 对象的限定 SQL 标识符。

        DECLARE
            V_OBJECT_NAME VARCHAR2(30);
        BEGIN
           BEGIN
                V_OBJECT_NAME  := DBMS_ASSERT.SQL_OBJECT_NAME('tab1');
                EXECUTE IMMEDIATE 'DROP TABLE tab1';
        
                EXCEPTION WHEN OTHERS THEN NULL;
           END;
        END;
        /
        

        DBFiddle Demo

        【讨论】:

        • 但它可能不是表名。
        • 在不同的架构中也可能有各种表使用该名称。
        【解决方案9】:

        oracle 中没有“DROP TABLE IF EXISTS”,您必须执行 select 语句。

        试试这个(我不了解 oracle 语法,所以如果我的变量是 ify,请原谅我):

        declare @count int
        select @count=count(*) from all_tables where table_name='Table_name';
        if @count>0
        BEGIN
            DROP TABLE tableName;
        END
        

        【讨论】:

        • 我已尝试将脚本翻译成 oracle 语法。
        • 声明计数; begin select count(*) into count from all_tables where table_name = 'x';如果 count > 0 则立即执行 'drop table x';万一;结尾;您不能直接从事务块运行 DDL,您需要使用执行。
        • 非常感谢!我没有意识到语法是如此不同。我确实知道您需要将整个内容包装在开始/结束中,但我认为它正在另一个脚本的中间运行。汤姆:我决定留下我的版本而不是复制你的版本,所以我不接受你的任何投票,显然你的答案是正确的。
        • 我认为这不会编译。在此处包含架构所有者也可能很重要,否则您可能会为您不打算使用相同名称的表获得“真”。
        • 你的答案was superseded 在此发布 10 分钟后使用正确的 Oracle 语法。
        【解决方案10】:

        如果您想让它可重新输入并最小化删除/创建周期,您可以使用 dbms_metadata.get_ddl 缓存 DDL 并使用如下构造重新创建所有内容: declare v_ddl varchar2(4000); begin select dbms_metadata.get_ddl('TABLE','DEPT','SCOTT') into v_ddl from dual; [COMPARE CACHED DDL AND EXECUTE IF NO MATCH] exception when others then if sqlcode = -31603 then [GET AND EXECUTE CACHED DDL] else raise; end if; end; 这只是一个示例,内部应该有一个循环,其中 DDL 类型、名称和所有者是变量。

        【讨论】:

          【解决方案11】:

          这样的块可能对你有用。

          DECLARE
              table_exist INT;
          
          BEGIN
              SELECT Count(*)
              INTO   table_exist
              FROM   dba_tables
              WHERE  owner = 'SCHEMA_NAME' 
              AND table_name = 'EMPLOYEE_TABLE';
          
              IF table_exist = 1 THEN
                EXECUTE IMMEDIATE 'drop table EMPLOYEE_TABLE';
              END IF;
          END;  
          

          【讨论】:

            【解决方案12】:

            很遗憾,没有这样的东西,如果存在则丢弃,或者如果不存在则创建

            您可以编写一个 plsql 脚本来包含那里的逻辑。

            http://download.oracle.com/docs/cd/B12037_01/server.101/b10759/statements_9003.htm

            我不太了解 Oracle 语法,但我认为 @Erich 的脚本会是这样的。

            declare 
            cant integer
            begin
            select into cant count(*) from dba_tables where table_name='Table_name';
            if count>0 then
            BEGIN
                DROP TABLE tableName;
            END IF;
            END;
            

            【讨论】:

            • 声明不能整数;表名 varchar2(100) := 'BLABLABL'; begin select count(*) into cant from dba_tables where lower(table_name) = tablename;如果 cant > 0 则立即执行 'DROP TABLE tablename';万一;结束;
            【解决方案13】:

            您总是可以自己发现错误。

            begin
            execute immediate 'drop table mytable';
            exception when others then null;
            end;
            

            过度使用它被认为是不好的做法,类似于其他语言中的空 catch()。

            问候

            【讨论】:

            • 不,永远不会“当其他人为空时例外”
            【解决方案14】:

            我更喜欢指定表和架构所有者。

            还要注意区分大小写。 (参见下面的“上”子句)。

            我扔了几个不同的对象,以表明它们可以在除 TABLEs 之外的地方使用。

            ......

            declare
               v_counter int;
            begin
             select count(*) into v_counter from dba_users where upper(username)=upper('UserSchema01');
               if v_counter > 0 then
                  execute immediate 'DROP USER UserSchema01 CASCADE';
               end if; 
            end;
            /
            
            
            
            CREATE USER UserSchema01 IDENTIFIED BY pa$$word
              DEFAULT TABLESPACE users
              TEMPORARY TABLESPACE temp
              QUOTA UNLIMITED ON users;
            
            grant create session to UserSchema01;  
            

            还有一个 TABLE 示例:

            declare
               v_counter int;
            begin
             select count(*) into v_counter from all_tables where upper(TABLE_NAME)=upper('ORDERS') and upper(OWNER)=upper('UserSchema01');
               if v_counter > 0 then
                  execute immediate 'DROP TABLE UserSchema01.ORDERS';
               end if; 
            end;
            /   
            

            【讨论】:

              【解决方案15】:
              BEGIN
                 EXECUTE IMMEDIATE 'DROP TABLE "IMS"."MAX" ';
              EXCEPTION
                 WHEN OTHERS THEN
                    IF SQLCODE != -942 THEN
                       RAISE;
                        END IF;
                       EXECUTE IMMEDIATE ' 
                CREATE TABLE "IMS"."MAX" 
                 (    "ID" NUMBER NOT NULL ENABLE, 
                  "NAME" VARCHAR2(20 BYTE), 
                   CONSTRAINT "MAX_PK" PRIMARY KEY ("ID")
                USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 
                STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
                PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
                TABLESPACE "SYSAUX"  ENABLE
                 ) SEGMENT CREATION IMMEDIATE 
                PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
                STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
                PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
                TABLESPACE "SYSAUX"  ';
              
              
              END;
              

              // 执行此代码,检查表是否存在,然后创建表 max.这仅适用于单个编译

              【讨论】:

              • 我相信这只会在抛出错误时创建表。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-14
              • 1970-01-01
              • 2020-10-20
              • 1970-01-01
              • 2020-09-26
              • 2011-03-10
              相关资源
              最近更新 更多