Oracle: si la table existe


343

J'écris des scripts de migration pour une base de données Oracle et j'espérais qu'Oracle avait quelque chose de similaire à la IF EXISTSconstruction de MySQL .

Plus précisément, chaque fois que je veux déposer une table dans MySQL, je fais quelque chose comme

DROP TABLE IF EXISTS `table_name`;

De cette façon, si la table n'existe pas, le DROPne produit pas d'erreur et le script peut continuer.

Oracle a-t-il un mécanisme similaire? Je me rends compte que je pourrais utiliser la requête suivante pour vérifier si une table existe ou non

SELECT * FROM dba_tables where table_name = 'table_name';

mais la syntaxe pour lier cela ensemble avec un DROPm'échappe.

Réponses:


586

Le moyen le plus efficace et le plus efficace consiste à intercepter l'exception "table non trouvée": cela évite la surcharge de vérification si la table existe deux fois; et ne souffre pas du problème que si le DROP échoue pour une autre raison (qui pourrait être importante), l'exception est toujours levée à l'appelant:

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

ADDENDUM Pour référence, voici les blocs équivalents pour les autres types d'objets:

Séquence

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

Vue

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

Déclencheur

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;

Colonne

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;

Lien de base de données

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

Vue matérialisée

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

Type

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

Contrainte

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;

Travail du planificateur

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

Utilisateur / Schéma

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;

Paquet

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

Procédure

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

Fonction

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

Espace disque logique

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

Synonyme

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

13
Et pour supprimer un USER, le SQLCODE à ignorer est -1918.
Andrew Swan

14
Il faut écrire une procédure, faire ça? N'y a-t-il pas une meilleure façon de le faire?
Wilson Freitas

8
Si j'ajoute plusieurs EXECUTE IMMEDIATE 'DROP TABLE mytable';phrases (une pour chaque table du script), dois-je mettre un gestionnaire d'exceptions pour chacune, ou est-ce suffisant pour encapsuler toutes les senteces dans un BEGIN ... EXCEPTION ... END;bloc?
Throoze

8
@ jpmc26: L'équivalent pour MS SQL est IF OBJECT_ID('TblName') IS NOT NULL DROP TABLE TblName. Il semble que la verbosité d'un langage SQL soit proportionnelle au prix.

6
@JeffreyKemp Vous ne le pensez pas, mais j'ai constaté maintes et maintes fois qu'Oracle rend tout difficile. Lorsque vous passez en moyenne une heure par erreur de syntaxe obscure ou que vous essayez de comprendre comment faire quelque chose qui est évident et facile dans une autre base de données (comme supprimer un élément de manière conditionnelle) et que ce genre de problèmes apparaît quotidiennement, cela s'ajoute. Vite.
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;

C'est pour vérifier si une table dans le schéma actuel existe. Pour vérifier si une table donnée existe déjà dans un schéma différent, vous devez utiliser à la all_tablesplace de user_tableset ajouter la conditionall_tables.owner = upper('schema_name')


34
+1 C'est mieux car il ne faut pas se fier au décodage d'exception pour comprendre quoi faire. Le code sera plus facile à maintenir et à comprendre
daitangio

4
D'accord avec @daitangio - les performances ne l'emportent généralement pas sur la maintenabilité avec les scripts de déploiement à exécution unique.
pettys

1
Je serais intéressé de comprendre si implicit-commit joue un rôle ici. Vous voudriez que SELECT et DROP soient dans la même transaction. [Ignorer évidemment toute DDL ultérieure qui pourrait être exécutée. ]
Mathew

2
@Matthew, le DROP est une commande DDL, il va donc d'abord émettre un COMMIT, supprimer la table, puis émettre un 2e COMMIT. Bien sûr, dans cet exemple, il n'y a pas de transaction (car il ne s'agit que d'une requête), donc cela ne fait aucune différence; mais si l'utilisateur avait précédemment émis du DML, il sera implicitement validé avant l'exécution de tout DDL.
Jeffrey Kemp

28

Je cherchais la même chose mais j'ai fini par écrire une procédure pour m'aider:

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;

J'espère que cela t'aides


Après avoir créé ci-dessus proc. delobject, j'ai essayé de l'appeler en émettant le code SQL suivant. Mais cela n'a pas fonctionné. delobject ('MyTable', 'TABLE'); J'obtiens l'erreur suivante -------------------------------- Erreur au démarrage à la ligne 1 de la commande: delobject ('MyTable ',' TABLE ') Rapport d'erreur: commande inconnue
Shai

1
utilisez la commande EXECUTE - EXECUTE DelObject ('MyTable', 'TABLE');
idanuda

13

Je voulais juste publier un code complet qui va créer un tableau et le déposer s'il existe déjà en utilisant le code de Jeffrey (bravo à lui, pas moi!).

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
Personnellement, je mettrais CREATE TABLE dans une étape distincte, car il n'a pas besoin d'être fait dynamiquement et n'a pas besoin d'un gestionnaire d'exceptions.
Jeffrey Kemp

11

Avec SQL * PLUS, vous pouvez également utiliser la commande WHENEVER SQLERROR:

WHENEVER SQLERROR CONTINUE NONE
DROP TABLE TABLE_NAME;

WHENEVER SQLERROR EXIT SQL.SQLCODE
DROP TABLE TABLE_NAME;

Une CONTINUE NONEerreur est signalée, mais le script continuera. Avec EXIT SQL.SQLCODEle script sera terminé en cas d'erreur.

voir aussi: WHENEVER SQLERROR Docs


3

Il n'y a pas de 'DROP TABLE IF EXISTS' dans oracle, vous devriez faire l'instruction select.

essayez ceci (je ne suis pas sur la syntaxe Oracle, donc si mes variables sont ify, veuillez me pardonner):

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

J'ai tenté de traduire le script en syntaxe Oracle.
Tom

3
déclarer le numéro de comptage; commencer à sélectionner count (*) dans count à partir de all_tables où table_name = 'x'; si count> 0 alors exécutez immédiatement 'drop table x'; fin si; fin; Vous ne pouvez pas exécuter DDL directement à partir d'un bloc de transaction, vous devez utiliser execute.
Khb

Merci beaucoup! Je n'avais pas réalisé que la syntaxe était si différente. Je savais que vous deviez envelopper le tout dans un début / fin, mais je pensais qu'il était exécuté au milieu d'un autre script. Tom: J'ai décidé de laisser ma version et de ne pas copier la vôtre, donc je ne prends aucun vote de votre part, qui a évidemment la bonne réponse.
Erich

Je ne pense pas que cela compilera. Il peut également être important d'inclure ici le propriétaire du schéma ou vous pouvez obtenir la valeur "true" pour une table que vous ne vouliez pas obtenir avec le même nom.
Allen

Votre réponse a été remplacée par la syntaxe Oracle correcte 10 minutes après sa publication.
jpmc26

3

Je préfère suivre la solution économique

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

Une autre méthode consiste à définir une exception, puis à intercepter uniquement cette exception en laissant tous les autres se propager.

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 "n'existe pas déjà" vs "existait, mais n'existe plus" :)
Jeffrey Kemp

2

Une façon consiste à utiliser DBMS_ASSERT.SQL_OBJECT_NAME :

Cette fonction vérifie que la chaîne de paramètre d'entrée est un identificateur SQL qualifié d'un objet SQL existant.

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
Mais ce n'est peut-être pas le nom d'une table.
Jeffrey Kemp

Il peut également y avoir différentes tables utilisant ce nom dans différents schémas.
Hybris95

0

Malheureusement non, il n'y a rien de tel que drop s'il existe, ou CRÉER SI N'EXISTE PAS

Vous pouvez écrire un script plsql pour y inclure la logique.

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

Je ne suis pas beaucoup dans la syntaxe Oracle, mais je pense que le script de @ Erich serait quelque chose comme ça.

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
Est-ce que cela compile même?
quillbreaker

0

Vous pouvez toujours détecter l'erreur vous-même.

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

Il est considéré comme une mauvaise pratique d'en abuser, comme pour les catch () vides dans d'autres langues.

Cordialement
K


1
Non, jamais "exception quand les autres sont alors nuls"
miracle173

0

Je préfère spécifier la table et le propriétaire du schéma.

Faites également attention à la sensibilité à la casse. (voir la clause "supérieure" ci-dessous).

J'ai jeté un couple d'objets différents pour montrer qu'il peut être utilisé dans des endroits autres que des 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;  

Et un exemple 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;
/   

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;

// En faisant ce code, vérifie si la table existe et plus tard, elle crée la table max. cela fonctionne simplement en une seule compilation


2
Je crois que cela ne crée la table que lorsque l'erreur est levée.
Fish Biscuit

0

Et si vous voulez le rendre réinscriptible et minimiser les cycles de suppression / création, vous pouvez mettre en cache le DDL à l'aide de dbms_metadata.get_ddl et tout recréer à l'aide d'une construction comme celle-ci: 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; ceci n'est qu'un exemple, il devrait y avoir une boucle à l'intérieur avec Le type, le nom et le propriétaire DDL étant des variables.


0

Un bloc comme celui-ci pourrait vous être 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;  
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.