Введение в Oracle SQL

Обновление данных в таблицах

Разбить на страницы
Показывать лекцию целиком

Обновление данных в таблицах

Операции SQL по изменению данных в БД — это принадлежащие категории DML INSERT, UPDATE и DELETE плюс вторичная по отношению к ним MERGE. Все они множественные (вслед за реляционной моделью, где это неслучайно), то есть в общем рассчитаны одним действием изменить сразу множество строк таблицы. Исключение составляет формально однострочная разновидность оператора INSERT.

Операции INSERT, UPDATE, DELETE и MERGE унаследованы в Oracle от стандарта SQL. В реляционной теории операций INSERT, UPDATE и DELETE как таковых нет, но они легко моделируются существующими другими.

Добавление новых строк

Добавление одной строки

Пример:

INSERT INTO proj ( projno ) VALUES ( 20 );

В общем случае имена столбцов (до слова VALUES) и значения (после слова VALUES) приводятся списками с равными количествами элементов.

Имена столбцов со свойством NOT NULL и значения для них должны присутствовать в списках обязательно, если только для них не определены умолчательные значения или если значения не заносятся триггерными процедурами. При несоблюдении этих условий возникает ошибка времени исполнения.

В то время как порядок перечисления выражений должен копировать порядок перечисления имен столбцов перед словом VALUES, сам порядок имен столбцов может быть произвольным. Об этом напоминает синтаксис, и это еще одно наследие в SQL реляционной теории. Следующие предложения равносильны:

INSERT 
INTO proj ( projno, pname, bdate, budget ) 
VALUES
   ( 30, 'BETA', SYSDATE, 20000 )
;
INSERT 
INTO proj ( bdate, budget, projno, pname )
VALUES
   ( SYSDATE, 20000, 30, 'BETA' )
;

Для последней пары запросов SQL допускает и более краткую формулировку:

INSERT INTO proj VALUES ( 30, 'BETA', SYSDATE, 20000 );

Эта краткость может показаться соблазнительной, однако подобная формулировка недопустима в реляционной БД, так как предполагает наличие порядка столбцов в таблице SQL, в то время как в отношениях реляционной модели порядка атрибутов не существует. С практической точки зрения полагаться в запросе на порядок столбцов — ненадежно, ведь Oracle разрешает добавлять и удалять столбцы в таблице; так что со временем порядок может нарушиться и операция окажется некорректной. Кроме того, перечисляя выражения для подстановки значений в поля добавляемой строки, программист, не имея перед глазами списка имен полей, легко может ошибиться.

Пример использования подзапроса в выражении во фразе VALUES:

INSERT INTO emp
   ( empno, deptno ) 
VALUES
   ( 1111, ( SELECT deptno FROM dept WHERE loc = 'CHICAGO' ) )
;

На деле это всего лишь пример использования скалярного подзапроса в построении выражения.

Добавление строк, полученных подзапросом

Множественный вариант INSERT предполагает добавление одним оператором в таблицу сразу группы строк. Добавляемые строки определяются оператором SELECT.

Пример:

DELETE FROM dept_copy;
INSERT INTO dept_copy SELECT * FROM dept WHERE deptno IN ( 10, 20 );
INSERT 
INTO dept_copy ( loc, dname, deptno )
   SELECT loc, INITCAP ( dname ), deptno + 100 FROM dept
;
INSERT INTO dept_copy ( deptno ) SELECT deptno + 200 FROM dept
;

Замечания

  • На формулировку SELECT в предложении INSERT никаких нарочных ограничений не накладывается (в том числе допускаются GROUP BY, агрегатные функции и тому подобное)
  • Типы столбцов должны быть совместимы.
  • Операция вставки INSERT в общем случае множественная. Ее однострочный вариант можно полагать частным случаем множественного.

    Добавление строк одним оператором в несколько таблиц

    С версии 9 строки, полученные подзапросом, можно "раскидать" по нескольким таблицам единственным оператором INSERT, например:

    CREATE TABLE e1000 AS SELECT ename, sal FROM emp WHERE 1 = 2;
    CREATE TABLE e2000 AS SELECT ename, sal FROM emp WHERE 1 = 2;
    CREATE TABLE e3000 AS SELECT *          FROM emp WHERE 1 = 2;
    INSERT ALL
       WHEN sal > 3000 THEN INTO e3000 
       WHEN sal > 2000 THEN INTO e2000 ( ename ) VALUES ( ename )
       WHEN sal > 1000 THEN INTO e1000           VALUES ( ename, sal )
    SELECT * FROM emp WHERE job <> 'SALESMAN'
    ;
    

    Упражнение. Проверьте содержимое таблиц E1000, E2000 и E3000. Сохраните их данные, удалите таблицы и выполните пример заново, указав вместо INSERT ALL слова INSERT FIRST. Сравните новое содержимое таблиц с предыдущим.

    Последовательность проверок WHEN можно завершать проверкой ELSE.

    Фразы WHEN … THEN можно опускать, тогда будет выполняться безусловная вставка строк в таблицы, например:

    INSERT ALL
       WHEN comm IS NULL THEN INTO e1000 VALUES ( ename, sal )
       INTO e2000 ( ename ) VALUES ( ename )
       INTO e3000
    SELECT * FROM emp WHERE job <> 'SALESMAN'
    ;
    

    Вставка строк оператором INSERT разом в несколько таблиц эффективнее последовательности однотабличных INSERT, так как перемещает процедурную логику внутрь машины SQL СУБД. Это заметно при добавлении данных больших объемов. Кроме того, обновление таблиц выполняется применительно к одному состоянию БД, а потому логически не сводимо к последовательному выполнению команд INSERT.

    Вот пример использования формально многотабличного оператора INSERT для занесения данных в БД с попутным выполнением переформатирования (похожее переформатирование, но только в запросе SELECT, а не при добавлении в таблицы БД может с версии 11 выполняться конструкцией SELECT ... FROM … UNPIVOT …).

    Пример. Построим таблицу исходных данных:

    CREATE TABLE e1 AS SELECT ename, sal, comm FROM emp;
    

    Построим пустую таблицу для результата, где вместо двух полей SAL и COMM каждому сотруднику сопоставим отдельное поле с указанием вида платежа и отдельное поле для величины платежа:

    CREATE TABLE e2 ( ename, amount )
      AS 
      SELECT ename, sal FROM emp WHERE 1 = 2
    ;
    ALTER TABLE e2 
      ADD ( 
      payment VARCHAR2 ( 10 ) CHECK ( payment IN ( 'salary', 'commission' ) ) 
    );
    

    Теперь заполнение таблицы E2 можно выполнить следующим образом:

    INSERT ALL 
    WHEN sal  IS NOT NULL THEN INTO e2 VALUES ( ename, sal,  'salary' )
    WHEN comm IS NOT NULL THEN INTO e2 VALUES ( ename, comm, 'commission' )
    SELECT * FROM e1
    ;
    

    Результат:

    ENAME          AMOUNT PAYMENT
    ---------- ---------- ----------
    SMITH             800 salary
    ALLEN            1600 salary
    WARD             1250 salary
    JONES            2975 salary
    MARTIN           1250 salary
    BLAKE            2850 salary
    CLARK            2450 salary
    SCOTT            3000 salary
    KING             5000 salary
    TURNER           1500 salary
    ADAMS            1100 salary
    JAMES             950 salary
    FORD             3000 salary
    MILLER           1300 salary
    ALLEN             300 commission
    WARD              500 commission
    MARTIN           1400 commission
    TURNER              0 commission
    18 rows selected.
    

    Изменение существующих значений полей строк

    Логически операция UPDATE вторична, так как сводима к последовательности DELETE и INSERT, но в системах SQL технически не отрабатывается. (Строго говоря, это не совсем так. Технически Oracle способна в некоторых случаях именно удалить запись в БД, представляющую строку таблицы, и добавить вместо нее новую, но это не единственный способ осуществления операции.) Она используется ради удобства применения. Любопытно, что если изменяется поле индексированного столбца и заодно с данными таблицы изменяется индекс, его изменение осуществляется ровно последовательным удалением из индекса старого значения и добавлением нового.

    Примеры употребления:

    UPDATE proj SET pname = 'GAMMA' WHERE projno = 15;
    UPDATE proj 
    SET 
      pname  = 'GAMMA'
    , budget = budget * 1.05 
    WHERE projno = 15
    ;
    

    Поскольку речь идет об изменении значений полей уже существующих строк, в предложении UPDATE присутствует фраза WHERE, уточняющая множество строк для внесения изменения. Правила записи фразы WHERE те же, что и для предложения SELECT. Как и в SELECT, если в предложении UPDATE фраза WHERE не указана, изменение коснется всех строк источника данных.

    Следующая форма UPDATE предполагает указание списка проставляемых в таблицу значений только однострочным подзапросом:

    UPDATE proj 
    SET
      ( pname, budget ) =
      ( SELECT pname || '1', budget * 0.95 FROM proj WHERE projno <= 10 )
    ;
    

    Упражнение. Проверьте, каков будет результат, если:

  • вложенный SELECT вернет более одной строки;
  • вложенный SELECT не вернет не одной строки;
  • столбец BUDGET будет не заполнен (NULL);
  • столбец BUDGET будет частично заполнен.
  • Еще пример формулирования предложения UPDATE:

    UPDATE proj 
    SET budget =
          CASE 
             WHEN pname = 'GAMMA' THEN budget
             WHEN budget IS NULL  THEN 0
          ELSE NULL
          END
    ;
    

    На деле это всего лишь пример использования оператора CASE в построении выражения.

    Операция UPDATE изменения существующих значений — множественная в силу своей формулировки.

    Общие свойства INSERT и UPDATE

    Операции INSERT и UPDATE роднит то, что обе по сути выполняют присвоение значений. Далее говорится о связанных с этим общими их свойствами.

    Использование умолчательных значений в INSERT и UPDATE

    Выражение для значения поля добавляемой или изменяемой строки можно заменить словом DEFAULT (разрешено в SQL:1999). В случае, когда в определении столбца присутствует выражение для вычисления умолчательного значения, именно оно и будет вычислено, и результат занесен в поле. Если умолчательное значение столбца явно не задавалось, указание слова DEFAULT в качестве значения равносильно указанию NULL (можно полагать, что если в определении столбца конструкция DEFAULT явно не указана, молчаливо предполагается DEFAULT NULL).

    Пример:

    CREATE TABLE t ( r NUMBER, a NUMBER, b NUMBER DEFAULT 123 )
    ;
    INSERT INTO t ( r, a, b ) VALUES ( 1, 1, 2 );
    INSERT INTO t ( r, a, b ) VALUES ( 2, NULL, NULL );
    INSERT INTO t ( r, a, b ) VALUES ( 3, DEFAULT, DEFAULT );
    INSERT INTO t ( r       ) VALUES ( 4 );
    INSERT INTO t             VALUES ( 5, DEFAULT, DEFAULT );
    Проверка:
    SQL> SELECT * FROM t;
             R          A          B
    ---------- ---------- ----------
             1          1          2
             2
             3                   123
             4                   123
             5                   123
    

    Аномалия проверки занесенного в БД значения

    Необычное поведение традиционных операций сравнения (=, <> и др.) с NULL влечет непривычный эффект проверки добавленного в БД командами INSERT и UPDATE значения. Обратимся снова к таблице T из предыдущего примера:

    SQL> VARIABLE n NUMBER
    SQL> INSERT INTO t ( r, a ) VALUES ( 6, :n );
    1 row created.
    SQL> SELECT * FROM t WHERE r = 6 AND a = :n;
    no rows selected
    SQL> SELECT COUNT ( * ) FROM t WHERE r = 6;
      COUNT(*)
    ----------
             1
    

    Такое поведение особенно неприятно в программе, и для привлечения внимания именно к этому контексту употребления вместо явного упоминания NULL в примере была применена переменная SQL*Plus. При простом объявлении переменной N она не получила никакого значения, так что появление NULL в запросах оказалось скрытым с ее помощью.

    Можно вспомнить, что корни такого поведения Oracle уходят в стандарт SQL.

    Удаление строк из таблицы

    Выборочное удаление

    Основной оператор для удаления строк из таблицы — DELETE.

    Примеры:

    DELETE FROM proj WHERE projno = 16;
    DELETE FROM proj WHERE pname IS NULL;
    DELETE FROM dept_copy;
    

    Поскольку удаляться могут только существующие строки, ради их указания в предложении DELETE присутствует в общем случае фраза WHERE.

    Операция удаления строк DELETE — множественная в силу своей формулировки.

    Вариант полного удаления

    Вместо полного удаления строк командой DELETE (в отсутствии фразы WHERE), например, вместо

    DELETE FROM dept_copy;
    

    можно употреблять более быструю команду TRUNCATE TABLE:

    TRUNCATE TABLE dept_copy;
    

    Особенности TRUNCATE TABLE:

  • DDL-операция невосстанавливаемая операция;
  • быстро выполняется (ощутимо на больших таблицах), поскольку строки удаляются как результат укорачивания структуры хранения данных таблицы в БД ("сегмента"), а не поштучно, как при DELETE.
  • Если очищенную от строк таблицу предполагается впоследствии снова заполнять, время последующего заполнения сократится, если выдать:

    TRUNCATE TABLE dept_copy REUSE STORAGE;
    

    В этом случае строки будут полагаться удаленными, а структура хранения данных таблицы в БД останется внешне неизменной.

    Объединение INSERT, UPDATE и DELETE в одном операторе

    В версии СУБД 9 появилась команда MERGE, позволяющая либо изменить существующие строки какой-нибудь таблицы, либо добавить — в зависимости от сформулированного условия. Условие формулируется на основе соединения целевой таблицы с другой (в общем случае — с источником данных, в качестве которого, однако, помимо обычной таблицы могут выступать еще представление данных, таблицы с внешним хранением с временным хранением данных, а также подзапрос).

    Заполним таблицу BONUS данными о сотрудниках, положим, имеющих комиссионные:

    INSERT INTO bonus 
       SELECT ename, job, sal, comm
       FROM   emp
       WHERE  comm IS NOT NULL
    ;
    

    Теперь обновим BONUS данными, "поступившими" из таблицы EMP. Если сотрудник из EMP уже есть в BONUS, повысим ему зарплату, а если нет — добавим к списку BONUS:

     
    MERGE
    INTO  bonus b
    USING emp e
    ON  ( b.ename = e.ename )
    WHEN MATCHED THEN
    UPDATE SET sal = sal * 10
    WHEN NOT MATCHED THEN
    INSERT VALUES ( e.ename, e.job, e.sal, e.comm )
    ;
    

    Одна из двух проверок формально может и отсутствовать, что позволяет всего одной командой SQL просто добавить записи, отсутствующие в однотипной таблице.

    В версии 10 фраза во фразе WHEN MATCHED можно дополнительно указать DELETE, например:

    MERGE 
    INTO  bonus b 
    USING emp e 
    ON  ( b.ename = e.ename )
    WHEN MATCHED THEN 
         UPDATE SET sal = sal / 10
         DELETE WHERE sal < 1000
    ;
    

    (Вернули BONUS в состояние до первого оператора MERGE).

    Назначение операции MERGE — ускорить обновление больших таблиц. Обратите внимание, что обновление выполняется применительно к одному состоянию БД и поэтому логически несводимо к последовательному выполнению команд INSERT, UPDATE и, возможно, DELETE.

    Целостность выполнения операторов обновления данных и реакция на ошибки

    В процессе выполнения множественных операторов обновления данных таблиц изменение отдельной строки может вызвать ошибку — например, вылиться в попытку нарушить ограничение целостности. В таких случаях операция обновления прекращается, а изменения, успевшие произойти до возникновения проблемы, отменяются. Иными словами, операции DML обновления данных либо выполняются целиком, либо, в конечном счете, не выполняются вовсе.

    Реакция на ошибки в процессе исполнения

    Традиционная реакция на ошибки в процессе выполнения изменяющего данные оператора ("все или ничего") логически оправдана, но не всегда практична в случае больших объемов данных. В версии 10.2 введена возможность не отказываться от исполнения огульно, а вместо этого запоминать возникающие на отдельных строках ошибки в специально подготовленной таблице с целью последующего разбирательства. Специальную таблицу можно завести с помощью особой системной процедуры. Пример ее создания для таблицы EMP и дальнейшего употребления приводится ниже.

    SQL> EXECUTE DBMS_ERRLOG.CREATE_ERROR_LOG ( 'EMP', 'ERR_EMP' )
    PL/SQL procedure successfully completed.
    SQL> INSERT INTO emp ( empno ) VALUES ( 1111 );
    1 row created.
    SQL> /
    INSERT INTO emp ( empno ) VALUES ( 1111 )
    *
    ERROR at line 1:
    ORA-00001: unique constraint (SCOTT.PK_EMP) violated
    SQL> INSERT INTO emp ( empno ) VALUES ( 1111 ) 
      2  LOG ERRORS INTO err_emp ( 'today error' ) REJECT LIMIT 10;
    0 rows created.
    SQL> COLUMN ora_err_mesg$ FORMAT A45 WORD
    SQL> COLUMN ora_err_tag$ FORMAT A12
    SQL> COLUMN err# FORMAT 99999
    SQL> COLUMN empno FORMAT A6
    SQL> SELECT ora_err_number$ err#, ora_err_mesg$, ora_err_tag$, empno 
    SQL> FROM err_emp;
      ERR# ORA_ERR_MESG$                                 ORA_ERR_TAG$ EMPNO
    ------ --------------------------------------------- ------------ ------
         1 ORA-00001: unique constraint (SCOTT.PK_EMP)   today error  1111
           violated
    

    Обратите внимание, что второй оператор INSERT строку не добавляет (правило первичного ключа нарушить нельзя), но и ошибку в программу не возвращает.

    Запрет на изменение данных в таблице

    С версии 11 изменение данных в таблице можно запретить, переведя таблицу в состояние READ ONLY:

    ALTER TABLE emp READ ONLY;
    

    До этой версии запретить изменения можно было только одновременно во всех объектах, хранящих свои данные в конкретном табличном пространстве.

    Фиксация или отказ от изменений в БД

    Для сеанса, выдающего команды DML на изменения данных, СУБД создает видимость, что они выполняются сразу в БД. Выполнив тут же запрос, пользователь увидит, будто данные изменились. На деле же они попадут в базу только после выдачи сеансом специальной команды фиксации изменений. Только после этого они станут видны прочим сеансам.

    Все изменения со стороны индивидуальных команд DML заносятся Oracle в БД только группами, в рамках транзакции, по завершению транзакции. Команды завершения текущей транзакции:

    COMMIT [WORK];
    ROLLBACK [WORK];
    

    Завершение транзакции с фиксацией изменений, внесенных операторами DML, происходит только по выдаче (а) команды COMMIT или (б) оператора DDL (скрытым образом завершающего свои действия по изменению таблиц словаря-справочника той же командой COMMIT).

    Завершение транзакции с отменой изменений, внесенных операторами DML, происходит только по выдаче команды ROLLBACK, которая или явно выдается программистом, или, в некоторых случаях, неявно порождается самой СУБД (например, по результату аварийного останова работы программы из-за неперехваченной исключительной ситуации).

    Упражнение. Вставьте запись в имеющуюся таблицу — откатите изменения. Вставьте запись — зафиксируйте. Создайте таблицу, вставьте запись — откатите изменения. Сохранилась ли таблица и ее данные? Создайте заполненную таблицу предложением CREATE TABLE … AS SELECT …. Откатите изменения.

    Oracle нумерует все транзакции сквозным образом постоянно растущими номерами, называемыми SCN (System Change Number, номер изменения системы). Каждая команда COMMIT переводит БД в новое состояние, получающее номер зафиксированной транзакции. Данные БД в более ранних состояниях становятся после этого доступны только средствами (а) восстановления по резервным копиям и (б) "быстрого" восстановления (flashback).

    В версии 11 Oracle разрешила не только аннулировать командой ROLLBACK изменения, совершавшиеся в рамках завершаемой транзакции, но также и отменять изменения, выполнявшиеся по очереди несколькими последними транзакциями (завершенными ранее командой COMMIT). Но делается это уже не операцией SQL, а программно, средствами системного пакета DBMS_FLASHBACK.

    Данные о номере последней транзакции, изменившей строку таблицы

    В версии 10 стало возможным с помощью системной переменной ("псевдостолбца") ORA_ROWSCN узнать SCN транзакции, внесшей последнее изменение в строку. Если ничего не предпринимать специально, то этот номер будет приближенный и соответствовать фактически не строке, а блоку, в котором хранится в БД строка. (Сама возможность хранить вместе со строкой SCN ее последней правки существовала и раньше и использовалась для параллельной репликации, но смотреть этот номер в программе было нельзя).

    Пример:

    SQL> CREATE TABLE dscn AS SELECT * FROM dept;
    Table created.
    SQL> UPDATE dscn SET dname = LOWER ( dname ) WHERE ROWNUM <= 2;
    2 rows updated.
    SQL> SELECT dname, ora_rowscn FROM dscn;
    DNAME          ORA_ROWSCN
    -------------- ----------
    accounting        3812962
    research          3812962
    SALES             3812962
    OPERATIONS        3812962
    SQL> COMMIT;
    Commit complete.
    SQL> SELECT dname, ora_rowscn FROM dscn;
    DNAME          ORA_ROWSCN
    -------------- ----------
    accounting        3812999
    research          3812999
    SALES             3812999
    OPERATIONS        3812999
    

    Однако если создать таблицу с особым указанием, в ней появится скрытый столбец (длиною 6 байт), рассчитанный на хранение номера SCN индивидуально для каждой строки:

    SQL> DROP TABLE dscn;
    Table dropped.
    SQL> CREATE TABLE dscn ROWDEPENDENCIES AS SELECT * FROM dept;
    Table created.
    SQL> UPDATE dscn SET dname = LOWER ( dname ) WHERE ROWNUM <= 2;
    2 rows updated.
    SQL> SELECT dname, ora_rowscn FROM dscn;
    DNAME          ORA_ROWSCN
    -------------- ----------
    accounting
    research
    SALES             3814027
    OPERATIONS        3814027
    SQL> COMMIT;
    Commit complete.
    SQL> SELECT dname, ora_rowscn FROM dscn;
    DNAME          ORA_ROWSCN
    -------------- ----------
    accounting        3814035
    research          3814035
    SALES             3814027
    OPERATIONS        3814027
    

    Эти данные можно использовать в программе, чтобы определить, изменялась ли строка с какого-то времени. Перевести SCN в астрономическое время (приближенно) можно обращением к специальной функции, например:

    SELECT dname, SCN_TO_TIMESTAMP ( ora_rowscn ) FROM dscn;
    

    Обращение с прошлыми данными после внесения изменений

    Традиционно все существующие СУБД создавались как средства моделирования текущего состояния предметной области. Выдача COMMIT переводит БД в новое состояние, и предыдущие данные оказываются потерянными. Такое поведение проще программировать разработчикам СУБД, но оно не всегда удобно пользователям. В частности:

  • для восстановления данных, потерянных в результате непродуманной выдачи COMMIT, пусть даже совсем недавно, приходилось довольствоваться восстановлением по резервной копии БД, что долго и требует наличия собственно резервной копии;
  • нередко возникающую потребность моделировать историю изменения данных приходится имитировать разработчику приложения на свое усмотрение.
  • В версии 9 в Oracle открылись возможности получать от СУБД значения данных таблицы по состоянию на прошлый момент, невзирая на осуществлявшиеся за это время операции фиксации транзакций. В версии 10 эти возможности получили свое развитие. Техническая основа у них разная: использование временно сохранившихся данных в табличном пространстве типа UNDO; использование свободного места в табличном пространстве БД с данными таблицы; особый способ журнализации данных. Отсюда проистекают различия в доступных сроках давности для восстановления в разных случаях.

    Обращение к прошлым значениям данных в таблице

    Для (быстрого) запроса к прежним данным ссылку на таблицу во фразе FROM предложения SELECT следует сопроводить указанием конструкции AS OF.

    Пример (выполнить в качестве упражнения последовательно):

    DELETE FROM emp;
    COMMIT;
    SELECT * FROM emp;
    INSERT INTO emp 
     SELECT *
     FROM   emp AS OF TIMESTAMP ( SYSTIMESTAMP - INTERVAL '1' MINUTE )
    ; 
    SELECT * FROM emp;
    

    (Синтаксис допускает употребление скобок в запросе выше, но не требует этого).

    Момент для восстановления можно еще указать в терминах номера изменений данных в БД (System Change Number, SCN).

    Подобное извлечение старых значений возможно только за период, когда к таблице не применялись команды DDL.

    Давность воспроизводимых данных ограничивается размером свободного места в табличном пространстве UNDO, которое определяется (а) интенсивностью изменений БД и (б) полным размером пространства.

    В версии 10 появилась возможность вместо AS OF уточнять имя таблицы во фразе FROM конструкцией VERSIONS BETWEEN, позволяющей извлекать из БД историю изменения строк определенной давности. Следующим примером можно продолжить приводившийся только что код:

    COLUMN versions_endtime FORMAT A22
    COLUMN versions_starttime FORMAT A22
    SELECT 
      empno
    , sal
    , versions_starttime
    , versions_endtime
    , versions_xid
    , versions_operation 
    FROM
    emp VERSIONS BETWEEN TIMESTAMP MINVALUE AND MAXVALUE
    ;
    

    Прочие поля строк таблицы EMP в этом запросе не выданы из экономии места.

    Упражнение: Измените несколько раз зарплаты разным сотрудникам и просмотрите историю изменений.

    Восстановление данных существующих таблиц, ранее удаленных таблиц и всей БД

    С версии 10 открылась возможность одной командой восстановить строки таблицы на момент в прошлом. Это позволяет вместо вышеуказанного INSERT … SELECT … AS OF записать

    FLASHBACK TABLE emp 
    TO TIMESTAMP ( SYSTIMESTAMP - INTERVAL '1' MINUTE )
    ;
    

    Тем не менее:

  • эти две команды не равносильны, когда перед восстановлением строк таблица была непуста;
  • чтобы восстановление строк таблицы командой FLASHBACK было возможным, мы должны разрешить системе изменять физические адреса строк таблицы. Например, выдать: ALTER TABLE emp ENABLE ROW MOVEMENT;
  • Для полного восстановления таблицы, ранее удалявшейся, команда FLASHBACK выглядит иначе. Вариант простого восстановления (из мусорной корзины):

    FLASHBACK TABLE emp TO BEFORE DROP;
    

    Вариант с переименованием (если с момента удаления таблица с именем EMP создавалась повторно):

    FLASHBACK TABLE emp TO BEFORE DROP RENAME TO emp_old;
    

    Если таблица переименовывалась несколько раз, вместо имени таблицы можно подставить внутреннее название для одной из старых версий таблицы, полученное из системной таблицы USER_RECYCLEBIN.

    Технически такая возможность достигается сохранением старой структуры хранения ("сегмента") таблицы в табличном пространстве при выполнении DROP TABLE, что определяет границы применимости такого подхода.

    Если же база данных работает в специальном режиме flashback, команда FLASHBACK позволяет восстановливать ее целиком:

    FLASHBACK DATABASE TO TIMESTAMP SYSTIMESTAMP - INTERVAL '1' HOUR;
    
    Страницы:

    Обновление данных в таблицах

    Операции SQL по изменению данных в БД — это принадлежащие категории DML INSERT, UPDATE и DELETE плюс вторичная по отношению к ним MERGE. Все они множественные (вслед за реляционной моделью, где это неслучайно), то есть в общем рассчитаны одним действием изменить сразу множество строк таблицы. Исключение составляет формально однострочная разновидность оператора INSERT.

    Операции INSERT, UPDATE, DELETE и MERGE унаследованы в Oracle от стандарта SQL. В реляционной теории операций INSERT, UPDATE и DELETE как таковых нет, но они легко моделируются существующими другими.

    Добавление новых строк

    Добавление одной строки

    Пример:

    INSERT INTO proj ( projno ) VALUES ( 20 );
    

    В общем случае имена столбцов (до слова VALUES) и значения (после слова VALUES) приводятся списками с равными количествами элементов.

    Имена столбцов со свойством NOT NULL и значения для них должны присутствовать в списках обязательно, если только для них не определены умолчательные значения или если значения не заносятся триггерными процедурами. При несоблюдении этих условий возникает ошибка времени исполнения.

    В то время как порядок перечисления выражений должен копировать порядок перечисления имен столбцов перед словом VALUES, сам порядок имен столбцов может быть произвольным. Об этом напоминает синтаксис, и это еще одно наследие в SQL реляционной теории. Следующие предложения равносильны:

    INSERT 
    INTO proj ( projno, pname, bdate, budget ) 
    VALUES
       ( 30, 'BETA', SYSDATE, 20000 )
    ;
    INSERT 
    INTO proj ( bdate, budget, projno, pname )
    VALUES
       ( SYSDATE, 20000, 30, 'BETA' )
    ;
    

    Для последней пары запросов SQL допускает и более краткую формулировку:

    INSERT INTO proj VALUES ( 30, 'BETA', SYSDATE, 20000 );
    

    Эта краткость может показаться соблазнительной, однако подобная формулировка недопустима в реляционной БД, так как предполагает наличие порядка столбцов в таблице SQL, в то время как в отношениях реляционной модели порядка атрибутов не существует. С практической точки зрения полагаться в запросе на порядок столбцов — ненадежно, ведь Oracle разрешает добавлять и удалять столбцы в таблице; так что со временем порядок может нарушиться и операция окажется некорректной. Кроме того, перечисляя выражения для подстановки значений в поля добавляемой строки, программист, не имея перед глазами списка имен полей, легко может ошибиться.

    Пример использования подзапроса в выражении во фразе VALUES:

    INSERT INTO emp
       ( empno, deptno ) 
    VALUES
       ( 1111, ( SELECT deptno FROM dept WHERE loc = 'CHICAGO' ) )
    ;
    

    На деле это всего лишь пример использования скалярного подзапроса в построении выражения.

    Добавление строк, полученных подзапросом

    Множественный вариант INSERT предполагает добавление одним оператором в таблицу сразу группы строк. Добавляемые строки определяются оператором SELECT.

    Пример:

    DELETE FROM dept_copy;
    INSERT INTO dept_copy SELECT * FROM dept WHERE deptno IN ( 10, 20 );
    INSERT 
    INTO dept_copy ( loc, dname, deptno )
       SELECT loc, INITCAP ( dname ), deptno + 100 FROM dept
    ;
    INSERT INTO dept_copy ( deptno ) SELECT deptno + 200 FROM dept
    ;
    

    Замечания

  • На формулировку SELECT в предложении INSERT никаких нарочных ограничений не накладывается (в том числе допускаются GROUP BY, агрегатные функции и тому подобное)
  • Типы столбцов должны быть совместимы.
  • Операция вставки INSERT в общем случае множественная. Ее однострочный вариант можно полагать частным случаем множественного.

    Добавление строк одним оператором в несколько таблиц

    С версии 9 строки, полученные подзапросом, можно "раскидать" по нескольким таблицам единственным оператором INSERT, например:

    CREATE TABLE e1000 AS SELECT ename, sal FROM emp WHERE 1 = 2;
    CREATE TABLE e2000 AS SELECT ename, sal FROM emp WHERE 1 = 2;
    CREATE TABLE e3000 AS SELECT *          FROM emp WHERE 1 = 2;
    INSERT ALL
       WHEN sal > 3000 THEN INTO e3000 
       WHEN sal > 2000 THEN INTO e2000 ( ename ) VALUES ( ename )
       WHEN sal > 1000 THEN INTO e1000           VALUES ( ename, sal )
    SELECT * FROM emp WHERE job <> 'SALESMAN'
    ;
    

    Упражнение. Проверьте содержимое таблиц E1000, E2000 и E3000. Сохраните их данные, удалите таблицы и выполните пример заново, указав вместо INSERT ALL слова INSERT FIRST. Сравните новое содержимое таблиц с предыдущим.

    Последовательность проверок WHEN можно завершать проверкой ELSE.

    Фразы WHEN … THEN можно опускать, тогда будет выполняться безусловная вставка строк в таблицы, например:

    INSERT ALL
       WHEN comm IS NULL THEN INTO e1000 VALUES ( ename, sal )
       INTO e2000 ( ename ) VALUES ( ename )
       INTO e3000
    SELECT * FROM emp WHERE job <> 'SALESMAN'
    ;
    

    Вставка строк оператором INSERT разом в несколько таблиц эффективнее последовательности однотабличных INSERT, так как перемещает процедурную логику внутрь машины SQL СУБД. Это заметно при добавлении данных больших объемов. Кроме того, обновление таблиц выполняется применительно к одному состоянию БД, а потому логически не сводимо к последовательному выполнению команд INSERT.

    Вот пример использования формально многотабличного оператора INSERT для занесения данных в БД с попутным выполнением переформатирования (похожее переформатирование, но только в запросе SELECT, а не при добавлении в таблицы БД может с версии 11 выполняться конструкцией SELECT ... FROM … UNPIVOT …).

    Пример. Построим таблицу исходных данных:

    CREATE TABLE e1 AS SELECT ename, sal, comm FROM emp;
    

    Построим пустую таблицу для результата, где вместо двух полей SAL и COMM каждому сотруднику сопоставим отдельное поле с указанием вида платежа и отдельное поле для величины платежа:

    CREATE TABLE e2 ( ename, amount )
      AS 
      SELECT ename, sal FROM emp WHERE 1 = 2
    ;
    ALTER TABLE e2 
      ADD ( 
      payment VARCHAR2 ( 10 ) CHECK ( payment IN ( 'salary', 'commission' ) ) 
    );
    

    Теперь заполнение таблицы E2 можно выполнить следующим образом:

    INSERT ALL 
    WHEN sal  IS NOT NULL THEN INTO e2 VALUES ( ename, sal,  'salary' )
    WHEN comm IS NOT NULL THEN INTO e2 VALUES ( ename, comm, 'commission' )
    SELECT * FROM e1
    ;
    

    Результат:

    ENAME          AMOUNT PAYMENT
    ---------- ---------- ----------
    SMITH             800 salary
    ALLEN            1600 salary
    WARD             1250 salary
    JONES            2975 salary
    MARTIN           1250 salary
    BLAKE            2850 salary
    CLARK            2450 salary
    SCOTT            3000 salary
    KING             5000 salary
    TURNER           1500 salary
    ADAMS            1100 salary
    JAMES             950 salary
    FORD             3000 salary
    MILLER           1300 salary
    ALLEN             300 commission
    WARD              500 commission
    MARTIN           1400 commission
    TURNER              0 commission
    18 rows selected.
    

    Изменение существующих значений полей строк

    Логически операция UPDATE вторична, так как сводима к последовательности DELETE и INSERT, но в системах SQL технически не отрабатывается. (Строго говоря, это не совсем так. Технически Oracle способна в некоторых случаях именно удалить запись в БД, представляющую строку таблицы, и добавить вместо нее новую, но это не единственный способ осуществления операции.) Она используется ради удобства применения. Любопытно, что если изменяется поле индексированного столбца и заодно с данными таблицы изменяется индекс, его изменение осуществляется ровно последовательным удалением из индекса старого значения и добавлением нового.

    Примеры употребления:

    UPDATE proj SET pname = 'GAMMA' WHERE projno = 15;
    UPDATE proj 
    SET 
      pname  = 'GAMMA'
    , budget = budget * 1.05 
    WHERE projno = 15
    ;
    

    Поскольку речь идет об изменении значений полей уже существующих строк, в предложении UPDATE присутствует фраза WHERE, уточняющая множество строк для внесения изменения. Правила записи фразы WHERE те же, что и для предложения SELECT. Как и в SELECT, если в предложении UPDATE фраза WHERE не указана, изменение коснется всех строк источника данных.

    Следующая форма UPDATE предполагает указание списка проставляемых в таблицу значений только однострочным подзапросом:

    UPDATE proj 
    SET
      ( pname, budget ) =
      ( SELECT pname || '1', budget * 0.95 FROM proj WHERE projno <= 10 )
    ;
    

    Упражнение. Проверьте, каков будет результат, если:

  • вложенный SELECT вернет более одной строки;
  • вложенный SELECT не вернет не одной строки;
  • столбец BUDGET будет не заполнен (NULL);
  • столбец BUDGET будет частично заполнен.
  • Еще пример формулирования предложения UPDATE:

    UPDATE proj 
    SET budget =
          CASE 
             WHEN pname = 'GAMMA' THEN budget
             WHEN budget IS NULL  THEN 0
          ELSE NULL
          END
    ;
    

    На деле это всего лишь пример использования оператора CASE в построении выражения.

    Операция UPDATE изменения существующих значений — множественная в силу своей формулировки.

    Общие свойства INSERT и UPDATE

    Операции INSERT и UPDATE роднит то, что обе по сути выполняют присвоение значений. Далее говорится о связанных с этим общими их свойствами.

    Использование умолчательных значений в INSERT и UPDATE

    Выражение для значения поля добавляемой или изменяемой строки можно заменить словом DEFAULT (разрешено в SQL:1999). В случае, когда в определении столбца присутствует выражение для вычисления умолчательного значения, именно оно и будет вычислено, и результат занесен в поле. Если умолчательное значение столбца явно не задавалось, указание слова DEFAULT в качестве значения равносильно указанию NULL (можно полагать, что если в определении столбца конструкция DEFAULT явно не указана, молчаливо предполагается DEFAULT NULL).

    Пример:

    CREATE TABLE t ( r NUMBER, a NUMBER, b NUMBER DEFAULT 123 )
    ;
    INSERT INTO t ( r, a, b ) VALUES ( 1, 1, 2 );
    INSERT INTO t ( r, a, b ) VALUES ( 2, NULL, NULL );
    INSERT INTO t ( r, a, b ) VALUES ( 3, DEFAULT, DEFAULT );
    INSERT INTO t ( r       ) VALUES ( 4 );
    INSERT INTO t             VALUES ( 5, DEFAULT, DEFAULT );
    Проверка:
    SQL> SELECT * FROM t;
             R          A          B
    ---------- ---------- ----------
             1          1          2
             2
             3                   123
             4                   123
             5                   123
    

    Аномалия проверки занесенного в БД значения

    Необычное поведение традиционных операций сравнения (=, <> и др.) с NULL влечет непривычный эффект проверки добавленного в БД командами INSERT и UPDATE значения. Обратимся снова к таблице T из предыдущего примера:

    SQL> VARIABLE n NUMBER
    SQL> INSERT INTO t ( r, a ) VALUES ( 6, :n );
    1 row created.
    SQL> SELECT * FROM t WHERE r = 6 AND a = :n;
    no rows selected
    SQL> SELECT COUNT ( * ) FROM t WHERE r = 6;
      COUNT(*)
    ----------
             1
    

    Такое поведение особенно неприятно в программе, и для привлечения внимания именно к этому контексту употребления вместо явного упоминания NULL в примере была применена переменная SQL*Plus. При простом объявлении переменной N она не получила никакого значения, так что появление NULL в запросах оказалось скрытым с ее помощью.

    Можно вспомнить, что корни такого поведения Oracle уходят в стандарт SQL.

    Удаление строк из таблицы

    Выборочное удаление

    Основной оператор для удаления строк из таблицы — DELETE.

    Примеры:

    DELETE FROM proj WHERE projno = 16;
    DELETE FROM proj WHERE pname IS NULL;
    DELETE FROM dept_copy;
    

    Поскольку удаляться могут только существующие строки, ради их указания в предложении DELETE присутствует в общем случае фраза WHERE.

    Операция удаления строк DELETE — множественная в силу своей формулировки.

    Вариант полного удаления

    Вместо полного удаления строк командой DELETE (в отсутствии фразы WHERE), например, вместо

    DELETE FROM dept_copy;
    

    можно употреблять более быструю команду TRUNCATE TABLE:

    TRUNCATE TABLE dept_copy;
    

    Особенности TRUNCATE TABLE:

  • DDL-операция невосстанавливаемая операция;
  • быстро выполняется (ощутимо на больших таблицах), поскольку строки удаляются как результат укорачивания структуры хранения данных таблицы в БД ("сегмента"), а не поштучно, как при DELETE.
  • Если очищенную от строк таблицу предполагается впоследствии снова заполнять, время последующего заполнения сократится, если выдать:

    TRUNCATE TABLE dept_copy REUSE STORAGE;
    

    В этом случае строки будут полагаться удаленными, а структура хранения данных таблицы в БД останется внешне неизменной.

    Объединение INSERT, UPDATE и DELETE в одном операторе

    В версии СУБД 9 появилась команда MERGE, позволяющая либо изменить существующие строки какой-нибудь таблицы, либо добавить — в зависимости от сформулированного условия. Условие формулируется на основе соединения целевой таблицы с другой (в общем случае — с источником данных, в качестве которого, однако, помимо обычной таблицы могут выступать еще представление данных, таблицы с внешним хранением с временным хранением данных, а также подзапрос).

    Заполним таблицу BONUS данными о сотрудниках, положим, имеющих комиссионные:

    INSERT INTO bonus 
       SELECT ename, job, sal, comm
       FROM   emp
       WHERE  comm IS NOT NULL
    ;
    

    Теперь обновим BONUS данными, "поступившими" из таблицы EMP. Если сотрудник из EMP уже есть в BONUS, повысим ему зарплату, а если нет — добавим к списку BONUS:

     
    MERGE
    INTO  bonus b
    USING emp e
    ON  ( b.ename = e.ename )
    WHEN MATCHED THEN
    UPDATE SET sal = sal * 10
    WHEN NOT MATCHED THEN
    INSERT VALUES ( e.ename, e.job, e.sal, e.comm )
    ;
    

    Одна из двух проверок формально может и отсутствовать, что позволяет всего одной командой SQL просто добавить записи, отсутствующие в однотипной таблице.

    В версии 10 фраза во фразе WHEN MATCHED можно дополнительно указать DELETE, например:

    MERGE 
    INTO  bonus b 
    USING emp e 
    ON  ( b.ename = e.ename )
    WHEN MATCHED THEN 
         UPDATE SET sal = sal / 10
         DELETE WHERE sal < 1000
    ;
    

    (Вернули BONUS в состояние до первого оператора MERGE).

    Назначение операции MERGE — ускорить обновление больших таблиц. Обратите внимание, что обновление выполняется применительно к одному состоянию БД и поэтому логически несводимо к последовательному выполнению команд INSERT, UPDATE и, возможно, DELETE.

    Целостность выполнения операторов обновления данных и реакция на ошибки

    В процессе выполнения множественных операторов обновления данных таблиц изменение отдельной строки может вызвать ошибку — например, вылиться в попытку нарушить ограничение целостности. В таких случаях операция обновления прекращается, а изменения, успевшие произойти до возникновения проблемы, отменяются. Иными словами, операции DML обновления данных либо выполняются целиком, либо, в конечном счете, не выполняются вовсе.

    Реакция на ошибки в процессе исполнения

    Традиционная реакция на ошибки в процессе выполнения изменяющего данные оператора ("все или ничего") логически оправдана, но не всегда практична в случае больших объемов данных. В версии 10.2 введена возможность не отказываться от исполнения огульно, а вместо этого запоминать возникающие на отдельных строках ошибки в специально подготовленной таблице с целью последующего разбирательства. Специальную таблицу можно завести с помощью особой системной процедуры. Пример ее создания для таблицы EMP и дальнейшего употребления приводится ниже.

    SQL> EXECUTE DBMS_ERRLOG.CREATE_ERROR_LOG ( 'EMP', 'ERR_EMP' )
    PL/SQL procedure successfully completed.
    SQL> INSERT INTO emp ( empno ) VALUES ( 1111 );
    1 row created.
    SQL> /
    INSERT INTO emp ( empno ) VALUES ( 1111 )
    *
    ERROR at line 1:
    ORA-00001: unique constraint (SCOTT.PK_EMP) violated
    SQL> INSERT INTO emp ( empno ) VALUES ( 1111 ) 
      2  LOG ERRORS INTO err_emp ( 'today error' ) REJECT LIMIT 10;
    0 rows created.
    SQL> COLUMN ora_err_mesg$ FORMAT A45 WORD
    SQL> COLUMN ora_err_tag$ FORMAT A12
    SQL> COLUMN err# FORMAT 99999
    SQL> COLUMN empno FORMAT A6
    SQL> SELECT ora_err_number$ err#, ora_err_mesg$, ora_err_tag$, empno 
    SQL> FROM err_emp;
      ERR# ORA_ERR_MESG$                                 ORA_ERR_TAG$ EMPNO
    ------ --------------------------------------------- ------------ ------
         1 ORA-00001: unique constraint (SCOTT.PK_EMP)   today error  1111
           violated
    

    Обратите внимание, что второй оператор INSERT строку не добавляет (правило первичного ключа нарушить нельзя), но и ошибку в программу не возвращает.

    Запрет на изменение данных в таблице

    С версии 11 изменение данных в таблице можно запретить, переведя таблицу в состояние READ ONLY:

    ALTER TABLE emp READ ONLY;
    

    До этой версии запретить изменения можно было только одновременно во всех объектах, хранящих свои данные в конкретном табличном пространстве.

    Фиксация или отказ от изменений в БД

    Для сеанса, выдающего команды DML на изменения данных, СУБД создает видимость, что они выполняются сразу в БД. Выполнив тут же запрос, пользователь увидит, будто данные изменились. На деле же они попадут в базу только после выдачи сеансом специальной команды фиксации изменений. Только после этого они станут видны прочим сеансам.

    Все изменения со стороны индивидуальных команд DML заносятся Oracle в БД только группами, в рамках транзакции, по завершению транзакции. Команды завершения текущей транзакции:

    COMMIT [WORK];
    ROLLBACK [WORK];
    

    Завершение транзакции с фиксацией изменений, внесенных операторами DML, происходит только по выдаче (а) команды COMMIT или (б) оператора DDL (скрытым образом завершающего свои действия по изменению таблиц словаря-справочника той же командой COMMIT).

    Завершение транзакции с отменой изменений, внесенных операторами DML, происходит только по выдаче команды ROLLBACK, которая или явно выдается программистом, или, в некоторых случаях, неявно порождается самой СУБД (например, по результату аварийного останова работы программы из-за неперехваченной исключительной ситуации).

    Упражнение. Вставьте запись в имеющуюся таблицу — откатите изменения. Вставьте запись — зафиксируйте. Создайте таблицу, вставьте запись — откатите изменения. Сохранилась ли таблица и ее данные? Создайте заполненную таблицу предложением CREATE TABLE … AS SELECT …. Откатите изменения.

    Oracle нумерует все транзакции сквозным образом постоянно растущими номерами, называемыми SCN (System Change Number, номер изменения системы). Каждая команда COMMIT переводит БД в новое состояние, получающее номер зафиксированной транзакции. Данные БД в более ранних состояниях становятся после этого доступны только средствами (а) восстановления по резервным копиям и (б) "быстрого" восстановления (flashback).

    В версии 11 Oracle разрешила не только аннулировать командой ROLLBACK изменения, совершавшиеся в рамках завершаемой транзакции, но также и отменять изменения, выполнявшиеся по очереди несколькими последними транзакциями (завершенными ранее командой COMMIT). Но делается это уже не операцией SQL, а программно, средствами системного пакета DBMS_FLASHBACK.

    Данные о номере последней транзакции, изменившей строку таблицы

    В версии 10 стало возможным с помощью системной переменной ("псевдостолбца") ORA_ROWSCN узнать SCN транзакции, внесшей последнее изменение в строку. Если ничего не предпринимать специально, то этот номер будет приближенный и соответствовать фактически не строке, а блоку, в котором хранится в БД строка. (Сама возможность хранить вместе со строкой SCN ее последней правки существовала и раньше и использовалась для параллельной репликации, но смотреть этот номер в программе было нельзя).

    Пример:

    SQL> CREATE TABLE dscn AS SELECT * FROM dept;
    Table created.
    SQL> UPDATE dscn SET dname = LOWER ( dname ) WHERE ROWNUM <= 2;
    2 rows updated.
    SQL> SELECT dname, ora_rowscn FROM dscn;
    DNAME          ORA_ROWSCN
    -------------- ----------
    accounting        3812962
    research          3812962
    SALES             3812962
    OPERATIONS        3812962
    SQL> COMMIT;
    Commit complete.
    SQL> SELECT dname, ora_rowscn FROM dscn;
    DNAME          ORA_ROWSCN
    -------------- ----------
    accounting        3812999
    research          3812999
    SALES             3812999
    OPERATIONS        3812999
    

    Однако если создать таблицу с особым указанием, в ней появится скрытый столбец (длиною 6 байт), рассчитанный на хранение номера SCN индивидуально для каждой строки:

    SQL> DROP TABLE dscn;
    Table dropped.
    SQL> CREATE TABLE dscn ROWDEPENDENCIES AS SELECT * FROM dept;
    Table created.
    SQL> UPDATE dscn SET dname = LOWER ( dname ) WHERE ROWNUM <= 2;
    2 rows updated.
    SQL> SELECT dname, ora_rowscn FROM dscn;
    DNAME          ORA_ROWSCN
    -------------- ----------
    accounting
    research
    SALES             3814027
    OPERATIONS        3814027
    SQL> COMMIT;
    Commit complete.
    SQL> SELECT dname, ora_rowscn FROM dscn;
    DNAME          ORA_ROWSCN
    -------------- ----------
    accounting        3814035
    research          3814035
    SALES             3814027
    OPERATIONS        3814027
    

    Эти данные можно использовать в программе, чтобы определить, изменялась ли строка с какого-то времени. Перевести SCN в астрономическое время (приближенно) можно обращением к специальной функции, например:

    SELECT dname, SCN_TO_TIMESTAMP ( ora_rowscn ) FROM dscn;
    

    Обращение с прошлыми данными после внесения изменений

    Традиционно все существующие СУБД создавались как средства моделирования текущего состояния предметной области. Выдача COMMIT переводит БД в новое состояние, и предыдущие данные оказываются потерянными. Такое поведение проще программировать разработчикам СУБД, но оно не всегда удобно пользователям. В частности:

  • для восстановления данных, потерянных в результате непродуманной выдачи COMMIT, пусть даже совсем недавно, приходилось довольствоваться восстановлением по резервной копии БД, что долго и требует наличия собственно резервной копии;
  • нередко возникающую потребность моделировать историю изменения данных приходится имитировать разработчику приложения на свое усмотрение.
  • В версии 9 в Oracle открылись возможности получать от СУБД значения данных таблицы по состоянию на прошлый момент, невзирая на осуществлявшиеся за это время операции фиксации транзакций. В версии 10 эти возможности получили свое развитие. Техническая основа у них разная: использование временно сохранившихся данных в табличном пространстве типа UNDO; использование свободного места в табличном пространстве БД с данными таблицы; особый способ журнализации данных. Отсюда проистекают различия в доступных сроках давности для восстановления в разных случаях.

    Обращение к прошлым значениям данных в таблице

    Для (быстрого) запроса к прежним данным ссылку на таблицу во фразе FROM предложения SELECT следует сопроводить указанием конструкции AS OF.

    Пример (выполнить в качестве упражнения последовательно):

    DELETE FROM emp;
    COMMIT;
    SELECT * FROM emp;
    INSERT INTO emp 
     SELECT *
     FROM   emp AS OF TIMESTAMP ( SYSTIMESTAMP - INTERVAL '1' MINUTE )
    ; 
    SELECT * FROM emp;
    

    (Синтаксис допускает употребление скобок в запросе выше, но не требует этого).

    Момент для восстановления можно еще указать в терминах номера изменений данных в БД (System Change Number, SCN).

    Подобное извлечение старых значений возможно только за период, когда к таблице не применялись команды DDL.

    Давность воспроизводимых данных ограничивается размером свободного места в табличном пространстве UNDO, которое определяется (а) интенсивностью изменений БД и (б) полным размером пространства.

    В версии 10 появилась возможность вместо AS OF уточнять имя таблицы во фразе FROM конструкцией VERSIONS BETWEEN, позволяющей извлекать из БД историю изменения строк определенной давности. Следующим примером можно продолжить приводившийся только что код:

    COLUMN versions_endtime FORMAT A22
    COLUMN versions_starttime FORMAT A22
    SELECT 
      empno
    , sal
    , versions_starttime
    , versions_endtime
    , versions_xid
    , versions_operation 
    FROM
    emp VERSIONS BETWEEN TIMESTAMP MINVALUE AND MAXVALUE
    ;
    

    Прочие поля строк таблицы EMP в этом запросе не выданы из экономии места.

    Упражнение: Измените несколько раз зарплаты разным сотрудникам и просмотрите историю изменений.

    Восстановление данных существующих таблиц, ранее удаленных таблиц и всей БД

    С версии 10 открылась возможность одной командой восстановить строки таблицы на момент в прошлом. Это позволяет вместо вышеуказанного INSERT … SELECT … AS OF записать

    FLASHBACK TABLE emp 
    TO TIMESTAMP ( SYSTIMESTAMP - INTERVAL '1' MINUTE )
    ;
    

    Тем не менее:

  • эти две команды не равносильны, когда перед восстановлением строк таблица была непуста;
  • чтобы восстановление строк таблицы командой FLASHBACK было возможным, мы должны разрешить системе изменять физические адреса строк таблицы. Например, выдать: ALTER TABLE emp ENABLE ROW MOVEMENT;
  • Для полного восстановления таблицы, ранее удалявшейся, команда FLASHBACK выглядит иначе. Вариант простого восстановления (из мусорной корзины):

    FLASHBACK TABLE emp TO BEFORE DROP;
    

    Вариант с переименованием (если с момента удаления таблица с именем EMP создавалась повторно):

    FLASHBACK TABLE emp TO BEFORE DROP RENAME TO emp_old;
    

    Если таблица переименовывалась несколько раз, вместо имени таблицы можно подставить внутреннее название для одной из старых версий таблицы, полученное из системной таблицы USER_RECYCLEBIN.

    Технически такая возможность достигается сохранением старой структуры хранения ("сегмента") таблицы в табличном пространстве при выполнении DROP TABLE, что определяет границы применимости такого подхода.

    Если же база данных работает в специальном режиме flashback, команда FLASHBACK позволяет восстановливать ее целиком:

    FLASHBACK DATABASE TO TIMESTAMP SYSTIMESTAMP - INTERVAL '1' HOUR;
    
    Вернуться к учебному плану