Oracle:如果表存在


343

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

具体来说,每当我想在MySQL中删除表时,我都会做类似的事情

DROP TABLE IF EXISTS `table_name`;

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

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

SELECT * FROM dba_tables where table_name = 'table_name';

但是将其与a捆绑在一起的语法使DROP我逃脱了。

Answers:


585

最好和最有效的方法是捕获“表未找到”异常:这样可以避免检查表是否存在两次的开销。并且不会遇到以下问题:如果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;

13
对于删除USER,要忽略的SQLCODE是-1918。
Andrew Swan

14
需要编写一个程序来做到这一点吗?难道没有更好的方法吗?
威尔逊·弗雷塔斯

8
如果添加许多EXECUTE IMMEDIATE 'DROP TABLE mytable';句子(脚本中的每个表一个),是否必须为每个句子放置一个异常处理程序,还是足以将所有语句包装在一个BEGIN ... EXCEPTION ... END;块中?
2013年

8
@ jpmc26:MS SQL的等效项是IF OBJECT_ID('TblName') IS NOT NULL DROP TABLE TblName。SQL语言的冗长程度似乎与价格成正比。

6
@JeffreyKemp您不会这么认为,但是我一次又一次发现Oracle使一切变得艰难。当您平均每个晦涩的语法错误花费一个小时,或者试图弄清楚如何在另一个数据库中做显而易见的事情(例如有条件地删除一个元素),并且每天都会弹出此类问题时,它就会加起来。快速。
jpmc26 2014年

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


34
+1这是更好的方法,因为不要依靠异常解码来了解该怎么做。代码将更易于维护和理解
daitangio 2011年

4
同意@daitangio-使用运行一次的部署脚本,性能通常不会超过可维护性。
小资

1
我想了解隐式提交是否在这里起作用。您可能希望SELECT和DROP处于同一事务中。[显然忽略了可能执行的任何后续DDL。]
Mathew

2
@ Matthew,DROP是DDL命令,因此它将首先发出COMMIT,删除表,然后发出第二个COMMIT。当然,在此示例中,没有事务(因为它仅发出查询),因此没有区别;但是,如果用户先前已经发布了一些DML,它将在执行任何DDL之前隐式提交。
杰弗里·肯普

28

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

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;

希望这可以帮助


在我创建上面的过程之后。delobject,我试图称其为发出以下SQL。但这没有用。delobject('MyTable','TABLE'); 我收到以下错误--------------------------------从命令的第1行开始出错:delobject('MyTable ','TABLE')错误报告:未知命令
Shai 2012年

1
使用EXECUTE命令-EXECUTE DelObject('MyTable','TABLE');
idanuda 2014年

13

只是想发布一个完整的代码,该代码将创建一个表,如果使用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;

2
就个人而言,我将CREATE TABLE放在一个单独的步骤中,因为它不需要动态完成,也不需要异常处理程序。
Jeffrey Kemp 2013年

11

使用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文档


3

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语法。
汤姆(Tom)

3
申报计数 从all_tables中开始选择count(*)进入count_,其中table_name ='x'; 如果count> 0,则立即执行“删除表x”;万一; 结束; 您不能直接从事务块运行DDL,需要使用execute。
2009年

非常感谢!我还没有意识到语法的不同。我知道您需要将整个内容包装在一个开始/结尾中,但是我发现它正在另一个脚本的中间运行。汤姆:我决定离开我的版本,而不复制您的版本,因此我不会从您那里获得任何投票,显然,他的答案正确。
Erich

我认为这不会编译。在此处包括架构所有者也可能很重要,否则您可能会为原本不是要使用相同名称的表获得“ true”。
艾伦

发布正确答案后10分钟,您的答案已被正确的Oracle语法所取代
jpmc26

3

我更喜欢遵循经济解决方案

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;

2

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

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 “已经不存在”“已经存在,但不再存在” :)
Jeffrey Kemp 2015年

2

一种方法是使用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演示


2
但这可能不是表的名称。
杰弗里·肯普

在不同的模式中也可能有使用该名称的各种表。
Hybris95

0

遗憾的是,不存在drop,如果存在的话,或者CREATE IF NOT EXIST

您可以编写一个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;

8
会编译吗?
quillbreaker,2010年

0

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

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

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

关于
K


1
不,从不“别人
打败的

0

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

还要注意区分大小写。(请参见下面的“上部”条款)。

我放入了两个不同的对象,以表明可以在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;
/   

0
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。这仅在单个编译中起作用


2
我相信这只会在引发错误时创建表。
鱼饼干

0

而且,如果您想使其可重复输入并最小化放置/创建周期,则可以使用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类型,名称和所有者是变量。


0

这样的代码块可能对您有用。

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;  
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.