Instead of trigger with Oracle sequence - oracle

I have some old applications (legacy applications) which uses their own MAX NUMBER table. The problem is that we cannot modify and release all the applications at the same time. And therefore, we would like to do it in phases.
I came up with the following proposal. But, I do not know why Oracle executes the view's select query when I am executing the update statement.
Current usage:
-- Update the max number table
Update Sysadm.DummyTable
Set MaxNumber = MaxNumber + 1;
-- An application needs to select current value from the MaxNumber table.
Select * From Sysadm.DummyTable;
Proposed solution:
CREATE SEQUENCE SYSADM.SEQ_DummyTable START WITH 12345 INCREMENT BY 1;
--- Common function ---
--- The following function will get the current sequence number of the Oracle sequence across all the sessions.
CREATE OR REPLACE FUNCTION Sysadm.GetCurrValue(sLSeqName VARCHAR)
RETURN NUMBER
AS nLDummy NUMBER;
BEGIN
if (sLSeqName = 'SEQ_DUMMYTABLE') Then
SELECT SEQ_DUMMYTABLE.CURRVAL Into nLDummy FROM DUAL;
Else
Return null;
End If;
RETURN(nLDummy);
END;
/
--- The existing DummyTable table will be dropped and DummyTable (exactly with the same name including its structure) view will be created.
Create or Replace View Sysadm.DummyTable (MAXNUMBER)
As
Select sysadm.GetCurrValue('SEQ_DUMMYTABLE') As MAXNUMBER From Sysadm.ABC Where RowNum = 1;
--- The following trigger will get executed when the application tried to update the DummyTable table. This trigger will get executed instead of update statement from the application.
Create Or Replace Trigger Sysadm.TR_DummyTable
Instead of UPDATE ON Sysadm.DummyTable
Declare
nLDummy NUMBER;
Begin
SELECT SEQ_DUMMYTABLE.NEXTVAL Into nLDummy FROM DUAL;
End;
/
I have Instead of trigger on the view. However, When I am trying to execute the following update query I get an error message.
-- Update the max number table
Update Sysadm.DummyTable
Set MaxNumber = MaxNumber + 1;
ORA error message:
Error starting at line : 1 in command -
Update Sysadm.DUMMYTABLE Set MaxNumber = MaxNumber + 1
Error report -
SQL Error: ORA-08002: sequence SEQ_DUMMYTABLE.CURRVAL is not yet defined in this session
ORA-06512: at "SYSADM.GETCURRVALUE", line 10
08002. 00000 - "sequence %s.CURRVAL is not yet defined in this session"
*Cause: sequence CURRVAL has been selected before sequence NEXTVAL
*Action: select NEXTVAL from the sequence before selecting CURRVAL

As Oracle told you: you can't fetch currval if you didn't fetch from the sequence within this session, and you do that by selecting nextval. For example:
SQL> create sequence seqa;
Sequence created.
This doesn't work - you already know that, you hit the error already:
SQL> select seqa.currval from dual;
select seqa.currval from dual
*
ERROR at line 1:
ORA-08002: sequence SEQA.CURRVAL is not yet defined in this session
So: nextval first, currval next (if you need it):
SQL> select seqa.nextval from dual;
NEXTVAL
----------
1
SQL> select seqa.currval from dual;
CURRVAL
----------
1
SQL>
In your case, it means that this would look like
if (sLSeqName = 'SEQ_DUMMYTABLE') Then
SELECT SEQ_DUMMYTABLE.NEXTVAL Into nLDummy FROM DUAL;
Else -------

By just changing the function we can get it to work. This will fix the concurrency issue as well:
--- The following function will get the current sequence number of the Oracle sequence across all the sessions.
CREATE OR REPLACE FUNCTION Sysadm.GetCurrValue(sLSeqName VARCHAR)
RETURN NUMBER
AS nLDummy NUMBER;
BEGIN
if (sLSeqName = 'SEQ_DUMMYTABLE') Then
SELECT SEQ_DUMMYTABLE.CURRVAL Into nLDummy FROM DUAL;
Else
Return null;
End If;
RETURN(nLDummy);
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END;
/

Related

Executing a procedure in Oracle

I'm trying to write a procedure that inserts paid invoices from the paid_invoices table into the invoice_archive table. Only those paid invoices that are older than or equal to 31-05-2014 should be transferred.
Here's my procedure:
SQL> create or replace procedure paid_invoice_transfer as
cursor paid is
select *
from paid_invoices
where invoice_total = credit_total + payment_total
and payment_date <= '2014-05-31';
invoice_archive_text paid%rowtype;
begin
for invoice_archive_text in paid loop
dbms_output.put_line(invoice_archive_text.invoice_id);
insert into invoice_archive values invoice_archive_text;
end loop;
end;
/
I'm not sure what to execute at this point:
SQL> set serveroutput on;
SQL> execute paid_invoice_transfer(???);
Now that you know how to execute a procedure without an argument, I would also like to point out the problems with your procedure.
It's not required to define a record variable for looping through the cursor.
invoice_archive_text paid%rowtype
Pl/SQL automatically creates it when you use it in for loop. I will go a step further and ask you to avoid loops to run INSERT. Just use plain INSERT INTO target_table select * from source_table and explicitly specify column names to be safe.
If you want to display the ids that are being inserted through DBMS_OUTPUT.PUT_LINE, store it in a a collection and loop it separately just for display purpose( only if you think display is needed).
I also wanted to show you how to pass a date argument for execution so I will pass payment_date. In your procedure you are wrongly comparing it with a literal string rather than a date. It is bound to fail if NLS_DATE_ parameters don't match with the string.
CREATE OR REPLACE PROCEDURE paid_invoice_transfer ( p_payment_date DATE ) AS
TYPE tab_inv IS TABLE OF paid_invoices.invoice_id%type;
t_tab tab_inv;
BEGIN
SELECT invoice_id BULK COLLECT INTO t_tab
FROM paid_invoices
WHERE invoice_total = credit_total + payment_total
AND payment_date <= p_payment_date;
FOR i IN t_tab.FIRST..t_tab.LAST LOOP
dbms_output.put_line(t_tab(i));
END LOOP;
INSERT INTO invoice_archive ( column1,column2,column3) select col1,col2,col3
FROM paid_invoices
WHERE invoice_total = credit_total + payment_total
AND payment_date <= p_payment_date;
END;
/
set serveroutput on
execute paid_invoice_transfer(DATE '2014-05-31' )

database update on large table oracle taking huge time

I want to copy one column data into another column in a large table containing 10 millions records.
I am using sys refcursor to copy data from one column into another column. It will taking more than 30 min to copy the data. I am using ORACLE 11gR2.
Is there any others alternative to do the same. Below is the scripts
CREATE OR REPLACE PROCEDURE tblCursor(org_mig OUT SYS_REFCURSOR)
IS
BEGIN
OPEN org_mig FOR
select id from tbl;
END;
/
DECLARE
org_mig SYS_REFCURSOR;
t_id organization.id%TYPE;
loop_var number(10);
commit_interval number(10);
BEGIN
loop_var :=1;
commit_interval:=10000;
tblCursor(org_mig);
LOOP
FETCH org_mig INTO t_id;
EXIT WHEN org_mig%NOTFOUND;
update tbl set col1=col2 where id=t_id;
IF mod(loop_var,commit_interval)=0 THEN
Commit;
End if;
loop_var :=loop_var+1;
END LOOP;
Commit;
CLOSE org_mig;
END;
/
You're doing this for every row in tbl, right? If so, you should just do this:
update tbl
set col1 = col2
/
Updating ten million rows will take some time, but a set operation will be way faster than the Row By Agonizing Row approach you've implemented. Plus, batching up your commits like that is bad practice. Not only does it slow things down, that approach can lead to ORA-01555: Snapshot too old exceptions. Find out more.
Still it has been taken long time to update.
I am trying with different one but getting error.
-----------------------------------
Error starting at line : 43 in command -
SELECT *
FROM TABLE(test_parallel_update(CURSOR(SELECT * FROM organization)))
Error report -
SQL Error: ORA-12801: error signaled in parallel query server P003
ORA-00932: inconsistent datatypes: expected - got -
ORA-06512: at "QA249.TEST_PARALLEL_UPDATE", line 21
12801. 00000 - "error signaled in parallel query server %s"
*Cause: A parallel query server reached an exception condition.
*Action: Check the following error message for the cause, and consult
your error manual for the appropriate action.
*Comment: This error can be turned off with event 10397, in which
case the server's actual error is signaled instead.
---------------------------
Here is the script:
CREATE OR REPLACE TYPE test_num_arr AS TABLE OF NUMBER;
CREATE OR REPLACE FUNCTION test_parallel_update (
test_cur IN SYS_REFCURSOR
)
RETURN test_num_arr
PARALLEL_ENABLE (PARTITION test_cur BY ANY)
PIPELINED
IS
PRAGMA AUTONOMOUS_TRANSACTION;
test_rec organization%ROWTYPE;
TYPE num_tab_t IS TABLE OF NUMBER(10,0);
TYPE vc2_tab_t IS TABLE OF number(1,0);
id NUM_TAB_T;
org_type_old NUM_TAB_T;
IS_DELETED_old VC2_TAB_T;
cnt INTEGER := 0;
BEGIN
LOOP
FETCH test_cur BULK COLLECT INTO id, org_type_old, IS_DELETED_old LIMIT 1000;
EXIT WHEN id.COUNT() = 0;
FORALL i IN id.FIRST .. id.LAST
UPDATE organization
SET org_type = org_type_old(i)
, IS_DELETED = IS_DELETED_old(i)
WHERE id = id(i);
cnt := cnt + id.COUNT;
END LOOP;
CLOSE test_cur;
COMMIT;
PIPE ROW(cnt);
RETURN;
END;
/
show error;
---- To Execute ----
SELECT *
FROM TABLE(test_parallel_update(CURSOR(SELECT * FROM organization)));
Note:
Table
organization
(
id number(10,0),
org_type number(10,0),
org_type_old number(10,0),
IS_DELETED number(1,0),
IS_DELETED_OLD number(1,0)
);
where id is a primary key, Now I want copy org_type_old and IS_DELETED_OLD into org_type and IS_DELETED respectively.

ORA-04091 mutating table error when calling a function from a procedure

I have a task:
Write a procedure to update salary (salary * % of increment) in emp table based on grade. Use function to get increment
This is my procedure:
CREATE OR REPLACE
PROCEDURE sal_incre
IS
CURSOR c_cur
IS
SELECT * FROM emp_task;
BEGIN
UPDATE emp_task SET sal = sal + sal_incr(grade_id);
FOR rec IN c_cur
LOOP
dbms_output.put_line(rec.empno||','||rec.ename||','||rec.sal);
END LOOP;
END;
This is my function code:
CREATE OR REPLACE
FUNCTION sal_incr(
p_grade NUMBER)
RETURN
IS
v_inc NUMBER;
BEGIN
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id IN
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade
);
RETURN v_inc;
COMMIT;
END;
When I call the procedure I'm getting:
ORA-04091: table SCOTT.EMP_TASK is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.SAL_INCR", line 8
ORA-06512: at "SCOTT.SAL_INCRE", line 6
ORA-06512: at line 2
What am I doing wrong?
Your function is referring to the same table you're using in the procedure at the point you call that function, which is what causes this error. You're updating and querying it at the same time, in a way that could cause indeterminate (or confusing) results, even though you aren't querying the column you're updating. Oracle is protecting you from yourself here.
In your function you're doing:
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id IN
(SELECT grade_id FROM emp_task WHERE grade_id = p_grade
);
There is no need to look at the emp_task table here. Unless you've been passed an nonexistent value (which can't happen from your procedure) the subquery can only return the original p_grade argument value, so this is the same as:
SELECT raise_percent
INTO v_inc
FROM sal_inc
WHERE grade_id = p_grade;
If you do that the function no longer refers to emp_task, so it won't throw the mutating trigger/function error when it's called as part of an update.
And your function should not be issuing a COMMIT - let the calling procedure, or preferably the session that calls the procedure, decide whether the who transaction should be committed or rolled back.
Also, from the title and column name it looks like raise_percent is a percentage, so you need to use that to find the value to multiply by - you shouldn't add that percentage figure. If that gives you a value of 2 for a 2% raise, for example, you need to either do this in your procedure:
UPDATE emp_task SET sal = sal * (1 + (sal_incr(grade_id)/100));
Or more neatly have your function return 1 + (raise_percent/100) and do:
UPDATE emp_task SET sal = sal * sal_incr(grade_id);
Change the procedure like this:
create or replace
procedure sal_incre
is
cursor c_cur is
select distinct e.grade_id,e.ename,e.sal from sal_inc s
join emp_task e on e.grade_id = s.grade_id order by e.ename ;
v_incr number;
begin
for f_cur in c_cur
loop
v_incr := sal_incr(f_cur.grade_id);
update emp_task set sal = sal + v_incr;
dbms_output.put_line('Emp Name : '||f_cur.ename||','
||' Sal:'||f_cur.sal||','||' Grade: '||f_cur.grade_id);
end loop;
end;

Oracle create trigger error (bad bind variable)

I at trying to create trigger with the following code.
CREATE OR REPLACE TRIGGER MYTABLE_TRG
BEFORE INSERT ON MYTABLE
FOR EACH ROW
BEGIN
select MYTABLE_SEQ.nextval into :new.id from dual;
END;
I am getting error
Error(2,52): PLS-00049: bad bind variable 'NEW.ID'
Any ideas? Thanks.
It seems like the error code is telling you there's no such column ID in your table...
Somehow your environment is treating your code as SQL instead of a DDL statement. This works for me (running in sqlplus.exe from a command prompt):
SQL> create sequence mytable_seq;
Sequence created.
SQL> create table mytable (id number);
Table created.
SQL> CREATE OR REPLACE TRIGGER MYTABLE_TRG
2 BEFORE INSERT ON MYTABLE
3 FOR EACH ROW
4 BEGIN
5 select MYTABLE_SEQ.nextval into :new.id from dual;
6 END;
7 /
Trigger created.
Note the trailing "/" - this might be important in the application you are compiling this with.
if one would use proper naming convention the spotting of this type
of errors would be much easier ( where proper means using pre- and postfixes )
for generic object names hinting about their purpose better
i.e. something like this would have spotted the correct answer
--START -- CREATE A SEQUENCE
/*
create table "TBL_NAME" (
"TBL_NAME_ID" number(19,0) NOT NULL
, ...
*/
--------------------------------------------------------
-- drop the sequence if it exists
-- select * from user_sequences ;
--------------------------------------------------------
declare
c int;
begin
select count(*) into c from user_sequences
where SEQUENCE_NAME = upper('SEQ_TBL_NAME');
if c = 1 then
execute immediate 'DROP SEQUENCE SEQ_TBL_NAME';
end if;
end;
/
CREATE SEQUENCE "SEQ_TBL_NAME"
MINVALUE 1 MAXVALUE 999999999999999999999999999
INCREMENT BY 1 START WITH 1
CACHE 20 NOORDER NOCYCLE ;
-- CREATE
CREATE OR REPLACE TRIGGER "TRG_TBL_NAME"
BEFORE INSERT
ON "TBL_NAME"
REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
DECLARE
tmpVar NUMBER;
BEGIN
tmpVar := 1 ;
SELECT SEQ_TBL_NAME.NEXTVAL INTO tmpVar FROM dual;
:NEW.TBL_NAME_ID := tmpVar;
END TRG_TBL_NAME;
/
ALTER TRIGGER "TRG_TBL_NAME" ENABLE;
-- STOP -- CREATE THE TRIGGER
If you're like me and your code should be working, try dropping the trigger explicitly before you re-create it. Stupid Oracle.

how to fire two oracle insert statement together?Oracle 11g

I am trying to fire two insert statements at a time. Actually i have tried with below query but its inserting in only one table.
EXECUTE IMMEDIATE 'select * from abc.test where test_NAME = ''aaa''' BULK COLLECT INTO T_SC;
IF T_SC.count = 0 THEN
Insert into abc.test (test_ID,test_NAME,status)
VALUES(1,'aaa','a') BULK COLLECT INTO insert_cnt;
IF insert_cnt.count = 1 THEN
INSERT INTO abc.test1(test1_id,test1_NAME,test1_ALIAS,test_ID)
VALUES(1,'bbb','b',1);
COMMIT;
END IF;
it is only inserting in abc.test1 table..What i am going to missing. If anyone knows than plz help me in this.
This whole code of yours doesn't seem right:
why the dynamic sql ?
there are lots of syntax errors
unclosed "if" (as vj shah commented)
missing returning keyword
why do you need the bulk collect if your returning from the insert only one row ?
what is the second "if" for ?
and so on...
Anyway, this code works:
EXECUTE IMMEDIATE 'select * from abc.test where test_NAME = ''aaa''' BULK COLLECT INTO T_SC;
/* BTW, why not
select * bulk collect into T_SC from abc.test where test_NAME = 'aaa';
*/
IF T_SC.count = 0 THEN
Insert into abc.test (test_ID, test_NAME, status)
VALUES(1,'aaa','a') returning test_ID, test_NAME, status BULK COLLECT INTO insert_cnt;
IF insert_cnt.count = 1 THEN
INSERT INTO abc.test1(test1_id,test1_NAME,test1_ALIAS,test_ID)
VALUES(1,'bbb','b',1);
END IF;
COMMIT;
END IF;
Can you explain your problem a little better. Neither your logic nor the data that you show gives any idea of what you are trying to accomplish. (the logic behind the if).
This is also not functional code (too many syntax errors), can you update with the real code you are firing? May be just change the table names?
If you want to make sure both the statements either complete successfully or both of them are rolled back, your approach of including them in a block is correct.
SQL> create table test_rc_2(
2 id number
3 );
Table created.
--Sample 1 : Submitting inserts seperately (only the latest statement is rolled
SQL> insert into test_rc_2 values (100);
1 row created.
SQL> insert into test_rc_2 values ('hello');
insert into test_rc_2 values ('hello')
*
ERROR at line 1:
ORA-01722: invalid number
SQL> commit;
Commit complete.
SQL> select * from test_rc_2;
ID
----------
100
--case 2 : submittig them in a block.
SQL> truncate table test_rc_2
2 ;
Table truncated.
SQL> begin
2 insert into test_rc_2 values(100);
3 insert into test_rc_2 values('hello..');
4 end;
5 /
begin
*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at line 3
SQL> commit;
Commit complete.
SQL> select * from test_rc_2;
no rows selected

Resources