Oracle: si la tabla existe

343

Estoy escribiendo algunos scripts de migración para una base de datos Oracle, y esperaba que Oracle tuviera algo similar a la IF EXISTSconstrucción de MySQL .

Específicamente, cada vez que quiero colocar una tabla en MySQL, hago algo como

DROP TABLE IF EXISTS `table_name`;

De esta manera, si la tabla no existe, DROPno produce un error y el script puede continuar.

¿Oracle tiene un mecanismo similar? Me doy cuenta de que podría usar la siguiente consulta para verificar si existe una tabla o no

SELECT * FROM dba_tables where table_name = 'table_name';

pero DROPse me escapa la sintaxis para vincular eso con a .

Alan Storm
fuente

Respuestas:

585

La forma mejor y más eficiente es detectar la excepción "tabla no encontrada": esto evita la sobrecarga de verificar si la tabla existe dos veces; y no sufre el problema de que si el DROP falla por alguna otra razón (que podría ser importante) la excepción aún se eleva a la persona que llama:

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

ADENDA Para referencia, aquí están los bloques equivalentes para otros tipos de objetos:

Secuencia

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

Ver

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

Desencadenar

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;

Columna

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;

Enlace de base de datos

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

Vista 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;

Restricción

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;

Programador de trabajo

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

Usuario / 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;

Paquete

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

Procedimiento

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

Función

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

Espacio de tabla

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
fuente
13
Y para descartar un USUARIO, el SQLCODE a ignorar es -1918.
Andrew Swan el
14
Uno necesita escribir un procedimiento para hacer eso? ¿No hay una mejor manera de hacer eso?
Wilson Freitas
8
Si agrego muchas EXECUTE IMMEDIATE 'DROP TABLE mytable';oraciones (una para cada tabla en el guión), ¿tengo que poner un controlador de excepción para cada una o es suficiente para envolver todas las oraciones en un BEGIN ... EXCEPTION ... END;bloque?
Throoze
8
@ jpmc26: el equivalente para MS SQL es IF OBJECT_ID('TblName') IS NOT NULL DROP TABLE TblName. Parece que la verbosidad de un lenguaje SQL es proporcional al precio.
66
@JeffreyKemp No lo pensarías, pero he encontrado una y otra vez que Oracle dificulta todo. Cuando pasas un promedio de una hora por error de sintaxis oscuro o intentas descubrir cómo hacer algo que es obvio y fácil en otra base de datos (como soltar un elemento condicionalmente) y ese tipo de problemas aparecen a diario, se acumula. Rápido.
jpmc26
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;

Eso es para verificar si existe una tabla en el esquema actual. Para verificar si una tabla dada ya existe en un esquema diferente, tendría que usar en all_tableslugar de user_tablesy agregar la condiciónall_tables.owner = upper('schema_name')

Marius Burz
fuente
34
+1 Esto es mejor porque no retransmitir en decodificación de excepción para entender qué hacer. Código será más fácil de entender y MANTENER
daitangio
44
De acuerdo con @daitangio: el rendimiento generalmente no supera la capacidad de mantenimiento con los scripts de implementación de ejecución única.
Pettys
1
Me interesaría entender si implícita-commit juega un papel aquí. Desearía que SELECT y DROP estén dentro de la misma transacción. [Obviamente ignorando cualquier DDL posterior que pueda ejecutarse. ]
Mateo
2
@Matthew, el DROP es un comando DDL, por lo que primero emitirá un COMMIT, abandonará la tabla y luego emitirá un 2º COMMIT. Por supuesto, en este ejemplo no hay transacción (ya que solo se emite una consulta), por lo que no hay diferencia; pero si el usuario había emitido previamente algún DML, se confirmará implícitamente antes de que se ejecute cualquier DDL.
Jeffrey Kemp
28

He estado buscando lo mismo pero terminé escribiendo un procedimiento para ayudarme:

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 esto ayude

Robert Vabo
fuente
Después de que creé el proceso anterior. delobject, intenté llamarlo emitiendo el siguiente SQL. Pero no funcionó. delobject ('MyTable', 'TABLE'); Recibo el siguiente error -------------------------------- Error al comenzar en la línea 1 en el comando: delobject ('MyTable ',' TABLE ') Informe de error: Comando desconocido
Shai
1
use el comando EXECUTE - EJECUTAR DelObject ('MyTable', 'TABLE');
idanuda
13

solo quería publicar un código completo que crearía una tabla y la soltaría si ya existe usando el código de Jeffrey (¡felicitaciones a él, no a mí!).

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
fuente
2
Personalmente, pondría CREATE TABLE en un paso separado, ya que no necesita hacerse dinámicamente y no necesita un controlador de excepciones.
Jeffrey Kemp
11

Con SQL * PLUS también puede usar el comando WHENEVER SQLERROR:

WHENEVER SQLERROR CONTINUE NONE
DROP TABLE TABLE_NAME;

WHENEVER SQLERROR EXIT SQL.SQLCODE
DROP TABLE TABLE_NAME;

Con CONTINUE NONEun error se informa, pero el script continuará. Con EXIT SQL.SQLCODEel script se terminará en caso de error.

ver también: CUANDO SQLERROR Docs

trunkc
fuente
3

No hay 'TABLA DE GOTAS SI EXISTE' en Oracle, tendría que hacer la declaración de selección.

intente esto (no estoy al tanto de la sintaxis de Oracle, así que si mis variables son dudosas, por favor perdóneme):

declare @count int
select @count=count(*) from all_tables where table_name='Table_name';
if @count>0
BEGIN
    DROP TABLE tableName;
END
Erich
fuente
Intenté traducir el script a la sintaxis Oracle.
Tom
3
declarar número de conteo; comenzar seleccione count (*) en count from all_tables donde table_name = 'x'; si cuenta> 0, ejecute inmediatamente 'drop table x'; terminara si; final; No puede ejecutar DDL directamente desde un bloque de transacciones, debe usar ejecutar.
Khb
¡Muchas gracias! No me había dado cuenta de que la sintaxis era tan diferente. SABÍA que necesita envolver todo en un comienzo / final, pero supuse que se estaba ejecutando en medio de otro script. Tom: Decidí dejar mi versión y no copiar la tuya, así que no recibo ningún voto de ti, que obviamente tiene la respuesta correcta.
Erich el
No creo que esto compile. También puede ser importante incluir el propietario del esquema aquí o puede obtener "verdadero" para una tabla que no quiso obtener con el mismo nombre.
Allen
Su respuesta fue reemplazada por la sintaxis correcta de Oracle 10 minutos después de que esto fue publicado.
jpmc26
3

Prefiero seguir la solución 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
fuente
2

Otro método es definir una excepción y luego capturar esa excepción dejando que todos los demás se propaguen.

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
fuente
@ Sk8erPeter "ya no existe" vs. "existió, pero ya no existe" :)
Jeffrey Kemp
2

Una forma es usar DBMS_ASSERT.SQL_OBJECT_NAME :

Esta función verifica que la cadena del parámetro de entrada es un identificador SQL calificado de un 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
fuente
2
Pero puede que no sea el nombre de una mesa.
Jeffrey Kemp,
También podría haber varias tablas con ese nombre en diferentes esquemas.
Hybris95
0

Lamentablemente no, no existe tal cosa como soltar si existe, o CREAR SI NO EXISTE

Podría escribir un script plsql para incluir la lógica allí.

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

No me gusta mucho la sintaxis de Oracle, pero creo que el script de @ Erich sería algo como esto.

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
fuente
8
¿Esto incluso compila?
Quillbreaker
0

Siempre puede detectar el error usted mismo.

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

Se considera una mala práctica abusar de esto, similar a la captura vacía () en otros idiomas.

Saludos
K

Khb
fuente
1
No, nunca "excepción cuando otros son nulos"
milagro173
0

Prefiero especificar la tabla y el propietario del esquema.

Tenga cuidado con la mayúsculas y minúsculas también. (ver cláusula "superior" a continuación).

Lancé un par de objetos diferentes para mostrar que se puede usar en lugares además 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;  

Y un ejemplo de TABLA:

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
fuente
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;

// Al hacer este código, verifica si la tabla existe y luego crea la tabla max. esto simplemente funciona en una sola compilación

Mahesh Pandeya
fuente
2
Creo que esto solo crea la tabla cuando se produce el error.
Fish Biscuit
0

Y si desea que sea reingresable y minimizar los ciclos de caída / creación, puede almacenar en caché el DDL usando dbms_metadata.get_ddl y volver a crear todo usando una construcción 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; Esto es solo una muestra, debe haber un bucle adentro con Tipo DDL, nombre y propietario siendo variables.

Andrei Nossov
fuente
0

Un bloque como este podría serle útil.

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;  

fuente