Oracle: se esiste una tabella


343

Sto scrivendo alcuni script di migrazione per un database Oracle e speravo che Oracle avesse qualcosa di simile al IF EXISTScostrutto di MySQL .

In particolare, ogni volta che voglio eliminare una tabella in MySQL, faccio qualcosa di simile

DROP TABLE IF EXISTS `table_name`;

In questo modo, se la tabella non esiste, DROPnon viene generato un errore e lo script può continuare.

Oracle ha un meccanismo simile? Mi rendo conto che potrei usare la seguente query per verificare se esiste una tabella o meno

SELECT * FROM dba_tables where table_name = 'table_name';

ma la sintassi per legare questo insieme a a DROPmi sta sfuggendo.

Risposte:


586

Il modo migliore ed efficiente è quello di catturare l'eccezione "tabella non trovata": questo evita il sovraccarico di verificare se la tabella esiste due volte; e non soffre del problema che se il DROP fallisce per qualche altro motivo (che potrebbe essere importante) l'eccezione viene comunque sollevata al chiamante:

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

ADDENDUM Per riferimento, ecco i blocchi equivalenti per altri tipi di oggetti:

Sequenza

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

Visualizza

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

grilletto

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

Indice

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

Colonna

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;

Collegamento al database

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

Vista materializzata

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

genere

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

costrizione

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;

Job Scheduler

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

Utente / Schema

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;

Pacchetto

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

Procedura

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

Funzione

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;

Sinonimo

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

13
E per far cadere un USER, il SQLCODE da ignorare è -1918.
Andrew Swan,

14
Bisogna scrivere una procedura per farlo? Non c'è un modo migliore per farlo?
Wilson Freitas,

8
Se aggiungo molte EXECUTE IMMEDIATE 'DROP TABLE mytable';frasi (una per ogni tabella nello script), devo mettere un gestore di eccezioni per ognuna o è sufficiente per racchiudere tutte le sentine in un BEGIN ... EXCEPTION ... END;blocco?
Throoze,

8
@ jpmc26: l'equivalente per MS SQL è IF OBJECT_ID('TblName') IS NOT NULL DROP TABLE TblName. Sembra che la verbosità di un linguaggio SQL sia proporzionale al prezzo.

6
@JeffreyKemp Non lo penseresti, ma ho scoperto più volte che Oracle rende tutto difficile. Quando trascorri in media un'ora per oscuro errore di sintassi o provi a capire come fare qualcosa di ovvio e facile in un altro database (come eliminare condizionalmente un elemento) e questi tipi di problemi compaiono quotidianamente, si aggiunge. Veloce.
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;

Questo per verificare se esiste una tabella nello schema corrente. Per verificare se una determinata tabella esiste già in uno schema diverso, è necessario utilizzare all_tablesinvece di user_tablesaggiungere la condizioneall_tables.owner = upper('schema_name')


34
+1 È meglio perché non inoltrare la decodifica delle eccezioni per capire cosa fare. Il codice sarà più facile da mantenere e da capire
daitangio

4
Concordo con @daitangio: le prestazioni generalmente non vincono la manutenibilità con gli script di distribuzione run-once.
pettys

1
Sarei interessato a capire se implicit-commit gioca un ruolo qui. Vorresti che SELECT e DROP fossero all'interno della stessa transazione. [Ovviamente ignorando qualsiasi DDL successivo che può essere eseguito. ]
Mathew,

2
@Matthew, il DROP è un comando DDL, quindi prima emetterà un COMMIT, eliminerà la tabella, quindi emetterà un 2 ° COMMIT. Ovviamente, in questo esempio non vi è alcuna transazione (poiché è stata emessa solo una query), quindi non fa alcuna differenza; ma se l'utente aveva precedentemente emesso del DML, questo verrà commesso implicitamente prima dell'esecuzione di qualsiasi DDL.
Jeffrey Kemp,

28

Ho cercato lo stesso ma ho finito per scrivere una procedura per aiutarmi:

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;

Spero che questo ti aiuti


Dopo aver creato sopra proc. delobject, ho provato a chiamarlo emettendo il seguente SQL. Ma non ha funzionato. delobject ('MyTable', 'TABLE'); Ricevo il seguente errore -------------------------------- Errore a partire dalla riga 1 nel comando: delobject ('MyTable ',' SPAZIO PER TABELLA ') Rapporto errori: comando sconosciuto
Shai

1
utilizzare il comando EXECUTE - EXECUTE DelObject ('MyTable', 'TABLE');
idanuda,

13

volevo solo pubblicare un codice completo che creerà una tabella e rilasciarla se esiste già usando il codice di Jeffrey (complimenti a lui, non a me!).

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
Personalmente, metterei CREATE TABLE in un passaggio separato, dal momento che non deve essere eseguito in modo dinamico e non richiede un gestore di eccezioni.
Jeffrey Kemp il

11

Con SQL * PLUS puoi anche usare il comando WHENEVER SQLERROR:

WHENEVER SQLERROR CONTINUE NONE
DROP TABLE TABLE_NAME;

WHENEVER SQLERROR EXIT SQL.SQLCODE
DROP TABLE TABLE_NAME;

Con CONTINUE NONEun errore viene segnalato, ma lo script continuerà. Con EXIT SQL.SQLCODElo script verrà terminato in caso di errore.

vedi anche: WHENEVER SQLERROR Docs


3

Non esiste "DROP TABLE IF EXISTS" in Oracle, dovresti fare l'istruzione select.

prova questo (non ho la sintassi dell'oracolo, quindi se le mie variabili sono ify, ti prego di perdonarmi):

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

Ho tentato di tradurre lo script nella sintassi di Oracle.
Tom,

3
dichiarare il numero di conteggio; inizia a selezionare count (*) in count da all_tables dove table_name = 'x'; se count> 0 esegue immediatamente 'drop table x'; finisci se; fine; Non è possibile eseguire DDL direttamente da un blocco di transazione, è necessario utilizzare execute.
Khb

Grazie mille! Non mi ero reso conto che la sintassi fosse così diversa. Sapevo che devi avvolgere il tutto in un inizio / fine, ma ho pensato che sarebbe stato eseguito nel mezzo di un'altra sceneggiatura. Tom: Ho deciso di lasciare la mia versione e di non copiarla, quindi non prendo nessun voto da te, che ovviamente ha la risposta giusta.
Erich,

Non penso che questo verrà compilato. Potrebbe anche essere importante includere qui il proprietario dello schema oppure potresti ottenere "vero" per una tabella che non intendevi ottenere con lo stesso nome.
Allen

La tua risposta è stata sostituita dalla sintassi Oracle corretta 10 minuti dopo la sua pubblicazione.
jpmc26,

3

Preferisco seguire la soluzione economica

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

Un altro metodo è quello di definire un'eccezione e quindi catturare quell'eccezione permettendo a tutti gli altri di propagarsi.

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 "già non esiste" vs. "esisteva, ma non esiste più" :)
Jeffrey Kemp

2

Un modo è utilizzare DBMS_ASSERT.SQL_OBJECT_NAME :

Questa funzione verifica che la stringa del parametro di input sia un identificatore SQL qualificato di un oggetto SQL esistente.

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


2
Ma potrebbe non essere il nome di una tabella.
Jeffrey Kemp,

Potrebbero esserci anche varie tabelle che usano quel nome in diversi schemi.
Hybris95,

0

Purtroppo no, non esiste nulla come drop, se esiste, o CREATE IF NOT EXIST

È possibile scrivere uno script plsql per includere la logica lì.

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

Non mi piace molto la sintassi di Oracle, ma penso che la sceneggiatura di @Erich sarebbe qualcosa del genere.

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
Anche questo si compila?
Quillbreaker

0

Puoi sempre rilevare l'errore da solo.

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

Si ritiene che sia una cattiva pratica abusare di questo, simile a svuotare catch () in altre lingue.

Saluti
K


1
No, mai "eccezione quando altri allora null"
miracle173

0

Preferisco specificare la tabella e il proprietario dello schema.

Fai attenzione anche alla distinzione tra maiuscole e minuscole. (vedere la clausola "superiore" di seguito).

Ho lanciato un paio di oggetti diversi per dimostrare che può essere utilizzato in luoghi diversi da TABELLE.

.............

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 un esempio di TABELLA:

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;

// Facendo questo codice, controlla se la tabella esiste e successivamente crea la tabella max. questo funziona semplicemente in un'unica compilation


2
Credo che questo crei la tabella solo quando viene generato l'errore.
Biscotto di pesce

0

E se vuoi renderlo nuovamente inseribile e minimizzare i cicli di drop / create, potresti mettere in cache il DDL usando dbms_metadata.get_ddl e ricreare tutto usando un costrutto come questo: 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; questo è solo un esempio, dovrebbe esserci un loop all'interno con Tipo DDL, nome e proprietario sono variabili.


0

Un blocco come questo potrebbe esserti utile.

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;  
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.