Oracle: Jeśli istnieje tabela

343

Piszę kilka skryptów migracyjnych dla bazy danych Oracle i miałem nadzieję, że Oracle ma coś podobnego do IF EXISTSkonstrukcji MySQL .

W szczególności, ilekroć chcę upuścić tabelę w MySQL, robię coś takiego

DROP TABLE IF EXISTS `table_name`;

W ten sposób, jeśli tabela nie istnieje, DROPnie powoduje błędu, a skrypt może być kontynuowany.

Czy Oracle ma podobny mechanizm? Zdaję sobie sprawę, że mogę użyć następującego zapytania, aby sprawdzić, czy tabela istnieje, czy nie

SELECT * FROM dba_tables where table_name = 'table_name';

ale składnia do wiązania tego razem ze znakiem DROPucieka mi.

Alan Storm
źródło

Odpowiedzi:

586

Najlepszym i najbardziej wydajnym sposobem jest wychwycenie wyjątku „nie znaleziono tabeli”: pozwala to uniknąć narzutu sprawdzania, czy tabela istnieje dwukrotnie; i nie ma problemu z tym, że jeśli DROP zawiedzie z innego powodu (może to być ważne) wyjątek jest nadal zgłaszany do osoby dzwoniącej:

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

DODATEK W celach informacyjnych znajdują się równoważne bloki dla innych typów obiektów:

Sekwencja

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

Widok

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

Wyzwalacz

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

Indeks

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

Kolumna

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 bazy danych

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

Widok zmaterializowany

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

Rodzaj

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

Przymus

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;

Zadanie harmonogramu

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

Użytkownik / schemat

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;

Pakiet

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;

Funkcjonować

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

Przestrzeń tabel

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

Synonim

BEGIN
  EXECUTE IMMEDIATE 'DROP SYNONYM ' || synonym_name;
EXCEPTION
  WHEN OTHERS THEN
    IF SQLCODE != -1434 THEN
      RAISE;
    END IF;
END;
Jeffrey Kemp
źródło
13
A porzucenie USER, SQLCODE do zignorowania to -1918.
Andrew Swan,
14
Trzeba napisać procedurę, czy to zrobić? Czy nie ma lepszego sposobu na zrobienie tego?
Wilson Freitas,
8
Jeśli dodam wiele EXECUTE IMMEDIATE 'DROP TABLE mytable';zdań (po jednym dla każdej tabeli w skrypcie), czy muszę wstawić jeden moduł obsługi wyjątków dla każdego z nich, czy wystarczy, aby wszystkie sentecy były zawinięte w jednym BEGIN ... EXCEPTION ... END;bloku?
Throoze
8
@ jpmc26: Odpowiednikiem MS SQL jest IF OBJECT_ID('TblName') IS NOT NULL DROP TABLE TblName. Wydaje się, że gadatliwość języka SQL jest proporcjonalna do ceny.
6
@JeffreyKemp Nie pomyślałbyś, ale raz po raz odkryłem, że Oracle utrudnia wszystko. Kiedy spędzasz średnio godzinę na niejasnym błędzie składni lub próbujesz dowiedzieć się, jak zrobić coś, co jest oczywiste i łatwe w innej bazie danych (np. Warunkowo upuścić element) i tego rodzaju problemy pojawiają się codziennie, sumuje się. Szybki.
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;

Służy to do sprawdzenia, czy istnieje tabela w bieżącym schemacie. Aby sprawdzić, czy dana tabela już istnieje w innym schemacie, musisz użyć all_tableszamiast user_tablesi dodać warunekall_tables.owner = upper('schema_name')

Marius Burz
źródło
34
+1 To jest lepsze, ponieważ nie polegaj na dekodowaniu wyjątków, aby zrozumieć, co robić. Kod będzie łatwiejszy do zarządzania i zrozumienia
daitangio,
4
Zgadzam się z @daitangio - wydajność ogólnie nie ma większego wpływu na łatwość konserwacji dzięki skryptom wdrażania uruchamianym po uruchomieniu.
pettys
1
Byłbym zainteresowany, aby zrozumieć, czy dorozumiane zatwierdzenie odgrywa tutaj rolę. Chcesz, aby SELECT i DROP były w tej samej transakcji. [Oczywiście ignoruje każdy kolejny DDL, który może zostać wykonany. ]
Mathew
2
@Mathew, DROP jest poleceniem DDL, więc najpierw wyda COMMIT, upuści tabelę, a następnie wyda 2. COMMIT. Oczywiście w tym przykładzie nie ma transakcji (ponieważ wydano tylko zapytanie), więc nie ma znaczenia; ale jeśli użytkownik wcześniej wydał trochę DML, zostanie ono domyślnie zatwierdzone przed wykonaniem jakiegokolwiek DDL.
Jeffrey Kemp,
28

Szukałem tego samego, ale ostatecznie napisałem procedurę, która mi pomoże:

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;

Mam nadzieję że to pomoże

Robert Vabo
źródło
Po utworzeniu powyżej proc. delobject, próbowałem to nazwać wydając następujący SQL. Ale to nie zadziałało. delobject („MyTable”, „TABLE”); Otrzymuję następujący błąd -------------------------------- Błąd rozpoczynania od wiersza 1 polecenia: delobject ('MyTable ”,„ TABELA ”) Raport o błędzie: Nieznane polecenie
Shai
1
użyj polecenia EXECUTE - EXECUTE DelObject („MyTable”, „TABLE”);
idanuda
13

chciałem tylko napisać pełny kod, który utworzy tabelę i upuści ją, jeśli już istnieje przy użyciu kodu Jeffreya (uznanie dla niego, nie dla mnie!).

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;
Miszkin
źródło
2
Osobiście umieściłem CREATE TABLE w osobnym kroku, ponieważ nie trzeba tego robić dynamicznie i nie trzeba obsługiwać wyjątków.
Jeffrey Kemp,
11

Za pomocą SQL * PLUS można także użyć polecenia WHENEVER SQLERROR:

WHENEVER SQLERROR CONTINUE NONE
DROP TABLE TABLE_NAME;

WHENEVER SQLERROR EXIT SQL.SQLCODE
DROP TABLE TABLE_NAME;

Z CONTINUE NONEbłędem jest zgłaszany, ale skrypt będzie kontynuowany. Ze EXIT SQL.SQLCODEskryptu zostanie rozwiązana w przypadku wystąpienia błędu.

zobacz także: KIEDYKOLWIEK Dokumenty SQLERROR

trunkc
źródło
3

W wyroczni nie ma „STOŁU UPADKU, JEŚLI ISTNIEJE”, należy wykonać instrukcję select.

spróbuj tego (nie używam składni Oracle, więc jeśli moje zmienne są ify, proszę wybacz mi):

declare @count int
select @count=count(*) from all_tables where table_name='Table_name';
if @count>0
BEGIN
    DROP TABLE tableName;
END
Erich
źródło
Podjąłem próbę przetłumaczenia skryptu na składnię Oracle.
Tom
3
zadeklaruj liczbę zliczeń; zacznij wybrać count (*) na count z all_tables gdzie table_name = 'x'; jeśli liczba> 0, wykonaj natychmiastową „tablicę upuszczania x”; koniec jeśli; koniec; Nie można uruchomić DDL bezpośrednio z bloku transakcji, należy użyć polecenia execute.
Khb
Dziękuję bardzo! Nie zdawałem sobie sprawy, że składnia jest tak inna. Wiem, że musisz wszystko zawinąć na początku / na końcu, ale pomyślałem, że jest uruchamiany w środku innego skryptu. Tom: Postanowiłem opuścić moją wersję i nie kopiować twojej, więc nie biorę od ciebie głosów, które oczywiście mają właściwą odpowiedź.
Erich,
Nie sądzę, że to się skompiluje. Może być również ważne, aby dołączyć tutaj właściciela schematu lub możesz uzyskać wartość „prawda” dla tabeli, której nie chciałeś uzyskać pod tą samą nazwą.
Allen
Twoja odpowiedź została zastąpiona prawidłową składnią Oracle 10 minut po opublikowaniu.
jpmc26,
3

Wolę następujące rozwiązanie ekonomiczne

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.
źródło
2

Inną metodą jest zdefiniowanie wyjątku, a następnie tylko złapanie tego wyjątku, umożliwiając wszystkim innym propagację.

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
źródło
@ Sk8erPeter „już nie istnieje” vs. „istniało, ale już nie” :)
Jeffrey Kemp
2

Jednym ze sposobów jest użycie DBMS_ASSERT.SQL_OBJECT_NAME :

Ta funkcja sprawdza, czy łańcuch parametru wejściowego jest kwalifikowanym identyfikatorem SQL istniejącego obiektu 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 Demo

Łukasz Szozda
źródło
2
Ale to może nie być nazwa stołu.
Jeffrey Kemp
Mogą istnieć również różne tabele używające tej nazwy w różnych schematach.
Hybris95
0

Niestety nie, nie ma czegoś takiego jak upuszczenie, jeśli istnieje, lub UTWÓRZ, JEŚLI NIE ISTNIEJE

Możesz napisać skrypt plsql, aby uwzględnić tam logikę.

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

Nie przepadam za składnią Oracle, ale myślę, że skrypt @ Ericha byłby mniej więcej taki.

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;
Tomek
źródło
8
Czy to się nawet kompiluje?
quillbreaker
0

Zawsze możesz sam złapać błąd.

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

Nadużywanie tego, podobnie jak puste catch () w innych językach, jest uważane za złą praktykę.

Pozdrawiam
K.

Khb
źródło
1
Nie, nigdy „wyjątek, gdy inni są wtedy
nieważni
0

Wolę określić tabelę i właściciela schematu.

Uważaj również na rozróżnianie wielkości liter. (patrz „górna” klauzula poniżej).

Wrzuciłem kilka różnych przedmiotów, aby pokazać, że można ich używać w innych miejscach niż TABELE.

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

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;  

I przykład TABELI:

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
źródło
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;

// Wykonując ten kod, sprawdza, czy tabela istnieje, a później tworzy tabelę max. to po prostu działa w jednej kompilacji

Mahesh Pandeya
źródło
2
Wierzę, że to tworzy tabelę tylko wtedy, gdy zostanie zgłoszony błąd.
Fish Biscuit
0

A jeśli chcesz, aby był on ponownie dostępny i minimalizował cykle upuszczania / tworzenia, możesz buforować DDL za pomocą dbms_metadata.get_ddl i odtworzyć wszystko za pomocą takiej konstrukcji: 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; To tylko próbka, wewnątrz powinna być pętla z Typ DDL, nazwa i właściciel są zmiennymi.

Andrei Nossov
źródło
0

Taki blok może ci się przydać.

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;  

źródło