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;

ADDENDUM Для справки вот эквивалентные блоки для других типов объектов:

Последовательность

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
И для отбрасывания ПОЛЬЗОВАТЕЛЯ, 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
@ Матвей, DROP - это команда DDL, поэтому он сначала выполнит команду COMMIT, сбросит таблицу, а затем выполнит 2-й 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;

Надеюсь это поможет

Роберт Вабо
источник
После того как я создал выше proc. delobject, я попытался назвать его выдачей следующего SQL. Но это не сработало. delobject ('MyTable', 'TABLE'); Я получаю следующую ошибку -------------------------------- Ошибка запуска в строке 1 команды: delobject ('MyTable ',' TABLE ') Сообщение об ошибке: неизвестная команда
Shai
1
используйте команду EXECUTE - EXECUTE DelObject ('MyTable', 'TABLE');
Идануда
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
Лично я бы поместил CREATE TABLE в отдельный шаг, поскольку он не должен выполняться динамически и не требует обработчика исключений.
Джеффри Кемп
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 Docs

trunkc
источник
3

В оракуле нет DROP TABLE IF EXISTS, вам нужно будет выполнить оператор select.

попробуйте это (я не разбираюсь в синтаксисе оракула, поэтому, если мои переменные ify, пожалуйста, прости меня):

declare @count int
select @count=count(*) from all_tables where table_name='Table_name';
if @count>0
BEGIN
    DROP TABLE tableName;
END
Erich
источник
Я сделал попытку перевести скрипт в синтаксис оракула.
Том
3
объявить номер счета; начать выбирать count (*) в count из all_tables, где table_name = 'x'; если count> 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 Demo

Лукаш Шозда
источник
2
Но это может быть не имя таблицы.
Джеффри Кемп
Также могут быть разные таблицы, использующие это имя в разных схемах.
Hybris95
0

К сожалению, нет, нет такой вещи, как отбрасывание, если существует, или СОЗДАТЬ, ЕСЛИ НЕ СУЩЕСТВУЕТ

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

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

Я не сильно разбираюсь в синтаксисе Oracle, но я думаю, что сценарий @ Erich будет примерно таким.

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
Нет, никогда "исключение, когда другие тогда обнуляют"
чудо173
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;
/   
granadaCoder
источник
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;

// Делая этот код, проверяет, существует ли таблица, и позже она создает таблицу max. это просто работает в одной компиляции

Махеш Пандея
источник
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;  

источник