Shay Shmeltzer posted the link to the Oracle Openworld 2007 sessions : http://www.cplan.com/oracleopenworld2007/sanfrancisco/cc
keep dense_rank with multiple column
create table t(
deptno number,
firstname varchar2(10),
lastname varchar2(10),
hiredate date);
insert into t values (
10,'Jo','Smith',date '2001-01-01');
insert into t values (
10,'Jack','River',date '2002-02-02');
to get the latest hiredate per department
select deptno,
max(hiredate) hiredate
from t
group by deptno;
DEPTNO HIREDATE
---------- ---------
10 02-FEB-02
if you want to get the name of the employee at that date, you could by mistake believe the following works
select deptno,
max(firstname) keep (dense_rank last
order by hiredate) firstname,
max(lastname) keep (dense_rank last
order by hiredate) lastname,
max(hiredate) hiredate
from t
group by deptno;
DEPTNO FIRSTNAME LASTNAME HIREDATE
---------- ---------- ---------- ---------
10 Jack River 02-FEB-02
This will produce wrong result if hiredate is not unique
insert into t values (10,'Bob','Zhong', date '2002-02-02');
select deptno,
max(firstname) keep (dense_rank last
order by hiredate) firstname,
max(lastname) keep (dense_rank last
order by hiredate) lastname,
max(hiredate) hiredate
from t
group by deptno;
DEPTNO FIRSTNAME LASTNAME HIREDATE
---------- ---------- ---------- ---------
10 Jack Zhong 02-FEB-02
of course there is Jack Zhong.
To get a consistent record, it is possible to add all the columns in the order by
select deptno,
max(firstname) keep (dense_rank last
order by hiredate,firstname,lastname) firstname,
max(lastname) keep (dense_rank last
order by hiredate,firstname,lastname) lastname,
max(hiredate) hiredate
from t
group by deptno;
DEPTNO FIRSTNAME LASTNAME HIREDATE
---------- ---------- ---------- ---------
10 Jack River 02-FEB-02
get Nth column of a table
I answered this question twice, once on otn forums and once on developpez.net
Here is the latest to get the third column of emp
select
column_name as name,
extractvalue(column_value,’/ROW/’||column_name) as value
from table(xmlsequence(cursor(select * from emp))),
user_tab_columns
where COLUMN_ID=3 and table_name=’EMP’
;
NAME VALUE
---- ----------
JOB CLERK
JOB SALESMAN
JOB SALESMAN
JOB MANAGER
JOB SALESMAN
JOB MANAGER
JOB MANAGER
JOB ANALYST
JOB PRESIDENT
JOB SALESMAN
JOB CLERK
JOB CLERK
JOB ANALYST
JOB CLERK
probably useless, but fun 😉
Oracle ACE Director
Congratulation to Eddie Awad for becoming an Oracle ACE Director !
How to compare schema
If you have receive ddl statements from your developer and you want to check if it matches the current state of the development database, because the developer have done a lot of change in a quick and undocumented manner, what are your options?
I found this handy feature in Toad :
1) I create my objects on a separate database with the ddl I received from development
2) I compare the schema they use with the schema I created in Toad
–> Database –> Compare –> Schema
I select the options I want:
–> functions, indexes, packages, procedures, triggers, tables, view
I select the Reference and Comparison connections/schemas. Then I click compare
3) I receive the result
(only) 29 differences
4) the real bonus, I receive a script to update the live data according to the script I received. Undocumented change should never happen, so I do some communication with the developers
drop index foo;
drop table bar;
alter table gaz drop column bop;
alter table gaz modify (quux null);
this is not going to be blind-executable, some change are simply impossible to implement, but for my little test, I was happy to discover that function
I have been using ERwin for this purpose before, but the version I have (4.1) is very buggy and does not support a lot of syntaxes (ex: deferred constraints, create view v as select cast(1 as number(1)) x from dual, etc…). Also ERwin can compare only with the current model, so no direct comparison between 2 database schema.
Oracle OpenWorld 2007
I will go to Oracle OpenWorld in San Francisco this year again
download 11g today
http://www.oracle.com/technology/software/products/database/index.html
9iR2 Desupport
REMINDER: Premier Support for Oracle Database 9.2 ends in July 2007 The 9iR2 premier support just ended.
Now the 9i is under Extended Support. The good news, it is free until July 2008!
First year Extended Support fee waived for Oracle9i Database Release 9.2
how to spell 999999999 ?
begin
dbms_output.put_line(
to_char(
timestamp '9999-12-31 23:59:59.999999999',
'FF9SP'));
end;
/
NINE HUNDRED NINETY-NINE MILLION NINE HUNDRED NINETY-NINE
THOUSAND NINE HUNDRED NINETY-NINE
Unfortunately, I could not get this in sql/10.2.0.2
select
to_char(
timestamp '9999-12-31 23:59:59.999999999',
'FF9SP') X
from
dual;
ORA-01877: string is too long for internal buffer
Well, since the string is too long, let’s try with a LONGER string 😈
select
regexp_substr(
to_char(
timestamp '9999-12-31 23:59:59.999999999',
'FF9SP/FMDAY MONTH DDTHSP YYYYSP A.D. HH24SP MISP SSSP')
,'[^/]+')X
from
dual;
X
---------------------------------------------------------
----------------------------------
NINE HUNDRED NINETY-NINE MILLION NINE HUNDRED NINETY-NINE
THOUSAND NINE HUNDRED NINETY-NINE
SQL Expert?
I have attended the sql certified expert beta exam this morning. There were a lot of errors in it, I added in the comment that they have to groundly review their regular expressions questions. There were a lot of rubbish question, but hardly any challenge, it is more like detecting the incorrect syntax. So I am deceived. I have to wait 3 months for the result, but I expect no more than 90% 😎
They even have an exhibit with a table containing many columns with the same name (!)
Well, I hope they will improve with the comments I made when the production release will come out.
The time to answer the question is sufficient. I had only 139 questions in 3 hours, and I needed only 2 hours actually with plenty of time to review.
oracle 11g hits the streets
I read on this site that Oracle11g GA is for 2007 August
ORA-01466: unable to read data – table definition has changed
I re-edited this post and it is unresolved yet. I thought it was related to system time, but apparently not 😮
SQL> create table t(x number);
Table created.
SQL> set transaction read only ;
Transaction set.
SQL> select * from t;
select * from t
*
ERROR at line 1:
ORA-01466: unable to read data - table definition has changed
If I wait one minute after my create table statement, it works
SQL> drop table t;
Table dropped.
SQL> create table t(x number);
Table created.
SQL> host sleep 60
SQL> set transaction read only;
Transaction set.
SQL> select * from t;
no rows selected
😈
Oracle Certified SQL Expert
Oracle Certification Program: Exam Details
I immediately registered for this exam and will do it next week, I just cannot wait for this one…
avoid unnecessary updates
I do update t set x=:a;
If I do it twice, I am doing a lot of unnecessary updates. This is true in an update, and also in the update clause of a MERGE.
I need to take care of null, I can update null with something, or something with null, but update null with null is also unnecessary.
SQL> update t set x=:a;
4977 rows updated.
Elapsed: 00:00:00.34
SQL> update t set x=:a;
4977 rows updated.
Elapsed: 00:00:00.32
SQL> update t set x=:a
2 where x!=:a
3 or (x is null and :a is not null)
4 or (x is not null and :a is null);
0 rows updated.
Elapsed: 00:00:00.04
v$session_longops in 10gR2
I have read an excellent overview of DBMS_APPLICATION_INFO in Andy Campbell blog on If only…
I have written a procedure P which is executing 5 statements. I want to add the name of the procedure P as a target. I do not want to hardcode the name however… So I can use $$PLSQL_UNIT in 10gR2 (documented in Using Predefined Inquiry Directives With Conditional Compilation)
Ok, here I go
create or replace procedure p is
rindex binary_integer;
slno binary_integer;
sofar number;
totalwork number;
target number;
begin
totalwork := 5;
sofar := 0;
select object_id
into target
from user_objects
where object_name = $$PLSQL_UNIT;
DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
rindex,slno,'Executing...',target,null,
sofar,totalwork,null,'Statement');
-- do something
sofar:=sofar+1;
DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
rindex,slno,'Executing...',target,null,
sofar,totalwork,null,'Statement');
-- do something
sofar:=sofar+1;
DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
rindex,slno,'Executing...',target,null,
sofar,totalwork,null,'Statement');
-- do something
sofar:=sofar+1;
DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
rindex,slno,'Executing...',target,null,
sofar,totalwork,null,'Statement');
-- do something
sofar:=sofar+1;
DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
rindex,slno,'Executing...',target,null,
sofar,totalwork,null,'Statement');
-- do something
sofar:=sofar+1;
DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
rindex,slno,'Executing...',target,null,
sofar,totalwork,null,'Statement');
end;
/
Now I execute it
SQL> exec p
PL/SQL procedure successfully completed.
and monitor it
SQL> select sid,opname,target,sofar,totalwork,units
2 from v$session_longops;
SID OPNAME TARGET SOFAR TOTALWORK UNITS
---- ------------ -------- ----- --------- ---------
538 Executing... SCOTT.P 5 5 Statement
RAC exam
I have attend the RAC beta exam this afternoon. 181 questions in 3.5 hours, it is a lot of questions! I prepared by reading the 2-day dba RAC document. There are also some questions about Maximum Availability Architecture (MAA) with physical and logical standby. Also some question which are Linux speci
I have been short on time, the question were very long to read, and 181 questions in 210 minutes, I did not have the time to review all.
Now I must wait about 10 weeks to get the result, but I am quite optimistic 😎
11g launch in 15 days
watch the launch event live
http://www.oracle.com/pls/ebn/live_viewer.main?p_shows_id=5717957
read user-input in plsql
How can I read user input in plsql?
kind of
begin
write('Enter a value for x : ');
read(x);
write('you enterred '||x);
end;
/
the short answer is : you cannot do that.
Ok, let’s try to do it in Linux !
$ cat interactiveplsql.sql
set feedb off
create or replace directory tmp as '/tmp';
declare
inFile utl_file.file_type;
outFile utl_file.file_type;
x varchar2(40);
begin
inFile := utl_file.fopen('TMP','in','R');
outFile := utl_file.fopen('TMP','out','W');
utl_file.put_line(outFile,'Enter a value for x : ');
utl_file.fflush(outFile);
utl_file.get_line(inFile,x);
utl_file.put_line(outFile,'you enterred '||x);
utl_file.fclose(inFile);
utl_file.fclose(outFile);
end;
/
quit
$ mknod /tmp/out p; mknod /tmp/in p
$ (cat /tmp/out &);(sqlplus -s scott/tiger @interactiveplsql &
);cat>/tmp/in
Enter a value for x :
ABC123
you enterred ABC123
rename column_value
SQL> create or replace type t is
2 table of varchar2(12);
3 /
Type created.
SQL> create or replace function f return t is
2 begin return t('foo'); end;
3 /
Function created.
SQL> select * from table(f);
COLUMN_VALUE
------------
foo
What is this column_value field? It is a pseudo-column. But you may want to have an user-defined column name.
SQL> create or replace type o is
2 object(BAR varchar2(12));
3 /
Type created.
SQL> create or replace type t is
2 table of o;
3 /
Type created.
SQL> create or replace function f return t is
2 begin return t(o('foo')); end;
3 /
Function created.
SQL> select * from table(f);
BAR
------------
foo
11g new feature course
Lutz announced his 11g courses in Switzerland in this postOracle University Switzerland announces brand new Oracle 11g courses
to divide or to multiply
warning, this test is cpu intensive, do not try on your productive server
One user on the developpez.net French forums asked today about rewritting a division in a multiplication for tuning. Like select avg(sal)/2 from emp; in select avg(sal)*.5 from emp;.
Well, I had to test this ! I execute 41055 divisions in a plsql loop. To avoid incrementation, I divide by 1.014… and multiply by 0.986… in a way that the result keep the same all over the loop.
SQL>
SQL> var z number
SQL> var y number
SQL> exec :z := power(2,102)*2e-31;
SQL> exec :y := 1e125;
SQL> set timi on
SQL> exec while (:y>1e-125) loop :y:=:y/:z; end loop
Elapsed: 00:00:00.42
SQL> set timi off
SQL> print yY
--------------------------------
9.9879215917842541374103299E-126SQL> exec :z := power(2,-104)*2e31;
SQL> exec :y := 1e125;
SQL> set timi on
SQL> exec while (:y<1e-125) loop :y:=:y*:z; end loop Elapsed: 00:00:00.28 SQL> set timi off
SQL> print yY
--------------------------------
9.9879215917842541374103299E-126It has been difficult to find an example with clear difference and not too weird :twisted:
The operation divide is slower than multiply, probably to catch divide by zero errors...
create your database with dbca
With dbca you can fasten the procedure of creating databases in your company. You can also run this in silent mode and create exactly the database you want, with your redo/undo size, with your parameters settings. You create the template once, and use it many times. Fast and easy 😀
I have one database which I created with SQL*PLUS called LSC01.
1) Create template lsc-template
dbca
–> Manage Templates
–> Create a database template
–> from an existing database (structure as well as data)
–> LSC01
–> lsc-template
–> convert the file locations to use OFA structure
This takes some place on disk and will speed up database creation. Technically speaking, it is doing a compressed backup with RMAN that will be restore, and restore is way faster than create database
2) Create database LSC99 in silent mode or progressOnly mode.
dbca -silent -createDatabase -templateName lsc-template.dbc -gdbName LSC99.lcsys.ch
It took me only two minutes to create my database on my notebook !
Try it ! Of course I expect comments on this post 
Column qualification best practice
Lazyness at the development can have dramatic costs in production and maintenance. I want to summarize why and where you should always qualify your columns.
Why? when you select or modify data from a table, you must qualify the columns you are using so if the order of the column change, or if one column is added, renamed or removed, so long this column is not related to your query your code should keep working. A typical example is when the dba add a LAST_MODIFICATION_DATE and a trigger to automatically fill that column for auditing purpose. It should never make your application fail.
0) sample table
create table t1(x number, y number);
create table t2(a number, b number);
create table t3(x number, z number);
1) insert into table
bad:insert into t1 values(1,2);
good:insert into t1(x,y) values(1,2);
bad:insert into t1
select * from t2;
good:insert into t1(x,y)
select t2.a,t2.b from t2;
bad:insert all
when a>0 then into t1
select * from t2;
good:insert all
when a>0 then into t1(x,y)
select t2.a,t2.b from t2;
2) merge
bad:merge into t1
using t2 on (x=a)
when matched then
update set y=b
where (b>y)
delete
where (b>y)
when not matched then
insert values(a,b)
where (b>0);
good:merge into t1
using t2 on (t1.x=t2.a)
when matched then
update set y=t2.b
where (t2.b>t1.y)
delete
where (t2.b>t1.y)
when not matched then
insert (x,y) values(t2.a,t2.b)
where (t2.b>0);
Not prefixing the column here will bug as soon as a column a or b is added to table t1
2) joins
just never use natural join in production
bad:select *
from t1 natural join t3;
good:select x, t1.y, t3.z
from t1 join t3 using (x);
bad:select *
from t1,t2
where x=a;
good:select t1.x,t1.y,t2.a,t2.b
from t1,t2
where t1.x=t2.a;
It is not about good looking code, it is about data quality and stability 😎
10.1.0.5 is the terminal patchset
I just read on the General Notes For Oracle Database – Enterprise Edition in Metalink that 10.1.0.5 is the terminal patchset for 10gR1
sql*plus pagesize explained
SQL*Plus is a not only the command-line interface to the database server, it is also a featured reporting tool with paging capabilities. The pagesize is the number of rows of one page. The default is 14 and the maximum is 50000. One of the common property of the page is the headers when selecting from a table.
SQL> sho pages
pagesize 14
SQL> select empno,ename from emp;
EMPNO ENAME
---------- ----------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
EMPNO ENAME
---------- ----------
7900 JAMES
7902 FORD
7934 MILLER
14 rows selected.
This is rather an annoying effect of the default setting than a feature and there is no set pagesize unlimited. The only way to have the header only once, is to set the pagesize to the maximum or use this trick : set pages 0 emb on newp none. Unfortunately, the later does not work for HTML reporting.
One of the less known and advanced sql*plus capabilities is the title of the page.
Today on the developpez.net forums I had a question about generating a describe for each table in user_tables.
The short answer is select * from user_tab_columns
In my output, I want to have each table on a separate page, with the table_name in the title, and the column names, not null options and datatypes in the page.
First I want to have a page per table, this I can define with break
bre on table_name ski page
I want to have the table name in the title of the page, but not as a column. I add some blank lines in the top title and in the bottom title.
col table_name new_v table_name nopri
tti le table_name s 2
bti s 1
Finally I set the pagesize to something bigger than the maximum number of columns of tables plus 6 for header and title
set pages 0
col pages new_v pages nopri
select max(count(*))+6 pages
from user_tab_columns
where table_name in (‘EMP’,’DEPT’,’T’)
group by table_name;
set pages &pages
Now the select
select
table_name,
column_name “Name”,
decode(nullable,’N’,’NOT NULL’) “Null?”,
DATA_TYPE||
case when DATA_TYPE in (‘NUMBER’,’FLOAT’)
and (data_precision is not null
or data_scale is not null) then
‘(‘||nvl(DATA_precision,38)||
case when data_scale!=0 then
‘,’||DATA_SCALE
end
||’)’
when data_type like ‘%CHAR%’ then
‘(‘||DATA_LENGTH||’)’ end
“Type”
from user_tab_columns
where table_name in (‘EMP’,’DEPT’,’T’);
DEPT Name Null? Type ------------------------------ -------- -------------------- DEPTNO NOT NULL NUMBER(2) DNAME VARCHAR2(14) LOC VARCHAR2(13) EMP Name Null? Type ------------------------------ -------- -------------------- EMPNO NOT NULL NUMBER(4) ENAME VARCHAR2(10) JOB VARCHAR2(9) MGR NUMBER(4) HIREDATE DATE SAL NUMBER(7,2) COMM NUMBER(7,2) DEPTNO NUMBER(2)
For a HTML report, set mark html on
set mark html on
/
| DEPT |
| Name | Null? | Type |
|---|---|---|
| DEPTNO | NOT NULL | NUMBER(2) |
| DNAME | VARCHAR2(14) | |
| LOC | VARCHAR2(13) |
| EMP |
| Name | Null? | Type |
|---|---|---|
| EMPNO | NOT NULL | NUMBER(4) |
| ENAME | VARCHAR2(10) | |
| JOB | VARCHAR2(9) | |
| MGR | NUMBER(4) | |
| HIREDATE | DATE | |
| SAL | NUMBER(7,2) | |
| COMM | NUMBER(7,2) | |
| DEPTNO | NUMBER(2) |
sequence / thread / system change number
I have seen a confusion between sequence and system change number quite often.
The sequence is a fairly little number which is the number of log switch since database creation (it can be resetted by open resetlogs).
The thread is only relevant in RAC. In single-instance database, it is always 1.
You can find the current sequence number with the following query
select sequence#,thread#
from v$log
where status='CURRENT';
The system change number (SCN) is a much higher number, which is continously increasing, even when you do nothing. The dbms_flashback package has a function to return the current system change number :
select dbms_flashback.get_system_change_number
from dual;
In 10g, there is a standard function to get the current system change number
select timestamp_to_scn(current_timestamp) from dual;
Before a major application/database upgrade, it is good practice to make a backup and write down the SCN for an easier recovery procedure.
read my comment about non-accuracy of timestamp_to_scn
select from a comma-separated string
This is one question I solved today with XML on the developpez.net/forums
I have a table T
| Un,Trois,Cinq,Six |
| Un,Deux,Quatre |
| Trois |
| Sept,Huit |
| Un,Six |
I want to select un,deux,trois,quatre,cinq,six,sept,huit.
I could well have written a plsql function like
create or replace type c as object (value varchar2(40));
/
create or replace type t_c as table of c;
/
sho err
create or replace function f(
list varchar2,
sep varchar2 default ',')
return t_c pipelined is
i number:=1;
begin
loop
if (instr(list,sep,1,i)>0)
then
if (i=1) then
pipe row(c(substr(list,
1, instr(list,sep)-1)));
else
pipe row(c(substr(list,
instr(list,sep,1,i-1)+1,
instr(list,sep,1,i)-
instr(list,sep,1,i-1)-1)));
end if;
else
if (i=1) then
pipe row(c(list));
else
pipe row(c(substr(list,
instr(list,sep,1,i-1)+1)));
end if;
return;
end if;
i:=i+1;
end loop;
end;
/
select distinct value
from t,
table(f(c))
;
or anything using recursion or whatever.
I just tried with XML
SQL> select distinct extractvalue(column_value,’/x’)
2 from t,
3 table(xmlsequence(extract(xmltype(
4 ‘<list><x>’||replace(
5 c,’,’,'</x><x>’)||
6 ‘</x></list>’),
7 ‘/list/x’)));
EXTRACTVALUE(COLUMN_VALUE,’/X’)
——————————-
Trois
Un
Quatre
Cinq
Six
Deux
Sept
Huit
8 rows selected.
dbms_xplan and v$sql_plan
do not miss yas comment !
tested in 10.2
create or replace procedure
explain_plan(hash_value number)
is begin
insert into plan_table
select
null,
(select nvl(max(plan_id),0)+1 from plan_table),
timestamp,
remarks,
operation,
options,
object_node,
object_owner,
object_name,
object_alias,
null,
object_type,
optimizer,
search_columns,
id,
parent_id,
depth,
position,
cost,
cardinality,
bytes,
other_tag,
partition_start,
partition_stop,
partition_id,
other,
other_xml,
distribution,
cpu_cost,
io_cost,
temp_space,
access_predicates,
filter_predicates,
projection,
time,
qblock_name
from v$sql_plan
where hash_value=explain_plan.hash_value;
for f in (
select PLAN_TABLE_OUTPUT from table(dbms_xplan.DISPLAY))
loop
dbms_output.put_line(f.PLAN_TABLE_OUTPUT);
end loop;
end;
/
SQL> set lin 200 trims on pages 0 serverout on
SQL> select ename from emp where empno=7788;
ENAME
----------
SCOTT
SQL> select hash_value from v$sql where sql_text like
2 'select ename from emp where empno=7788%';
HASH_VALUE
----------
1019401098
SQL> exec explain_plan(1019401098)
Plan hash value: 4066871323
------------------------------------------------------
| Id | Operation | Name | Rows |
------------------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 |
|* 2 | INDEX UNIQUE SCAN | PK_EMP | 1 |
------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPNO"=7788)
PL/SQL procedure successfully completed.
rename
Views:
SQL> create view v1 as select * from dual;
View created.
SQL> rename v1 to v2;
Table renamed.
Tables:
SQL> create table t1 as select * from dual;
Table created.
SQL> rename t1 to t2;
Table renamed.
Sequences:
SQL> create sequence s1;
Sequence created.
SQL> rename s1 to s2;
Table renamed.
Synonyms:
SQL> create synonym syn1 for dual;
Synonym created.
SQL> rename syn1 to syn2;
Table renamed.
Constraints :
SQL> create table t(x number not null);
Table created.
SQL> select constraint_name
2 from user_constraints
3 where table_name='T';
CONSTRAINT_NAME
------------------------------
SYS_C0018686
SQL> alter table t rename constraint SYS_C0018682 to X_NOT_NULL;
Table altered.
Indexes :
SQL> create index i1 on t(x);
Index created.
SQL> alter index i1 rename to i2;
Index altered.
Columns :
SQL> alter table t rename column x to y;
Table altered.
Partitions :
SQL> create table t (x number, y number)
2 partition by range(x)
3 subpartition by hash(y) (
4 partition p1 values less than(1)
5 (subpartition sp1)
6 ) ;
Table created.
SQL> alter table t rename partition p1 to p2;
Table altered.
Subpartitions:
SQL> alter table t rename subpartition sp1 to sp2;
Table altered.
Triggers:
SQL> create trigger tr1 after update on t
2 begin null; end;
3 /
Trigger created.
SQL>
SQL> alter trigger tr1 rename to tr2;
Trigger altered.
For the dba, you can also rename datafile, logfile and even tablespaces (10g)
constraints for referential integrity
On the developpez.net forums I answered a question about referential integrity. How can you delete/update a parent row when the child exist ?
SQL> create table continent(
2 name varchar2(10),
3 constraint continent_pk primary key(name));
Table created.
SQL> create table country(
2 name varchar2(10),
3 continent varchar2(10),
4 constraint country_pk
5 primary key(name),
6 constraint country_continent_fk
7 foreign key(continent) references continent);
Table created.
SQL> insert into continent values('Africa');
1 row created.
SQL> insert into country values('Benin', 'Africa');
1 row created.
CONTINENT
| NAME |
|---|
| Africa |
COUNTRY
| NAME | CONTINENT |
|---|---|
| Benin | Africa |
So in this default constellation, I cannot delete a parent row where the child exist.
SQL> delete continent where name='Africa';
delete continent where name='Africa'
*
ERROR at line 1:
ORA-02292: integrity constraint (AUDBA.COUNTRY_CONTINENT_FK)
violated - child record found
I could specify CASCADE to delete the child rows automatically
SQL> alter table country
2 drop constraint country_continent_fk;
Table altered.
SQL> alter table country
2 add constraint country_continent_fk
3 foreign key(continent)
4 references continent
5 on delete cascade;
Table altered.
SQL> delete continent where name='Africa';
1 row deleted.
CONTINENT
| NAME |
|---|
COUNTRY
| NAME | CONTINENT |
|---|
but this is dangerous. When I read 1 row deleted., I am not informed that I have deleted rows in the child table.
I could rather set the column to null
SQL> rollback;
Rollback complete.
SQL> alter table country
2 drop constraint country_continent_fk;
Table altered.
SQL> alter table country
2 add constraint country_continent_fk
3 foreign key(continent)
4 references continent
5 on delete set null;
Table altered.
SQL> delete continent where name='Africa';
1 row deleted.
CONTINENT
| NAME |
|---|
COUNTRY
| NAME | CONTINENT |
|---|---|
| Benin |
Ok, but what If I need to rename Africa to EMEA ? I can do this neither on the child nor on the parent
SQL> rollback;
Rollback complete.
SQL>
SQL> update continent
2 set name='EMEA'
3 where name='Africa';
update continent
*
ERROR at line 1:
ORA-02292: integrity constraint (AUDBA.COUNTRY_CONTINENT_FK)
violated - child record found
SQL> update country
2 set continent ='EMEA'
3 where continent ='Africa';
update country
*
ERROR at line 1:
ORA-02291: integrity constraint (AUDBA.COUNTRY_CONTINENT_FK)
violated - parent key not found
So I could defer the constraint validation to the commit
SQL> alter table country
2 drop constraint country_continent_fk;
Table altered.
SQL> alter table country
2 add constraint country_continent_fk
3 foreign key(continent)
4 references continent
5 deferrable initially deferred;
Table altered.
SQL> update continent
2 set name='EMEA'
3 where name='Africa';
1 row updated.
SQL> update country
2 set continent='EMEA'
3 where continent ='Africa';
1 row updated.
SQL> commit;
Commit complete.
CONTINENT
| NAME |
|---|
| EMEA |
COUNTRY
| NAME | CONTINENT |
|---|---|
| Benin | EMEA |
You can use triggers too, but constraints are more efficient than triggers.
insert/update/delete/merge LOG ERRORS
the following works only on 10gR2
Table PRODUCTCATEGORY:
| CATEGORY (PK) |
|---|
| Fruit |
| Fish |
Table TMP_PRODUCT:
| PRODUCT | CATEGORY |
|---|---|
| Banana | Fruit |
| Chicken | Meat |
Table PRODUCT:
| PRODUCT (PK) | CATEGORY (FK) |
|---|
Now I want to load product
SQL> insert into product select * from tmp_product;
insert into product select * from tmp_product
*
ERROR at line 1:
ORA-02291: integrity constraint (AUDBA.SYS_C0016368)
violated - parent key not found
Well, there is a missing category. I will fix it later. I want to load the valid data now.
SQL> exec dbms_errlog.create_error_log('PRODUCT')
PL/SQL procedure successfully completed.
SQL> insert into product select * from tmp_product
2 log errors reject limit unlimited;
1 row created.
Table PRODUCT:
| PRODUCT (PK) | CATEGORY (FK) |
|---|---|
| Banana | Fruit |
Now I have successfully loaded my valid data. I need to fix the problem. Let’s identify the offending row :
SQL> select ORA_ERR_OPTYP$,ORA_ERR_MESG$,CATEGORY
2 from err$_product;
ORA_ERR_OPTYP$ ORA_ERR_MESG$ CATEGORY
-------------- -------------------------------- --------
I ORA-02291: integrity constraint Meat
(AUDBA.SYS_C0016368) violated -
parent key not found
1 row selected.
Let’s create the missing category manually
SQL> insert into productcategory(category) values ('Meat');
1 row created.
SQL> insert into product
2 select * from tmp_product
3 where category='Meat';
1 row created.
Table PRODUCTCATEGORY:
| CATEGORY (PK) |
|---|
| Fruit |
| Fish |
| Meat |
Table PRODUCT:
| PRODUCT (PK) | CATEGORY (FK) |
|---|---|
| Banana | Fruit |
| Chicken | Meat |