"Yes, indeed," they repeated together; "but if we'd told you then, you might not
have gone -- and, as you've discovered, so many things are possible just as long as
you don't know they're impossible."
-- Norton Juster, The Phantom Tollbooth
A recent tweet on Oracle’s flashback technology caused me to dig through some older posts to find the original post I made about the restorative powers of flashback query. Reading through the text inspired me to update the original post with more current information — it was published in 2008 so a lot of water has gone under the bridge, so to speak. The basic concepts have not changed appreciably but the implementation has become much easier, and the options to flashback an entire table, or an entire database, have been added (presuming sufficient space and/or the required undo is available). Let’s take a test drive.
The original implementation of flashback query was, as noted in a prior post, a bit cumbersome but better than the alternative of having an export handy to restore a table deleted by mistake. Flashback query allowed for recovering data up to the point of deletion (which an export can’t do) so the bit of extra PL/SQL coding necessary to restore missing data was worth the work. A script like the one below would be used to retrieve the missing data then replace it in the current table:
declare
--
-- Cursor to fetch the required data
--
cursor c1 is
select flerg, snerm, anguplast
from snangpiester;
--
-- A place to stash each row of the returned data
--
c1_rec c1%rowtype;
begin
--
-- We flashback the data image to before the problem began
--
dbms_flashback.enable_at_time(to_timestamp('16-SEP-2007 06:53:00 AM','DD-MON-RRRR HH:MI:SS AM'));
--
-- Open the cursor while in flashback mode
--
open c1;
--
-- Disable flashback mode so we can update the problem table
--
dbms_flashback.disable;
--
-- Go fetch the good data and update the problem table
--
loop
fetch c1 into c1_rec;
exit when c1%notfound; -- say goodbye when there's no more data
update snangpiester
set anguplast = c1_rec.anguplast
where flerg = c1_rec.flerg
and snerm = c1_rec.snerm;
end loop;
--
-- Commit the changes
--
commit;
end;
/
From 10g onward flashback query has been enabled by default and usable without the extra coding the dbms_flashback package required, making it much simpler to restore deleted data providing the undo retention has not been exceeded. Flashback query syntax remains the same and is as follows:
select [select list here]
from [table]
as of timestamp to_timestamp('date/time or timestamp string');
The latest releases also provide the ability to flashback the entire table:
flashback table [table]
to timestamp to_date([date string],[date format]);
making it a simpler task to revert all changes made after a given date/time value. For flashback table to work row movement must be enabled:
SQL> alter table emp enable row movement;
Table altered.
SQL>
Now the table can be flashed back to an hour ago, before the errant changes were made:
SQL> flashback table emp to timestamp to_timestamp(sysdate-1/24);
Flashback complete.
SQL>
Let’s take the original example, that used flashback query, and use flashback table to restore the original data:
Koffi Cupps, assistant HR manager and part-time ankle model, wanted to increase the salaries of the sales team by 17 percent and add $85 to each salesman’s commission; unfortunately she updated the entire employee table without first making a copy of the original data and committed the changes before checking the results:
SQL> update emp
2 set sal = sal*1.17, comm = nvl(comm, 0) + 85;
14 rows updated.
SQL> commit;
Commit complete.
SQL> select
2 from emp;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 936 85 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1872 385 30
7521 WARD SALESMAN 7698 22-FEB-81 1462.5 585 30
7566 JONES MANAGER 7839 02-APR-81 3480.75 85 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1462.5 1485 30
7698 BLAKE MANAGER 7839 01-MAY-81 3334.5 85 30
7782 CLARK MANAGER 7839 09-JUN-81 2866.5 85 10
7788 SCOTT ANALYST 7566 09-DEC-82 3510 85 20
7839 KING PRESIDENT 17-NOV-81 5850 85 10
7844 TURNER SALESMAN 7698 08-SEP-81 1755 85 30
7876 ADAMS CLERK 7788 12-JAN-83 1287 85 20
7900 JAMES CLERK 7698 03-DEC-81 1111.5 85 30
7902 FORD ANALYST 7566 03-DEC-81 3510 85 20
7934 MILLER CLERK 7782 23-JAN-82 1521 85 10
14 rows selected.
SQL>
Fortunately her younger sister, Dixie, is the Oracle DBA for the company. Knowing the HR software was using Oracle 19.3.0.0 Dixie sprang into action to restore the data prior to the change using flashback table (it was also a good thing this was early on a Monday morning and that the undo_retention was set to a sufficient size else Dixie could have received an ORA-01555 because the necessary undo blocks had been overwritten):
SQL> flashback tabld emp as of timestamp to_timestamp(trunc(sysdate));
Flashback complete.
SQL> select * From emp;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 09-DEC-82 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 12-JAN-83 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10
14 rows selected.
SQL>
Now Dixie can update the table properly, only processing the sales employees:
SQL> update emp
2 set sal = sal*1.17, comm = nvl(comm, 0) + 85
3 where job = 'SALESMAN'
4 /
4 rows updated.
SQL>
SQL> select * From emp
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1872 385 30
7521 WARD SALESMAN 7698 22-FEB-81 1462.5 585 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1462.5 1485 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 09-DEC-82 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1755 85 30
7876 ADAMS CLERK 7788 12-JAN-83 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10
14 rows selected.
SQL> commit;
Xommit complete.
SQL>
Flashback table restored the data to it’s ‘original’ state as of the date/time specified in the flashback statement so the correct update could be applied. Of course the data could also have been restored using flashback query; I will refer you to the original post to find that example.
Another example from that post seems worthy to re-post here, involving flashback query to generate a salary increase report to verify that no employee has exceeded the maximum percentage increase on their salary (which is why this example generates the report after the increase has been processed but before the payroll data has been released):
SQL> --
SQL> -- Get reference timestamp before changes
SQL> --
SQL> select to_timestamp(to_char(sysdate, 'DD-MON-YYYY HH:MI:SS AM')) curr_timestamp from dual;
CURR_TIMESTAMP
---------------------------------------------------------------------------
09-JAN-08 11.54.40.000000000 AM
SQL>
SQL> --
SQL> -- Change the data in EMP
SQL> --
SQL> update emp
2 set sal = sal+120, comm = nvl(comm, 0) + 85
3 where job = 'SALESMAN';
4 rows updated.
SQL>
SQL> --
SQL> -- Commit and make the changes "permanent"
SQL> --
SQL> commit;
Commit complete.
SQL>
SQL> --
SQL> -- Show the current state of the data
SQL> --
SQL> select *
2 from emp;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1720 385 30
7521 WARD SALESMAN 7698 22-FEB-81 1370 585 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1370 1485 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 09-DEC-82 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1620 85 30
7876 ADAMS CLERK 7788 12-JAN-83 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10
14 rows selected.
SQL>
SQL>
SQL> --
SQL> -- Generate a report of employee number,
SQL> -- employee name, job, salary, percent increase,
SQL> -- commission, increase
SQL> --
SQL> with get_old as(
2 select empno, ename, job, sal, nvl(comm,0) comm
3 from emp
4 as of timestamp to_timestamp('&curr_ts')
5 ),
6 get_curr as (
7 select empno, ename, job, sal, comm
8 from emp
9 )
10 select c.empno, c.ename, c.job, c.sal,
11 (c.sal - o.sal)/o.sal*100 sal_incr,
12 c.comm,
13 (c.comm - o.comm) comm_incr
14 from get_old o, get_curr c
15 where c.empno = o.empno;
EMPNO ENAME JOB SAL SAL_INCR COMM COMM_INCR
---------- ---------- --------- ---------- ---------- ---------- ----------
7369 SMITH CLERK 800 0
7499 ALLEN SALESMAN 1720 7.5 385 85
7521 WARD SALESMAN 1370 9.6 585 85
7566 JONES MANAGER 2975 0
7654 MARTIN SALESMAN 1370 9.6 1485 85
7698 BLAKE MANAGER 2850 0
7782 CLARK MANAGER 2450 0
7788 SCOTT ANALYST 3000 0
7839 KING PRESIDENT 5000 0
7844 TURNER SALESMAN 1620 8 85 85
7876 ADAMS CLERK 1100 0
7900 JAMES CLERK 950 0
7902 FORD ANALYST 3000 0
7934 MILLER CLERK 1300 0
14 rows selected.
SQL>
Flashback query, and flashback table, are two very useful tools for the DBA to have access to. Fast action and a sufficieintly sized undo retention can go a long way in restoring data changed in error, or verifying that changes are within the scope of company policy. And that can keep (almost) everyone happy.
Sort of makes the “impossible” … possible.