Log all errors for a value into error log if a single row fails - oracle

I am trying to maintain data integrity and logging errors to an error table. I've got 3 tables with unique constraints and 1 error table:
create table tbl_one (
pass_no number,
constraint tbl_one_u01 unique (pass_no) );
insert into tbl_one values(10);
insert into tbl_one values(20);
create table tbl_two (
cus_no number,
cus_name varchar2(50),
pass_no number,
constraint tbl_two_u01 unique (cus_no) );
insert into tbl_two values( 101, 'NameX',10);
insert into tbl_two values( 102, 'NameY',10);
insert into tbl_two values( 103, 'NameZ',20);
create table tbl_target (
cus_no number,
pass_no number,
constraint tbl_target_u01 unique (cus_no),
constraint tbl_target_u02 unique (pass_no));
exec dbms_errlog.create_error_log('tbl_target','tbl_target_err');
I am trying to log all ORA-00001 errors to the error table tbl_target_err like this:
begin
insert into tbl_target
select a.pass_no, b.cus_no
from tbl_one a
inner join tbl_two b on b.pass_no = a.pass_no
log errors into tbl_target_err reject limit 10;
end;
The result is:
select * from tbl_target;
-------------------
CUS_NO PASS_NO
101 10
103 20
and the error table:
CUS_NO PASS_NO
102 10
I need all the violated errors to go into the error table. If the value of pass_no 10 is violated then all 10 values should inserted into the error table; not, one into target and one to error table. I don't want to use exists statements because I won't able to log all violated values.
How could I go about doing this?

You can't use the error logging mechanism for this as it isn't designed to support it. It errors at the point it tries to create the duplicate in the table - the first value it tries to insert for pass_no 10 is valid on its own - so it would have to distinguish between data that already existed and multiple values coming from the insert, to start with. So you'd need to roll your own.
One option is to create your own table to hold the duplicates, and use an insert all to decide which values belong in each table:
create table tbl_target_dup (
cus_no number,
pass_no number
);
insert all
when cus_count = 1 and pass_count = 1 then
into tbl_target values (cus_no, pass_no)
else
into tbl_target_dup values (cus_no, pass_no)
select a.pass_no, b.cus_no,
count(*) over (partition by a.pass_no) as pass_count,
count(*) over (partition by b.cus_no) as cus_count
from tbl_one a
join tbl_two b on b.pass_no = a.pass_no;
This allows you to have more columns than those affected by the PK/UK, and insert them only into the real table if you prefer, or a subset into the 'error' table. With just those two columns in each table you get:
select * from tbl_target;
CUS_NO PASS_NO
---------- ----------
103 20
select * from tbl_target_dup;
CUS_NO PASS_NO
---------- ----------
101 10
102 10
SQL Fiddle demo.
You could do the same thing with two inserts based on the same select, one with a subquery checking that both counts are 1, other checking that at least one is not, but this might perform better.

Related

Oracle Inserting a row in a table only if a match found in some other table

I have 3 tables in my database namely employees,students and Images
create table employees(id number primary key,name varchar2(100), address varchar2(100));
create table students(id number primary key,name varchar2(100),address varchar2(100));
create table Images (image_id number primary key,employee_id number,student_id number,image_name varchar2(100));
Insert into employees values (1,'asdfasd','asdfasdf');
Insert into employees values (2,'asdfasd','asdfasdf');
Insert into employees values (3,'asdfasd','asdfasdf');
Insert into employees values (4,'asdfasd','asdfasdf');
Insert into employees values (5,'asdfasd','asdfasdf');
Insert into students values (1,'asdfasd','asdfasdf');
Insert into students values (2,'asdfasd','asdfasdf');
Insert into students values (3,'asdfasd','asdfasdf');
Insert into students values (4,'asdfasd','asdfasdf');
Insert into students values (5,'asdfasd','asdfasdf');
Insert into students values (49,'asdfasd','asdfasdf');
Insert into Images(image_id,employee_id,image_name) values (1,5,'adsfasdfasdf');
Insert into Images(image_id,student_id,image_name) values (2,49,'asfasdfasdf');
Now, when Inserting a row into the Images table I should check whether the employee_id / Student_id is existed in the employees table/student table, If a match found then only I have to Insert else it should not.
I thought of adding two foreign keys on the images table as follows:
alter table images add constraint fk_employeeid foreign key(employee_id)
references employees(id);
alter table images add constraint fk_studentsid foreign key(student_id)
references students(id);
But, If I do so. It will not allow me to insert null values. How can I modify my design so that whenever I insert a row in images table either it should be an employee_id or an student_id.
If I created any confusion, here is the link for the sql fiddle,http://www.sqlfiddle.com/#!4/92d24/1/0
I thought of adding two foreign keys on the images table.
But, If I do so. It will not allow me to insert null values.
You are wrong when you say the foreign key constraint won't allow NULL values. It will definitely allow the NULL values.
Test case
Let's add the foreign key constraint on the IMAGES table for the employee_id and student_id.
ALTER TABLE images ADD CONSTRAINT fk_emp FOREIGN KEY(employee_id) REFERENCES employees(ID);
ALTER TABLE images ADD CONSTRAINT fk_stu FOREIGN KEY(student_id) REFERENCES students(ID);
Let's check the INSERT with NULL values:
SQL> INSERT INTO Images(image_id,employee_id,image_name) VALUES (1,5,'adsfasdfasdf');
1 row created.
SQL> INSERT INTO Images(image_id,student_id,image_name) VALUES (2,49,'asfasdfasdf');
1 row created.
SQL> INSERT INTO Images(image_id,employee_id,image_name) VALUES (3,null,'adsfasdfasdf');
1 row created.
SQL> INSERT INTO Images(image_id,student_id,image_name) VALUES (4,null,'asfasdfasdf');
1 row created.
SQL>
SQL> COMMIT;
Commit complete.
SQL>
SQL> SELECT * FROM images;
IMAGE_ID EMPLOYEE_ID STUDENT_ID IMAGE_NAME
---------- ----------- ---------- ---------------
1 5 adsfasdfasdf
2 49 asfasdfasdf
3 adsfasdfasdf
4 asfasdfasdf
SQL>
Let's check the foreign key constraint validation:
SQL> INSERT INTO Images(image_id,employee_id,image_name) VALUES (1,10,'adsfasdfasdf');
INSERT INTO Images(image_id,employee_id,image_name) VALUES (1,10,'adsfasdfasdf')
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.SYS_C0010739) violated
SQL> INSERT INTO Images(image_id,student_id,image_name) VALUES (2,20,'asfasdfasdf');
INSERT INTO Images(image_id,student_id,image_name) VALUES (2,20,'asfasdfasdf')
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.SYS_C0010739) violated
SQL>
So, everything works as per the design.
Add constraints to images like you did. Add one more constraint:
alter table images add constraint chk_nulls
check (
(employee_id is not null and student_id is null)
or (employee_id is null and student_id is not null)
);
This way you cannot insert both nulls nor both not nulls for employee_id and student_id. And foreign keys are also checked.
Test:
insert into images values (1, 1, null, 'not important'); -- OK
insert into images values (2, null, null, 'not important'); -- error
insert into images values (3, 1, 1, 'not important'); -- error
insert into images values (4, null, 1, 'not important'); -- OK
I would keep the foreign keys, that is the correct approach.
But instead of inserting NULL's, you should define an "unknown record" in your EMPLOYEES and STUDENTS tables (maybe with an id=-1, name='UNKNOWN'), and insert -1 into your IMAGES table.
I recommend you to change the primary key in table images, and have as PK three columns (image_id, employee_id and student_id).
Edit 1: Based on comment
I think you planned in a wrong way the problem. If the images are for both, employees and students, you should have two tables, images_employees and images_students, with respectively foreign keys employee_id and student_id and of course an id for the image.
But is there any sense to have a row like this?
id: 1 student_id: null image_name: 'something'
I don't think so... please explain more about the purpose of images table.

insert multiple row into table using select however table has primery key in oracle SQL [duplicate]

This question already has answers here:
How to create id with AUTO_INCREMENT on Oracle?
(18 answers)
Closed 8 years ago.
I am facing issue while inserting multiple row in one go into table because column id has primary key and its created based on sequence.
for ex:
create table test (
iD number primary key,
name varchar2(10)
);
insert into test values (123, 'xxx');
insert into test values (124, 'yyy');
insert into test values (125, 'xxx');
insert into test values (126, 'xxx');
The following statement creates a constraint violoation error:
insert into test
(
select (SELECT MAX (id) + 1 FROM test) as id,
name from test
where name='xxx'
);
This query should insert 3 rows in table test (having name=xxx).
You're saying that your query inserts rows with primary key ID based on a sequence. Yet, in your insert/select there is select (SELECT MAX (id) + 1 FROM test) as id, which clearly is not based on sequence. It may be the case that you are not using the term "sequence" in the usual, Oracle way.
Anyway, there are two options for you ...
Create a sequence, e.g. seq_test_id with the starting value of select max(id) from test and use it (i.e. seq_test_id.nextval) in your query instead of the select max(id)+1 from test.
Fix the actual subselect to nvl((select max(id) from test),0)+rownum instead of (select max(id)+1 from test).
Please note, however, that the option 2 (as well as your original solution) will cause you huge troubles whenever your code runs in multiple concurrent database sessions. So, option 1 is strongly recommended.
Use
insert into test (
select (SELECT MAX (id) FROM test) + rownum as id,
name from test
where name='xxx'
);
as a workaround.
Of course, you should be using sequences for integer-primary keys.
If you want to insert an ID/Primary Key value generated by a sequence you should use the sequence instead of selecting the max(ID)+1.
Usually this is done using a trigger on your table wich is executed for each row. See sample below:
CREATE TABLE "MY_TABLE"
(
"MY_ID" NUMBER(10,0) CONSTRAINT PK_MY_TABLE PRIMARY KEY ,
"MY_COLUMN" VARCHAR2(100)
);
/
CREATE SEQUENCE "S_MY_TABLE"
MINVALUE 1 MAXVALUE 999999999999999999999999999
INCREMENT BY 1 START WITH 10 NOCACHE ORDER NOCYCLE NOPARTITION ;
/
CREATE OR REPLACE TRIGGER "T_MY_TABLE"
BEFORE INSERT
ON
MY_TABLE
REFERENCING OLD AS OLDEST NEW AS NEWEST
FOR EACH ROW
WHEN (NEWEST.MY_ID IS NULL)
DECLARE
IDNOW NUMBER;
BEGIN
SELECT S_MY_TABLE.NEXTVAL INTO IDNOW FROM DUAL;
:NEWEST.MY_ID := IDNOW;
END;
/
ALTER TRIGGER "T_MY_TABLE" ENABLE;
/
insert into MY_TABLE (MY_COLUMN) values ('DATA1');
insert into MY_TABLE (MY_COLUMN) values ('DATA2');
insert into MY_TABLE (MY_ID, MY_COLUMN) values (S_MY_TABLE.NEXTVAL, 'DATA3');
insert into MY_TABLE (MY_ID, MY_COLUMN) values (S_MY_TABLE.NEXTVAL, 'DATA4');
insert into MY_TABLE (MY_COLUMN) values ('DATA5');
/
select * from MY_TABLE;

Oracle Partition - Error ORA14400 - inserted partition key does not map to any partition

I'm trying to insert information in a partition table, but I don't know what I'm doing wrong!
Show me this error: ORA-14400: inserted partition key does not map to any partition"
The table dba_tab_partitions shows this informations below:
1 PDIA_98_20091023 0
2 PDIA_98_20091022 0
3 PDIA_98_20091021 0
4 PDIA_98_20091020 0
5 PDIA_98_20091019 0
Please help me rs
select partition_name,column_name,high_value,partition_position
from ALL_TAB_PARTITIONS a , ALL_PART_KEY_COLUMNS b
where table_name='YOUR_TABLE' and a.table_name = b.name;
This query lists the column name used as key and the allowed values. make sure, you insert the allowed values(high_value). Else, if default partition is defined, it would go there.
EDIT:
I presume, your TABLE DDL would be like this.
CREATE TABLE HE0_DT_INF_INTERFAZ_MES
(
COD_PAIS NUMBER,
FEC_DATA NUMBER,
INTERFAZ VARCHAR2(100)
)
partition BY RANGE(COD_PAIS, FEC_DATA)
(
PARTITION PDIA_98_20091023 VALUES LESS THAN (98,20091024)
);
Which means I had created a partition with multiple columns which holds value less than the composite range (98,20091024);
That is first COD_PAIS <= 98 and Also FEC_DATA < 20091024
Combinations And Result:
98, 20091024 FAIL
98, 20091023 PASS
99, ******** FAIL
97, ******** PASS
< 98, ******** PASS
So the below INSERT fails with ORA-14400; because (98,20091024) in INSERT is EQUAL to the one in DDL but NOT less than it.
SQL> INSERT INTO HE0_DT_INF_INTERFAZ_MES(COD_PAIS, FEC_DATA, INTERFAZ)
VALUES(98, 20091024, 'CTA'); 2
INSERT INTO HE0_DT_INF_INTERFAZ_MES(COD_PAIS, FEC_DATA, INTERFAZ)
*
ERROR at line 1:
ORA-14400: inserted partition key does not map to any partition
But, we I attempt (97,20091024), it goes through
SQL> INSERT INTO HE0_DT_INF_INTERFAZ_MES(COD_PAIS, FEC_DATA, INTERFAZ)
2 VALUES(97, 20091024, 'CTA');
1 row created.
For this issue need to add the partition for date column values, If last partition 20201231245959, then inserting the 20210110245959 values, this issue will occurs.
For that need to add the 2021 partition into that table
ALTER TABLE TABLE_NAME ADD PARTITION PARTITION_NAME VALUES LESS THAN (TO_DATE('2021-12-31 24:59:59', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) NOCOMPRESS

Oracle 'INSERT ALL' ignore duplicates

I have a database table with a unique constraint on it (unique (DADSNBR, DAROLEID) pair). I am going to be inserting multiple values into this table simultaneously, so I'd like to get it done using one query - I'm assuming this would be the faster way. My query is thus:
INSERT ALL
INTO ACCESS (DADSNBR, DAROLEID) VALUES (68, 1)
INTO ACCESS (DADSNBR, DAROLEID) VALUES (68, 2)
INTO ACCESS (DADSNBR, DAROLEID) VALUES (68, 3)
INTO ACCESS (DADSNBR, DAROLEID) VALUES (68, 4)
SELECT 1 FROM DUAL
Since there are some entries within the statement that are duplicates of those already in the database, the whole insert fails and none of the rows are inserted.
Is there a way to ignore the cases where the unique constraint fails, and just insert the ones that are unique, without having to split it up into individual INSERT statements?
Edit: I realised I probably don't want to do this anyway, but I'm still curious as to whether it's possible or not.
In Oracle, statements either succeed completely or fail completely (they are atomic). However, you can add clauses in certain cases to log exceptions instead of raising errors:
using BULK COLLECT - SAVE EXCEPTIONS, as demonstrated in this thread on askTom,
or using DBMS_ERRLOG (available since 10g I think).
The second method is all automatic, here's a demo (using 11gR2):
SQL> CREATE TABLE test (pk1 NUMBER,
2 pk2 NUMBER,
3 CONSTRAINT pk_test PRIMARY KEY (pk1, pk2));
Table created.
SQL> /* Statement fails because of duplicate */
SQL> INSERT into test (SELECT 1, 1 FROM dual CONNECT BY LEVEL <= 2);
ERROR at line 1:
ORA-00001: unique constraint (VNZ.PK_TEST) violated
SQL> BEGIN dbms_errlog.create_error_log('TEST'); END;
2 /
PL/SQL procedure successfully completed.
SQL> /* Statement succeeds and the error will be logged */
SQL> INSERT into test (SELECT 1, 1 FROM dual CONNECT BY LEVEL <= 2)
2 LOG ERRORS REJECT LIMIT UNLIMITED;
1 row(s) inserted.
SQL> select ORA_ERR_MESG$, pk1, pk2 from err$_test;
ORA_ERR_MESG$ PK1 PK2
--------------------------------------------------- --- ---
ORA-00001: unique constraint (VNZ.PK_TEST) violated 1 1
You can use the LOG ERROR clause with INSERT ALL (thanks #Alex Poole), but you have to add the clause after each table:
SQL> INSERT ALL
2 INTO test VALUES (1, 1) LOG ERRORS REJECT LIMIT UNLIMITED
3 INTO test VALUES (1, 1) LOG ERRORS REJECT LIMIT UNLIMITED
4 (SELECT * FROM dual);
0 row(s) inserted.
Use the MERGE statement to handle this situation:
merge into "ACCESS" a
using
(
select 68 as DADSNBR,1 as DAROLEID from dual union all
select 68,2 from dual union all
select 68,3 from dual union all
select 68,4 from dual
) t
on (t.DADSNBR = a.DADSNBR and t.DAROLEID = a.DAROLEID)
when not matched then
insert (DADSNBR, DAROLEID)
values (t.DADSNBR, t.DAROLEID);

SQL Loader like errors in a table

I have external tables. And I'd like to extract the data from those tables and insert/merge that data in other tables.
Now when a select from => insert into query or merge query runs then it is possible (and likely possible) that the data might be in a bad quality, which will result into breaking query. Say there is 000000 as date in external table which will result breaking query if I am merging data.
How can I log these errors in a table (for example) error table which will log the error, reason of error, line number and column name? Just like you see in SQL Loader logs. For example:
Record 2324: Rejected - Error on table AA_STAG_VR_01, column KS1.
ORA-01843: not a valid month
And the query shouldn't break upon rather. Rather log the error and move on like it happens in SQL Loader.
Is it possible? I tried to look around net but I wasn't unable to find anything, or maybe I simply don't know the magic words
Thanks in advance :-)
EDIT:
Ok, I was able to solve the problem (well, partly) using the following approach.
CREATE TABLE error_table (
ora_err_number$ NUMBER,
ora_err_mesg$ VARCHAR2(2000),
ora_err_rowid$ ROWID,
ora_err_optyp$ VARCHAR2(2),
ora_err_tag$ VARCHAR2(2000)
)
INSERT INTO destination_table (column)
SELECT column FROM external_table
LOG ERRORS INTO error_table REJECT LIMIT UNLIMITED
This gives me:
SELECT * FROM error_table;
----------------------------------------------------------------------------------------------------------------------------------------------------------
ORA_ERR_NUMBER$ | ORA_ERR_MESG$ | ORA_ERR_ROWID$ | ORA_ERR_OPTYP$ | ORA_ERR_TAG$ |
----------------------------------------------------------------------------------------------------------------------------------------------------------
12899 |ORA-12899: value too large for column "SYSTEM"."destination_table"."column"
So far, so good. However, I would like to know what record number (line number in external_table) has this error. Because it is possible that fist 10 records went ok but 11th record was bad.
Check out FORALL + SAVE EXCEPTIONS clause. It might help you.
15:57:02 #> conn hr/hr#vm_xe
Connected.
15:57:15 HR#vm_xe> create table err_test(unique_column number primary key);
Table created.
Elapsed: 00:00:01.51
15:57:46 HR#vm_xe> EXECUTE DBMS_ERRLOG.CREATE_ERROR_LOG('err_test', 'errlog');
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.46
15:59:22 HR#vm_xe> insert into err_test select mod(rownum, 2) from dual connect by rownum < 10
16:00:00 2 log errors into errlog ('test') reject limit unlimited;
2 rows created.
Elapsed: 00:00:00.87
16:00:27 HR#vm_xe> commit;
Commit complete.
Elapsed: 00:00:00.00
16:02:37 HR#vm_xe> col ora_err_mesg$ for a75
16:02:43 HR#vm_xe> col unique_column for a10
16:02:47 HR#vm_xe> select unique_column, ora_err_mesg$ from errlog;
UNIQUE_COL ORA_ERR_MESG$
---------- ---------------------------------------------------------------------------
1 ORA-00001: unique constraint (HR.SYS_C007056) violated
0 ORA-00001: unique constraint (HR.SYS_C007056) violated
1 ORA-00001: unique constraint (HR.SYS_C007056) violated
0 ORA-00001: unique constraint (HR.SYS_C007056) violated
1 ORA-00001: unique constraint (HR.SYS_C007056) violated
0 ORA-00001: unique constraint (HR.SYS_C007056) violated
1 ORA-00001: unique constraint (HR.SYS_C007056) violated
7 rows selected.
Elapsed: 00:00:00.03
Below some syntax, you have reject limit as in sqlloader, you have log files, bad files etc.
CREATE TABLE <table_name> (
<column_definitions>)
ORGANIZATION EXTERNAL
(TYPE oracle_loader
DEFAULT DIRECTORY <oracle_directory_object_name>
ACCESS PARAMETERS (
RECORDS DELIMITED BY newline
BADFILE <file_name>
DISCARDFILE <file_name>
LOGFILE <file_name>
[READSIZE <bytes>]
[SKIP <number_of_rows>
FIELDS TERMINATED BY '<terminator>'
REJECT ROWS WITH ALL NULL FIELDS
MISSING FIELD VALUES ARE NULL
(<column_name_list>))\
LOCATION ('<file_name>'))
[PARALLEL]
REJECT LIMIT <UNLIMITED | integer>;
See here some examples, and here the docs

Resources