Oracle: Якщо таблиця існує


343

Я пишу кілька сценаріїв міграції для бази даних Oracle і сподівався, що Oracle має щось подібне до IF EXISTSконструкції MySQL .

Зокрема, коли я хочу опустити таблицю в MySQL, я роблю щось подібне

DROP TABLE IF EXISTS `table_name`;

Таким чином, якщо таблиці не існує, DROPне створюється помилка, і сценарій може продовжуватися.

Чи має Oracle подібний механізм? Я розумію, що міг би використати наступний запит, щоб перевірити, чи існує таблиця чи ні

SELECT * FROM dba_tables where table_name = 'table_name';

але синтаксис прив’язки, що разом із a DROP, рятується від мене.

Відповіді:


586

Найкращий і найефективніший спосіб - це визначити виняток "таблицю не знайдено": це дозволяє уникнути накладних перевірок, чи існує таблиця двічі; і не страждає від проблеми у тому, що якщо DROP виходить з ладу з якоїсь іншої причини (що може бути важливо), виняток все одно піднімається на абонента:

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

ДОПОЛНЕННЯ Для довідки, ось такі еквівалентні блоки для інших типів об’єктів:

Послідовність

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

Вид

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

Тригер

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

Покажчик

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

Стовпчик

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;

Посилання до бази даних

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

Матеріалізований вид

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

Тип

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

Обмеження

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;

Робота планувальника

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

Користувач / схема

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;

Пакет

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

Порядок

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

Функція

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

Простір таблиць

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

Синонім

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

13
А для відмови від USER, SQLCODE для ігнорування - -1918.
Ендрю Лебедь

14
Потрібно написати процедуру, щоб це зробити? Хіба немає кращого способу це зробити?
Вілсон Фрейтас

8
Якщо я додаю багато EXECUTE IMMEDIATE 'DROP TABLE mytable';речень (по одній для кожної таблиці в сценарії), чи потрібно мені покласти один обробник винятків для кожного, чи достатньо, щоб обернути всі сенси в один BEGIN ... EXCEPTION ... END;блок?
Throoze

8
@ jpmc26: еквівалент для MS SQL є IF OBJECT_ID('TblName') IS NOT NULL DROP TABLE TblName. Здається, багатослівність мови SQL пропорційна ціні.

6
@JeffreyKemp Ви б не думали, але я знову і знову виявляв, що Oracle робить все важким. Коли ви витрачаєте в середньому годину на одну незрозумілу помилку синтаксису або намагаєтесь зрозуміти, як зробити щось очевидне і просте в іншій базі даних (наприклад, умовно скинути елемент), і такі проблеми виникають щодня, це додається. Швидкий.
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;

Це для перевірки існування таблиці в поточній схемі. Щоб перевірити, чи існує дана таблиця в іншій схемі, вам доведеться використовувати all_tablesзамість user_tablesі додати умовуall_tables.owner = upper('schema_name')


34
+1 Це краще, тому що не розглядайте питання декодування виключень, щоб зрозуміти, що робити. Кодекс буде простішим у догляді та розумінні
daitangio

4
Погодьтеся з @daitangio - продуктивність, як правило, не коментує ремонтопридатність за допомогою сценаріїв розгортання після запуску.
pettys

1
Мені було б цікаво зрозуміти, чи відіграє тут роль неявна фіксація. Ви хочете, щоб SELECT і DROP знаходилися всередині однієї транзакції. [Очевидно, що ігнорується будь-який наступний DDL, який може бути виконаний. ]
Матвій

2
@Matthew, DROP - це команда DDL, тому вона спочатку видасть COMMIT, опустить таблицю, а потім видасть другу COMMIT. Звичайно, у цьому прикладі транзакції немає (оскільки видається лише запит), тому це не має ніякого значення; але якщо користувач раніше видав якийсь DML, він буде неявно зафіксований перед виконанням будь-якого DDL.
Джеффрі Кемп

28

Я шукав те саме, але закінчив написати процедуру, щоб допомогти мені:

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;

Сподіваюсь, це допомагає


Після того, як я створив вище проц. delobject, я намагався викликати його, видаючи наступний SQL. Але це не вийшло. delobject ('MyTable', 'TABLE'); Я отримую таку помилку -------------------------------- Помилка, починаючи з рядка 1 у команді: delobject ('MyTable ',' ТАБЛИЦЯ ') Повідомлення про помилки: Невідома команда
Шай

1
використовувати команду EXECUTE - EXECUTE DelObject ('MyTable', 'TABLE');
idanuda

13

просто хотів опублікувати повний код, який створить таблицю та видалить її, якщо вона вже існує за допомогою коду Джефрі (кудо йому, а не мені!).

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
Особисто я б поставив СТВОРИТУ ТАБЛИЦЮ окремим кроком, оскільки це не потрібно робити динамічно і не потребує обробника винятків.
Джеффрі Кемп

11

За допомогою SQL * PLUS ви також можете використовувати команду WHENEVER SQLERROR:

WHENEVER SQLERROR CONTINUE NONE
DROP TABLE TABLE_NAME;

WHENEVER SQLERROR EXIT SQL.SQLCODE
DROP TABLE TABLE_NAME;

З CONTINUE NONEповідомленнями про помилку, але сценарій продовжиться. Зі EXIT SQL.SQLCODEскриптом буде припинено у разі помилки.

дивіться також: КОЛИ ДОКУМЕНТИ SQLERROR Документи


3

В оракулі немає "DROP TABLE IF EXISTS", вам доведеться виконати операцію select.

спробуйте це (я не в синтаксисі oracle, тому якщо мої змінні є ify, будь ласка, пробачте мене:)

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

Я зробив спробу перекласти сценарій у синтаксис oracle.
Том

3
оголосити номер рахунку; почати вибирати count (*) у count з all_tables, де table_name = 'x'; якщо підрахунок> 0, то виконайте негайний 'drop table x'; закінчується, якщо; кінець; Ви не можете запустити DDL безпосередньо з блоку транзакцій, вам потрібно використовувати Execute.
Хб

Дуже дякую! Я не розумів, що синтаксис такий інший. Я ДІД знаю, що вам потрібно завернути всю справу на початку / в кінці, але я подумав, що вона запускається посеред іншого сценарію. Том: Я вирішив залишити свою версію, а не копіювати вашу, тому я не беру жодного голосу від вас, який, очевидно, має право відповідати.
Еріх

Я не думаю, що це складеться. Тут також може бути важливо включити власника схеми або ви можете отримати "true" для таблиці, яку ви не хотіли отримати з тим самим іменем.
Аллен

Ваша відповідь була замінена правильним синтаксисом Oracle через 10 хвилин після публікації.
jpmc26

3

Я вважаю за краще дотримуватися економічного рішення

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

Інший метод - визначити виняток, а потім лише вилучити цей виняток, даючи всім іншим розповсюджуватися.

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 "вже не існує" проти "існував, але більше не існує" :)
Джеффрі Кемп

2

Один із способів - використовувати DBMS_ASSERT.SQL_OBJECT_NAME :

Ця функція підтверджує, що рядок вхідного параметра є кваліфікованим ідентифікатором SQL існуючого об'єкта 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


2
Але це може бути не назва назви таблиці.
Джефрі Кемп

Можуть також бути різні таблиці, що використовують це ім'я в різних схемах.
Hybris95

0

На жаль ні, немає такого поняття, як крапля, якщо існує, або СТВОРИТИ, АКО НЕ ІСНУЄ

Ви можете написати сценарій plsql, щоб включити туди логіку.

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

Я не сильно в синтаксисі Oracle, але думаю, що сценарій @ Еріха був би таким.

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
Це навіть компілюється?
quillbreaker

0

Ви завжди могли самі зрозуміти помилку.

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

Вважається поганою практикою надмірне використання цього, подібне до порожніх catch () інших мов.

З повагою
К


1
Ні, ніколи не "виняток, коли інші
недійсні

0

Я вважаю за краще вказати таблицю та власника схеми.

Слідкуйте також за чутливістю до справ. (див. пункт "верхній" нижче).

Я кинув пару різних об'єктів, щоб показати, що їх можна використовувати в інших місцях, крім таблиць.

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

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;  

І приклад ТАБЛИЦІ:

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;

// Виконуючи цей код, перевіряється, чи існує таблиця, і пізніше вона створює таблицю макс. це просто працює в одній компіляції


2
Я вважаю, що це створює таблицю лише при помилці.
Рибне печиво

0

І якщо ви хочете зробити його повторним включенням та мінімізацією циклів падіння / створення, ви можете кешувати DDL за допомогою dbms_metadata.get_ddl і знову створити все за допомогою такої конструкції: 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; Це просто зразок, усередині має бути цикл із Тип DDL, ім'я та власник є змінними.


0

Такий блок може вам стати в нагоді.

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;  
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.