Here is my scenario:
I want to write a procedure in oracle, there are four tables, tab1, tab2, tab3, err. tab1 has some data in two columns (id number, name varchar(250)), while others are empty.
schema for tab2 is
(id number, name varchar(50)), for tab3 (id number, name varchar(250)).
I want to insert data from tab1 to tab2 and when there is exception like name is greater than varchar(50), it will insert into tab3 and also insert error message into err table.
So all record from tab1 should be inserted into tab2 and tab3 accordingly using exception handling.
Here is what I tried but failed.
CREATE OR REPLACE
PROCEDURE exception_handler
IS
vSqlErr VARCHAR2(200) ;
vSqlCode VARCHAR2(5) ;
id2 NUMBER;
name2 VARCHAR(250);
BEGIN
INSERT ALL INTO tab3 VALUES
(id, name
)
SELECT * FROM tab1 t;
EXCEPTION
WHEN OTHERS THEN
INSERT INTO tab2 VALUES
(id, name
);
vSqlErr := SUBSTR(sqlerrm, 1, 200) ;
vSqlCode := SUBSTR(SQLCODE, 1, 5) ;
INSERT INTO err VALUES
(vSqlErr, vSqlCode
) ;
COMMIT ;
RAISE;
END;
This is just a simple demonstration based on your inputs in the question. Better go for BULK processing and SQL%BULK_EXCEPTIONS. And don't use WHEN OTHERS blindly.
Let's say you have an EMP table, and you have a check constraint on employee name as not more than 5 characters. There is an EMP_ERR table to log the error values and error message. Lets see a test case:
SQL> DROP TABLE emp_new PURGE;
Table dropped.
SQL> CREATE TABLE emp_new AS
2 SELECT * FROM emp WHERE 1 =2;
Table created.
SQL> ALTER TABLE emp_new ADD CONSTRAINT check_ename CHECK(LENGTH(ename)<=5);
Table altered.
SQL> DROP TABLE emp_err PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE emp_err
2 (
3 empno NUMBER,
4 ename VARCHAR2(100),
5 err_msg VARCHAR2(250)
6 );
Table created.
SQL> CREATE OR REPLACE
2 PROCEDURE p
3 (
4 v_empno NUMBER,
5 v_ename VARCHAR2
6 )
7 IS
8 vSqlErr VARCHAR2(200) ;
9 vSqlCode VARCHAR2(5) ;
10 empno2 NUMBER;
11 ename2 VARCHAR2(250);
12 BEGIN
13 INSERT INTO emp_new
14 (empno, ename
15 ) VALUES
16 (v_empno, v_ename
17 );
18 COMMIT;
19 EXCEPTION
20 WHEN OTHERS THEN
21 vSqlErr := SUBSTR(sqlerrm, 1, 200) ;
22 vSqlCode := SUBSTR(SQLCODE, 1, 5) ;
23 INSERT
24 INTO emp_err
25 (
26 empno,
27 ename,
28 err_msg
29 )
30 VALUES
31 (
32 v_empno,
33 v_ename,
34 vSqlErr
35 ||' - '
36 ||vSqlCode
37 );
38 COMMIT ;
39 raise;
40 END;
41 /
Procedure created.
Lets execute the procure with ename value as more than 5 characters, so that it raises an error, and we expect a row to be inserted into the emp_err table.
SQL> exec p(1, 'abcdef');
BEGIN p(1, 'abcdef'); END;
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.CHECK_ENAME) violated
ORA-06512: at "SCOTT.P", line 38
ORA-06512: at line 1
So, the error is raised. Lets see if it is logged in the error table.
SQL> column ename format a10
SQL> column err_msg format a100
SQL> set linesize 150
SQL> select * from emp_err;
EMPNO ENAME ERR_MSG
---------- ---------- ----------------------------------------------------------------
1 abcdef ORA-02290: check constraint (SCOTT.CHECK_ENAME) violated - -2290
SQL>
We have the error details logged.
Related
I would like to exchange the Oracle partition to another partitioned table. But to do that we are using interim non partitioned table. Can this be done in a different way?
Can't we exchange a partition from one partitioned table to another without using non-partitioned table as a medium? Can't we directly move a partition to target table?
Easy to put a little API together to make this simple. In this example, T1 and T2 are matching, but T1 and T3 are not
SQL>
SQL> create table t1
2 partition by list ( owner) automatic
3 ( partition px values ('SYS') )
4 as select * from dba_objects;
Table created.
SQL>
SQL> create table t2
2 partition by list ( owner) automatic
3 ( partition py values ('SYS') )
4 as select * from dba_objects;
Table created.
SQL>
SQL> create table t3
2 partition by list ( owner) automatic
3 ( partition pz values ('SYSTEM') )
4 as select * from dba_segments;
Table created.
SQL>
SQL> create sequence seq;
Sequence created.
SQL>
SQL> create or replace
2 procedure exchange_par(
3 p_table1 varchar2,
4 p_table2 varchar2,
5 p_table1_par varchar2,
6 p_table2_par varchar2) is
7 l_tab_cnt int;
8 l_hash_cnt int;
9 l_interim varchar2(100);
10 begin
11 select count(distinct tot), count(*)
12 into l_hash_cnt, l_tab_cnt
13 from
14 ( select table_name, sum(ora_hash(column_name)) tot
15 from user_tab_cols
16 where table_name in (p_table1,p_table2)
17 group by table_name
18 );
19
20 if l_tab_cnt != 2 or l_hash_cnt != 1 then
21 raise_application_error(-20000,'Dead in the water');
22 end if;
23 l_interim := 'TMPTAB$'||seq.nextval;
24
25 begin
26 execute immediate
27 'drop table '||l_interim||' purge';
28 exception
29 when others then
30 if sqlcode = -942 then null; else raise; end if;
31 end;
32
33 execute immediate
34 'create table '||l_interim||' for exchange with table '||p_table1;
35
36 execute immediate
37 'alter table '||p_table1||' exchange partition '||p_table1_par||
38 ' with table '||l_interim;
39
40 execute immediate
41 'alter table '||p_table2||' exchange partition '||p_table2_par||
42 ' with table '||l_interim;
43
44 execute immediate
45 'drop table '||l_interim||' purge';
46
47 end;
48 /
Procedure created.
SQL>
SQL> exec exchange_par('T1','T2','PX','PY');
PL/SQL procedure successfully completed.
SQL>
SQL> exec exchange_par('T1','T3','PX','PZ');
BEGIN exchange_par('T1','T3','PX','PZ'); END;
*
ERROR at line 1:
ORA-20000: Dead in the water
ORA-06512: at "MCDONAC.EXCHANGE_PAR", line 20
ORA-06512: at line 1
SQL>
SQL>
SQL>
SQL>
Can make this as robust as you like with more conditions etc.
DECLARE
TYPE norollno IS TABLE OF VARCHAR2(100);
rollno norollno;
BEGIN
BEGIN
SELECT token
BULK COLLECT INTO rollno
FROM tableA
WHERE columname='rollnoofstud';
EXCEPTION
WHEN NO_DATA_FOUND THEN
rollno := norollno();
END ;
IF rollno >0 THEN
FOR i IN rollno.FIRST..norollno.LAST
LOOP
<doSomeThing>
END LOOP;
END IF;
END;
I am trying this but I am not getting output. I doubt if my select statement is correct.
I don't have your table so I created one:
SQL> CREATE TABLE tablea
2 AS
3 SELECT ename AS token, 'rollnoofstud' AS columname
4 FROM emp
5 WHERE deptno = 10;
Table created.
Code you posted isn't that wrong; requires a little bit of fixing (see line #17, the way you check whether collection contains something (count it!); typo in FOR loop):
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 TYPE norollno IS TABLE OF VARCHAR2 (100);
3
4 rollno norollno;
5 BEGIN
6 BEGIN
7 SELECT token
8 BULK COLLECT INTO rollno
9 FROM tableA
10 WHERE columname = 'rollnoofstud';
11 EXCEPTION
12 WHEN NO_DATA_FOUND
13 THEN
14 rollno := norollno ();
15 END;
16
17 IF rollno.COUNT > 0
18 THEN
19 FOR i IN rollno.FIRST .. rollno.LAST
20 LOOP
21 DBMS_OUTPUT.put_line (rollno (i));
22 END LOOP;
23 END IF;
24 END;
25 /
CLARK --> here's the result
KING
MILLER
PL/SQL procedure successfully completed.
SQL>
[EDIT: with your sample table and data:]
(note that there's no text datatype in Oracle!)
SQL> CREATE TABLE students
2 (
3 rollnostud INTEGER PRIMARY KEY,
4 name VARCHAR2 (10) NOT NULL,
5 gender VARCHAR2 (1) NOT NULL
6 );
Table created.
SQL> INSERT INTO students
2 VALUES (1, 'Ryan', 'M');
1 row created.
SQL> INSERT INTO students
2 VALUES (2, 'Joanna', 'F');
1 row created.
SQL> INSERT INTO students
2 VALUES (3, 'John', 'M');
1 row created.
Procedure:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 TYPE norollno IS TABLE OF VARCHAR2 (100);
3
4 rollno norollno;
5 BEGIN
6 BEGIN
7 SELECT name
8 BULK COLLECT INTO rollno
9 FROM students;
10 EXCEPTION
11 WHEN NO_DATA_FOUND
12 THEN
13 rollno := norollno ();
14 END;
15
16 IF rollno.COUNT > 0
17 THEN
18 FOR i IN rollno.FIRST .. rollno.LAST
19 LOOP
20 DBMS_OUTPUT.put_line (rollno (i));
21 END LOOP;
22 END IF;
23 END;
24 /
Ryan
Joanna
John
PL/SQL procedure successfully completed.
SQL>
I am creating a stored procedure in Oracle database that's resulting in error "ORA-01858: a non-numeric character was found where a numeric was expected".
My procedure is as below:
create or replace procedure testProc(
id IN VARCHAR2,
user IN VARCHAR2,
sender IN VARCHAR2
)
as
vCount number;
begin
select count(*) into vCount from table1 where id='12345'
if vCount=0
insert into table1 (id, user, sender, status) values (id, user, partner, status);
else
update table1 set status='ERR' where id='12345'
end if;
end procedure;
Error: ORA-01858: a non-numeric character was found where a numeric was expected
I tried replacing vCount as int that did not help. Also tried declaring vCount below sender IN VARCHAR2.
Can someone please tell what is correct way to use the above procedure.
Use a MERGE statement then you can do it in a single statement (rather than SELECT followed by either INSERT or UPDATE):
CREATE PROCEDURE testProc(
i_id IN table1.id%TYPE,
i_user IN table1."USER"%TYPE,
i_sender IN table1.sender%TYPE,
i_status IN table1.status%TYPE
)
AS
BEGIN
MERGE INTO table1 dst
USING (
SELECT '12345' AS id
FROM DUAL
) src
ON (src.id = dst.id)
WHEN MATCHED THEN
UPDATE SET status = 'Err'
WHEN NOT MATCHED THEN
INSERT (id, "USER", sender, status)
VALUES (i_id, i_user, i_sender, i_status);
END testProc;
/
db<>fiddle here
This code can't possibly return error you specified because
procedure is invalid (mising statement terminators; column name can't be USER because it is a keyword, reserved for currently logged user)
that error code is related to date issues, while - in your code - there's nothing that looks like a date
Therefore, it is impossible to help you with error you stated. Otherwise, consider NOT naming procedure's parameters the same as column names because that leads to various problems.
Something like this would work, but it is not related to error you got.
Sample table:
SQL> CREATE TABLE table1
2 (
3 id VARCHAR2 (5),
4 c_user VARCHAR2 (20),
5 partner VARCHAR2 (10),
6 sender VARCHAR2 (10),
7 status VARCHAR2 (5)
8 );
Table created.
SQL>
Procedure:
SQL> CREATE OR REPLACE PROCEDURE testProc (p_id IN VARCHAR2,
2 p_user IN VARCHAR2,
3 p_sender IN VARCHAR2)
4 AS
5 vCount NUMBER;
6 BEGIN
7 SELECT COUNT (*)
8 INTO vCount
9 FROM table1
10 WHERE id = p_id;
11
12 IF vCount = 0
13 THEN
14 INSERT INTO table1 (id,
15 c_user,
16 sender,
17 status)
18 VALUES (p_id,
19 p_user,
20 NULL,
21 'NEW');
22 ELSE
23 UPDATE table1
24 SET status = 'ERR'
25 WHERE id = p_id;
26 END IF;
27 END testproc;
28 /
Procedure created.
Testing:
SQL> EXEC testproc('12345', 'Little', 'Foot');
PL/SQL procedure successfully completed.
SQL> SELECT * FROM table1;
ID C_USER PARTNER SENDER STATU
----- -------------------- ---------- ---------- -----
12345 Little NEW
SQL> EXEC testproc('12345', 'Little', 'Foot');
PL/SQL procedure successfully completed.
SQL> SELECT * FROM table1;
ID C_USER PARTNER SENDER STATU
----- -------------------- ---------- ---------- -----
12345 Little ERR
SQL>
I would like to know if it is possible to check a table for two conditions in a trigger.
More precisely, I want to decide what to enter based on entries from two columns.
create or replace TRIGGER AUDIT_TABLE1
-- starts on every update or insert
AFTER INSERT OR UPDATE ON table1
FOR EACH ROW
DECLARE
v_user varchar2(30);
v_userid USERS.UUID%TYPE;
BEGIN
v_user := SYS_CONTEXT('APEX$SESSION','APP_USER');
select UUID into v_userid from USERS where lower(username)=lower(v_user);
IF :NEW.COLUMN4 ='REQUESTED' AND :NEW.COLUMN5='TEXT123' THEN
INSERT INTO AUDIT_TABLE1
(COLUMN1,COLUMN2,COLUMN3)
VALUES
(:NEW.COLUMN1, :NEW.COLUMN2, 'TEXT123');
ELSIF :NEW.COLUMN4 ='REQUESTED' AND :NEW.COLUMN5='TEXT3265' THEN
INSERT INTO AUDIT_TABLE1
(COLUMN1,COLUMN2,COLUMN3)
VALUES
(:NEW.COLUMN1, :NEW.COLUMN2, 'TEXT52354');
END IF;
END;
I'll get no error when compiling this trigger but I as well get no entry in the AUDIT_TABLE1 table.
The principle is OK, which means that you either didn't post what you really have (which might be the case, regarding column names), or you didn't insert what you should.
Sample tables:
SQL> CREATE TABLE table1
2 (
3 column4 VARCHAR2 (20),
4 column5 VARCHAR2 (20)
5 );
Table created.
SQL> CREATE TABLE audit_table
2 (
3 column4 VARCHAR2 (20),
4 column5 VARCHAR2 (20),
5 column6 VARCHAR2 (20)
6 );
Table created.
SQL>
Trigger:
SQL> CREATE OR REPLACE TRIGGER audit_table1
2 -- starts on every update or insert
3 AFTER INSERT OR UPDATE
4 ON table1
5 FOR EACH ROW
6 DECLARE
7 v_user VARCHAR2 (30);
8 BEGIN
9 v_user := SYS_CONTEXT ('APEX$SESSION', 'APP_USER');
10
11 IF :new.column4 = 'REQUESTED'
12 AND :new.column5 = 'TEXT123'
13 THEN
14 INSERT INTO audit_table (column4, column5, column6)
15 VALUES (:new.column4, :new.column5, 'TEXT123');
16 ELSIF :new.column4 = 'REQUESTED'
17 AND :new.column5 = 'TEXT3265'
18 THEN
19 INSERT INTO audit_table (column4, column5, column6)
20 VALUES (:new.column4, :new.column5, 'TEXT52354');
21 END IF;
22 END;
23 /
Trigger created.
Testing:
These values won't insert anything because none of IF conditions is met:
SQL> INSERT INTO table1
2 VALUES ('x', 'y');
1 row created.
SQL> SELECT * FROM audit_table;
no rows selected
SQL>
These values will insert a row into the audit table:
SQL> INSERT INTO table1
2 VALUES ('REQUESTED', 'TEXT123');
1 row created.
SQL> SELECT * FROM audit_table;
COLUMN4 COLUMN5 COLUMN6
-------------------- -------------------- --------------------
REQUESTED TEXT123 TEXT123
SQL>
I am trying to get all the new values and old values in trigger before inserting it to another table but i could just get the specific value not the all the data.
I tried with :NEW.* and :OLD.* it works fine for postgress but not for oracle.
CREATE OR REPLACE TRIGGER trg_customer_tbl_ib01
BEFORE INSERT OR UPDATE OR DELETE ON TEST.customer_test
FOR EACH ROW
DECLARE
vAction VARCHAR2(4000) := null;
vFound INT := null;
vREC TEST.CUSTOMER_TEST%ROWTYPE;
BEGIN
IF INSERTING OR UPDATING THEN
select :NEW.* into vREC FROM DUAL;
ELSIF DELETING THEN
select :OLD.* into vREC FROM DUAL;
END IF;
IF DELETING THEN
INSERT INTO TEST.CUSTOMER_TEST_AUDIT SELECT
CUSTOMER_TEST_AUDIT_id_seq.NEXTVAL,'D',SYSDATE, vREC.* FROM DUAL;
ELSIF UPDATING THEN
INSERT INTO TEST.CUSTOMER_TEST_AUDIT SELECT
CUSTOMER_TEST_AUDIT_id_seq.NEXTVAL,'U',SYSDATE, vREC.*FROM DUAL;
ELSIF INSERTING THEN
INSERT INTO TEST.CUSTOMER_TEST_AUDIT SELECT
CUSTOMER_TEST_AUDIT_id_seq.NEXTVAL,'I',SYSDATE, vREC.* FROM DUAL;
END IF;
END;
I am getting error :
Error(10,8): PLS-00049: bad bind variable 'NEW.'
Well, don't be impatient. Don't use shortcuts.
Test case:
SQL> create table test as select * From dept;
Table created.
SQL> create table test_log as select * From dept where 1 = 2;
Table created.
SQL> alter table test_log add (id number, action varchar2(1), datum date);
Table altered.
SQL> create sequence seqlog;
Sequence created.
SQL>
Trigger. Reference each column separately. The fact that :NEW.* works in Postgres doesn't mean it works in Oracle.
SQL> create or replace trigger trg_test
2 before insert or update or delete on test
3 for each row
4 begin
5 if updating then
6 insert into test_log (deptno, dname, loc, id, action, datum)
7 values
8 (:old.deptno, :old.dname, :old.loc, seqlog.nextval, 'U', sysdate);
9 insert into test_log (deptno, dname, loc, id, action, datum)
10 values
11 (:new.deptno, :new.dname, :new.loc, seqlog.nextval, 'U', sysdate);
12 elsif inserting then
13 insert into test_log (deptno, dname, loc, id, action, datum)
14 values
15 (:new.deptno, :new.dname, :new.loc, seqlog.nextval, 'I', sysdate);
16 elsif deleting then
17 insert into test_log (deptno, dname, loc, id, action, datum)
18 values
19 (:old.deptno, :old.dname, :old.loc, seqlog.nextval, 'D', sysdate);
20 end if;
21 end;
22 /
Trigger created.
SQL>
Testing:
SQL> alter session set nls_Date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> update test set loc = 'Croatia' where deptno = 10;
1 row updated.
SQL> delete from test where deptno = 20;
1 row deleted.
SQL> insert into test (deptno, dname, loc) values (99, 'IT', 'Zagreb');
1 row created.
SQL> select * From test_log order by id;
DEPTNO DNAME LOC ID A DATUM
---------- -------------- ------------- ---------- - -------------------
10 ACCOUNTING NEW YORK 5 U 20.05.2019 21:44:33
10 ACCOUNTING Croatia 6 U 20.05.2019 21:44:33
20 RESEARCH DALLAS 7 D 20.05.2019 21:44:43
99 IT Zagreb 8 I 20.05.2019 21:44:52
SQL>