Oracle: se a tabela existir

343

Estou escrevendo alguns scripts de migração para um banco de dados Oracle e esperava que o Oracle tivesse algo semelhante à IF EXISTSconstrução do MySQL .

Especificamente, sempre que eu quero soltar uma tabela no MySQL, faço algo como

DROP TABLE IF EXISTS `table_name`;

Dessa forma, se a tabela não existir, DROPela não produzirá um erro e o script poderá continuar.

A Oracle tem um mecanismo semelhante? Sei que poderia usar a seguinte consulta para verificar se uma tabela existe ou não

SELECT * FROM dba_tables where table_name = 'table_name';

mas a sintaxe de vincular isso a um DROPestá me escapando.

Alan Storm
fonte

Respostas:

585

A maneira melhor e mais eficiente é capturar a exceção "tabela não encontrada": isso evita a sobrecarga de verificar se a tabela existe duas vezes; e não sofre com o problema de que, se o DROP falhar por algum outro motivo (isso pode ser importante), a exceção ainda será gerada para o chamador:

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

ADENDO Para referência, aqui estão os blocos equivalentes para outros tipos de objetos:

Seqüência

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

Visão

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

Desencadear

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

Índice

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

Coluna

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;

Link do banco de dados

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

Visão materializada

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

Tipo

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

Restrição

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;

Trabalho do Agendador

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

Usuário / Esquema

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;

Pacote

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

Procedimento

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

Função

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

Tablespace

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

Sinônimo

BEGIN
  EXECUTE IMMEDIATE 'DROP SYNONYM ' || synonym_name;
EXCEPTION
  WHEN OTHERS THEN
    IF SQLCODE != -1434 THEN
      RAISE;
    END IF;
END;
Jeffrey Kemp
fonte
13
E para descartar um USER, o SQLCODE a ser ignorado é -1918.
Andrew Swan
14
É preciso escrever um procedimento, faça isso? Não existe uma maneira melhor de fazer isso?
Wilson Freitas
8
Se eu adicionar muitas EXECUTE IMMEDIATE 'DROP TABLE mytable';frases (uma para cada tabela no script), preciso colocar um manipulador de exceções para cada uma ou será suficiente para agrupar todas as senteces em um BEGIN ... EXCEPTION ... END;bloco?
Throoze
8
@ jpmc26: O equivalente para MS SQL é IF OBJECT_ID('TblName') IS NOT NULL DROP TABLE TblName. Parece que a verbosidade de uma linguagem SQL é proporcional ao preço.
6
@JeffreyKemp Você não pensaria assim, mas descobri várias vezes que a Oracle dificulta tudo. Quando você gasta em média uma hora por erro de sintaxe obscuro ou tenta descobrir como fazer algo óbvio e fácil em outro banco de dados (como eliminar condicionalmente um elemento) e esses tipos de problemas surgem diariamente, acrescenta-se. Rápido.
precisa
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;

Isso é para verificar se existe uma tabela no esquema atual. Para verificar se uma determinada tabela já existe em um esquema diferente, você teria que usar em all_tablesvez de user_tablese adicionar a condiçãoall_tables.owner = upper('schema_name')

Marius Burz
fonte
34
+1 Isso é melhor porque não retransmitir a decodificação de exceção para entender o que fazer. Código será mais fácil de mantain e compreender
daitangio
4
Concorde com @daitangio - o desempenho geralmente não supera a capacidade de manutenção com scripts de implantação executados uma vez.
Pettys
11
Eu estaria interessado em entender se o implícito-commit desempenha um papel aqui. Você deseja que o SELECT e o DROP estejam dentro da mesma transação. [Obviamente ignorando qualquer DDL subsequente que possa ser executado. ]
Mathew
2
@ Matthew, o DROP é um comando DDL, então ele primeiro emitirá um COMMIT, soltará a tabela e, em seguida, emitirá um segundo COMMIT. Obviamente, neste exemplo não há transação (uma vez que é emitida apenas uma consulta), portanto não faz diferença; mas se o usuário tiver emitido anteriormente algum DML, ele será implicitamente confirmado antes da execução de qualquer DDL.
21415 Jeffrey Kemp
28

Eu estava procurando o mesmo, mas acabei escrevendo um procedimento para me ajudar:

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;

Espero que isto ajude

Robert Vabo
fonte
Depois que eu criei acima proc. delobject, tentei chamá-lo emitindo o seguinte SQL. Mas não funcionou. delobject ('MyTable', 'TABLE'); Estou recebendo o seguinte erro -------------------------------- Erro iniciando na linha 1 no comando: delobject ('MyTable ',' TABLE ') Relatório de erro: Comando desconhecido
Shai
11
use o comando EXECUTE - EXECUTE DelObject ('MyTable', 'TABLE');
Idanuda 7/07/2014
13

só queria postar um código completo que criará uma tabela e a largará, se ela já existir, usando o código de Jeffrey (parabéns a ele, não a mim!).

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;
mishkin
fonte
2
Pessoalmente, eu colocaria a CREATE TABLE em uma etapa separada, pois ela não precisa ser feita dinamicamente e não precisa de um manipulador de exceções.
Jeffrey Kemp
11

Com o SQL * PLUS, você também pode usar o comando WHENEVER SQLERROR:

WHENEVER SQLERROR CONTINUE NONE
DROP TABLE TABLE_NAME;

WHENEVER SQLERROR EXIT SQL.SQLCODE
DROP TABLE TABLE_NAME;

Com CONTINUE NONEum erro é relatado, mas o script continuará. Com EXIT SQL.SQLCODEo script será encerrado no caso de um erro.

veja também: WHENEVER SQLERROR Docs

trunkc
fonte
3

Não há 'DROP TABLE IF EXISTS' no oracle, você precisaria fazer a instrução select.

tente isso (não gosto da sintaxe do oracle, portanto, se minhas variáveis ​​forem duvidosas, por favor, me perdoe):

declare @count int
select @count=count(*) from all_tables where table_name='Table_name';
if @count>0
BEGIN
    DROP TABLE tableName;
END
Erich
fonte
Fiz uma tentativa de traduzir o script para a sintaxe do oracle.
25411 Tom
3
declarar número de contagem; comece selecione count (*) no count de all_tables em que table_name = 'x'; se contar> 0, execute imediatamente 'drop table x'; fim se; fim; Você não pode executar o DDL diretamente de um bloco de transação, é necessário usar execute.
KHB
Muito obrigado! Eu não tinha percebido que a sintaxe era tão diferente. Eu sabia que você precisava agrupar tudo em um começo / fim, mas achei que estava sendo executado no meio de outro script. Tom: Decidi deixar minha versão e não copiar a sua, para não receber nenhum voto seu, que obviamente tem a resposta certa.
Erich
Eu não acho que isso irá compilar. Também pode ser importante incluir o proprietário do esquema aqui ou você pode ser 'verdadeiro' para uma tabela que não pretendia obter com o mesmo nome.
Allen
Sua resposta foi substituída pela sintaxe correta do Oracle 10 minutos após a postagem.
jpmc26
3

Eu prefiro seguir solução econômica

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;
Pavel S
fonte
2

Outro método é definir uma exceção e capturar apenas essa exceção, permitindo a propagação de todas as outras.

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;
Leigh Riffel
fonte
@ Sk8erPeter "já não existe" vs. "existia, mas não faz mais" :)
Jeffrey Kemp
2

Uma maneira é usar DBMS_ASSERT.SQL_OBJECT_NAME :

Essa função verifica se a sequência de parâmetros de entrada é um identificador SQL qualificado de um objeto SQL existente.

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

Lukasz Szozda
fonte
2
Mas pode não ser o nome de uma tabela.
Jeffrey Kemp
Também pode haver várias tabelas usando esse nome em esquemas diferentes.
Hybris95 6/04
0

Infelizmente não, não existe drop, se existir, ou CREATE SE NÃO EXISTIR

Você pode escrever um script plsql para incluir a lógica lá.

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

Não gosto muito do Oracle Syntax, mas acho que o script do @ Erich seria algo parecido com isto.

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;
Tom
fonte
8
Isso ainda compila?
quillbreaker
0

Você sempre pode pegar o erro sozinho.

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

É considerado uma prática inadequada usar em excesso isso, semelhante a catch () vazio em outros idiomas.

Atenciosamente
K

Khb
fonte
11
Não, nunca "exceção quando os outros então nulo"
miracle173
0

Prefiro especificar a tabela e o proprietário do esquema.

Cuidado também com a distinção entre maiúsculas e minúsculas. (veja a cláusula "superior" abaixo).

Joguei alguns objetos diferentes para mostrar que podem ser usados ​​em outros lugares além de 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;  

E um exemplo de 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;
/   
granadaCoder
fonte
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;

// Fazendo esse código, verifica se a tabela existe e depois cria a tabela no máximo. isso simplesmente funciona em compilação única

Mahesh Pandeya
fonte
2
Eu acredito que isso só cria a tabela quando o erro é gerado.
Peixe biscoito
0

E se você quiser torná-lo re-digitado e minimizar os ciclos de descartar / criar, poderá armazenar em cache o DDL usando dbms_metadata.get_ddl e recriar tudo usando uma construção como esta: 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; Esta é apenas uma amostra, deve haver um loop interno com Tipo DDL, nome e proprietário sendo variáveis.

Andrei Nossov
fonte
0

Um bloco como esse pode ser útil para você.

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;  

fonte