I have a simple PL/SQL procedure that increases the salary of an employee in the EMP table of the SCOTT schema. This receives the employee number per parameter and increment. The UPDATE statement that performs the update does not filter by that identifier and when accessing the ROWCOUNT of the cursor indicates that all the rows in the table were updated.
If this update I do from SQL Plus. It only updates a row.
CREATE OR REPLACE PROCEDURE INCREASE_SALARY(
empno EMP.EMPNO%TYPE,
incre NUMBER
)
AUTHID DEFINER
AS PRAGMA AUTONOMOUS_TRANSACTION;
INCREMENT_MUST_BE_GREATER_THAN_ZERO EXCEPTION;
BEGIN
IF incre = 0 THEN
RAISE INCREMENT_MUST_BE_GREATER_THAN_ZERO;
END IF;
DBMS_OUTPUT.PUT_LINE('EMPLOYEE TO UPDATE: ' || empno);
UPDATE EMP
SET SAL = SAL + (SAL * incre / 100)
WHERE EMPNO = empno;
DBMS_OUTPUT.PUT_LINE(TO_CHAR(SQL%ROWCOUNT)||' rows affected.');
IF SQL%ROWCOUNT > 0 THEN
INSERT INTO TABLA_LOG VALUES(USER, SYSDATE);
DBMS_OUTPUT.PUT_LINE('SALARY UPDATED SUCCESSFULLY');
ELSE
DBMS_OUTPUT.PUT_LINE('NO EMPLOYEE FOUND FOR THAT ID');
END IF;
COMMIT WORK;
EXCEPTION
WHEN INCREMENT_MUST_BE_GREATER_THAN_ZERO THEN
DBMS_OUTPUT.PUT_LINE('THE INCREMENT PERCENTAGE MUST BE GREATER THAN 0');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR : ' || SQLCODE || 'MENSAJE: ' || SQLERRM);
END;
/
When executing the procedure with the EMPNO 7900 (JAMES)
SET SERVEROUTPUT ON;
EXEC INCREASE_SALARY(7900, 20);
I get the following output:
EMPLOYEE TO UPDATE: 7900
13 rows affected.
SALARY UPDATED SUCCESSFULLY
Procedimiento PL/SQL terminado correctamente.
Does anyone know I'm doing wrong? Thanks in advance.
select count(1) from EMP WHERE EMPNO = 7900;
Return 1.
I'm using Oracle Enterprise 12c. The procedure compiles. The identifier error too long does not appear.
Your problem is empno = empno. These are interpreted as the column name.
You should try to give your arguments and local variables different names:
CREATE OR REPLACE PROCEDURE INCREASE_SALARY (
in_empno EMP.EMPNO%TYPE,
iin_ncre NUMBER
)
AUTHID DEFINER
AS PRAGMA AUTONOMOUS_TRANSACTION;
INCREMENT_MUST_BE_GREATER_THAN_ZERO EXCEPTION;
BEGIN
IF in_incre = 0 THEN
RAISE INCREMENT_MUST_BE_GREATER_THAN_ZERO;
END IF;
DBMS_OUTPUT.PUT_LINE('EMPLOYEE TO UPDATE: ' || in_empno);
UPDATE EMP
SET SAL = SAL + (SAL * in_incre / 100)
WHERE EMPNO = in_empno;
. . .
I am trying to learn pl/sql triggers. I am trying to create a simple trigger by tracking tutorial http://www.tutorialspoint.com/plsql/plsql_triggers.htm but I got below error. I searched on the internet but could not find the solution. Could you help me on this issue?
CREATE OR replace TRIGGER display_salary_changes
BEFORE DELETE OR INSERT OR UPDATE ON ok.customers
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :NEW.salary - :OLD.salary;
END;
/
Trıgger DISPLAY_SALARY_CHANGES created.
SP2-0552: Bind Variable "NEW" is not declared.
PL/SQL procedure successfully completed.
Edit: I am using Sql Developer Version 4.1.1
Works for me (example from your link, but it's basically the same as your post):
SQL> create table demo (id integer, salary number);
Table created.
SQL> create or replace trigger display_salary_changes
2 before delete or insert or update on demo
3 for each row
4 when (new.id > 0)
5 declare
6 sal_diff number;
7 begin
8 sal_diff := :new.salary - :old.salary;
9 dbms_output.put_line('Old salary: ' || :old.salary);
10 dbms_output.put_line('New salary: ' || :new.salary);
11 dbms_output.put_line('Salary difference: ' || sal_diff);
12 end;
13 /
Trigger created.
SQL> show errors
No errors.
SQL> insert into demo (id, salary) values (1, 100);
Old salary:
New salary: 100
Salary difference:
1 row created.
SQL> update demo set salary = salary * 1.1 where id = 1;
Old salary: 100
New salary: 110
Salary difference: 10
1 row updated.
In your example it shows
Trıgger DISPLAY_SALARY_CHANGES created.
which doesn't look like SQL*Plus output. What tool did you use?
After that it gives a SQL*Plus SP2-0552 error about an undefined bind variable, followed by
PL/SQL procedure successfully completed.
What procedure was that? I suspect this is the output from a script with some other step that is failing after the trigger is created.
Is the trigger valid? You can normally right-click and check properties in desktop tools, or at the SQL*Plus prompt enter
show errors trigger display_salary_changes
Try this:
CREATE OR replace TRIGGER test_trg
BEFORE DELETE OR INSERT OR UPDATE ON test
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :new.d - :old.d;
END;
/
Can you please check you column name. I have tried your code below and I got output.
create table test
(
no number(10),
sal number(10)
);
CREATE OR replace TRIGGER test_tr
BEFORE DELETE OR INSERT OR UPDATE ON test
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :NEW.sal - :OLD.sal;
dbms_output.put_line(sal_diff);
END;
/
insert into test values(1,100);
update test set sal=200 where no=1;
Output :
1 rows inserted.
4 rows updated.
100
100
100
100
1 rows inserted.
I think it is missing the "REFERENCING NEW AS NEW OLD AS OLD" sentence:
CREATE TRIGGER [trigger_name]
BEFORE INSERT OR UPDATE ON [table_name]
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
I am trying to create a simple trigger in sql developer to display the change in salary when it is changed
CREATE OR REPLACE TRIGGER salary_changes
BEFORE DELETE OR INSERT OR UPDATE ON FACULTY
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :NEW.F_SALARY - :OLD.F_SALARY;
DBMS_OUTPUT.PUT_LINE('Difference: ' || sal_diff);
END;
when I attempt to run the trigger it prompts be to enter binds for NEW and OLD and when i try run an update to see if it works it states the trigger failed. So how am i using the old and new tags incorrectly? or is that not the issue
There are several issues with your code.
You need to create a after trigger instead of the before trigger.
You are trying to write a trigger that performs an operation for
insert,delete or update. So you should do the conditional checks
(such as if inserting,deleting or updating) clause.
Also, when you do delete, there is no new value, but just the old
value.
I would change your trigger as below..
CREATE OR REPLACE TRIGGER salary_changes
AFTER DELETE OR INSERT OR UPDATE ON FACULTY
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
If (INSERTING or UPDATING) then
sal_diff := :NEW.F_SALARY - :OLD.F_SALARY;
DBMS_OUTPUT.PUT_LINE('Difference: ' || sal_diff);
END IF;
IF DELETING THEN
DBMS_OUTPUT.PUT_LINE('The deleted value is:' || :OLD.F_SALARY);
END IF;
END;
I am writing a simple trigger that is supposed to just send a message with the updated Count of rows as well as the old value of Gender and the updated value of Gender. When i run an update however I am getting the error that the table is mutating and the table might not be able to see it but I'm not exactly sure why.
trigger
create or replace trigger updatePERSONS
after update
on PERSONS
for each row
declare
n int;
oldGender varchar(20):= :OLD.Gender;
newGender varchar(20):= :NEW.Gender;
begin
select Count(*)
into n
from PERSONS;
if (oldGender != newGender) then
dbms_output.put_line('There are now '|| n || ' rows after update. Old gender: ' || oldGender
|| ', new Gender: ' || newGender);
end if;
End;
`
i know it has to do with the select statement after begin but how else would i get count of rows?
As #San points out, a row-level trigger on persons cannot generally query the persons table.
You'd need two triggers, a row-level trigger that can see the old and new gender and a statement-level trigger that can do the count. You could also, if you're using 11g, create a compound trigger with both row- and statement-level blocks.
create or replace trigger trg_stmt
after update
on persons
declare
l_cnt integer;
begin
select count(*)
into l_cnt
from persons;
dbms_output.put_line( 'There are now ' || l_cnt || ' rows.' );
end;
create or replace trigger trg_row
after update
on persons
for each row
begin
if( :new.gender != :old.gender )
then
dbms_output.put_line( 'Old gender = ' || :old.gender || ', new gender = ' || :new.gender );
end if;
end;
For some reason, people in the past have inserted data without using sequence.NEXTVAL. So when I go to use sequence.NEXTVAL in order to populate a table, I get a PK violation, since that number is already in use in the table.
How can I update the next value so that it is usable? Right now, I'm just inserting over and over until it's successful (INSERT INTO tbl (pk) VALUES (sequence.NEXTVAL)), and that syncs up the nextval.
You can temporarily increase the cache size and do one dummy select and then reset the cache size back to 1. So for example
ALTER SEQUENCE mysequence INCREMENT BY 100;
select mysequence.nextval from dual;
ALTER SEQUENCE mysequence INCREMENT BY 1;
In my case I have a sequence called PS_LOG_SEQ which had a LAST_NUMBER = 3920.
I then imported some data from PROD to my local machine and inserted into the PS_LOG table. Production data had more than 20000 rows with the latest LOG_ID (primary key) being 20070. After importing I tried to insert new rows in this table but when saving I got an exception like this one:
ORA-00001: unique constraint (LOG.PS_LOG_PK) violated
Surely this has to do with the Sequence PS_LOG_SEQ associated with the PS_LOG table. The LAST_NUMBER was colliding with data I imported which had already used the next ID value from the PS_LOG_SEQ.
To solve that I used this command to update the sequence to the latest \ max(LOG_ID) + 1:
alter sequence PS_LOG_SEQ restart start with 20071;
This command reset the LAST_NUMBER value and I could then insert new rows into the table. No more collision. :)
Note: this alter sequence command is new in Oracle 12c.
Note: this blog post documents the ALTER SEQUENCE RESTART option does exist, but as of 18c, is not documented; it is apparently intended for internal Oracle use.
These two procedures let me reset the sequence and reset the sequence based on data in a table (apologies for the coding conventions used by this client):
CREATE OR REPLACE PROCEDURE SET_SEQ_TO(p_name IN VARCHAR2, p_val IN NUMBER)
AS
l_num NUMBER;
BEGIN
EXECUTE IMMEDIATE 'select ' || p_name || '.nextval from dual' INTO l_num;
-- Added check for 0 to avoid "ORA-04002: INCREMENT must be a non-zero integer"
IF (p_val - l_num - 1) != 0
THEN
EXECUTE IMMEDIATE 'alter sequence ' || p_name || ' increment by ' || (p_val - l_num - 1) || ' minvalue 0';
END IF;
EXECUTE IMMEDIATE 'select ' || p_name || '.nextval from dual' INTO l_num;
EXECUTE IMMEDIATE 'alter sequence ' || p_name || ' increment by 1 ';
DBMS_OUTPUT.put_line('Sequence ' || p_name || ' is now at ' || p_val);
END;
CREATE OR REPLACE PROCEDURE SET_SEQ_TO_DATA(seq_name IN VARCHAR2, table_name IN VARCHAR2, col_name IN VARCHAR2)
AS
nextnum NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT MAX(' || col_name || ') + 1 AS n FROM ' || table_name INTO nextnum;
SET_SEQ_TO(seq_name, nextnum);
END;
If you can count on having a period of time where the table is in a stable state with no new inserts going on, this should do it (untested):
DECLARE
last_used NUMBER;
curr_seq NUMBER;
BEGIN
SELECT MAX(pk_val) INTO last_used FROM your_table;
LOOP
SELECT your_seq.NEXTVAL INTO curr_seq FROM dual;
IF curr_seq >= last_used THEN EXIT;
END IF;
END LOOP;
END;
This enables you to get the sequence back in sync with the table, without dropping/recreating/re-granting the sequence. It also uses no DDL, so no implicit commits are performed. Of course, you're going to have to hunt down and slap the folks who insist on not using the sequence to populate the column...
With oracle 10.2g:
select level, sequence.NEXTVAL
from dual
connect by level <= (select max(pk) from tbl);
will set the current sequence value to the max(pk) of your table (i.e. the next call to NEXTVAL will give you the right result); if you use Toad, press F5 to run the statement, not F9, which pages the output (thus stopping the increment after, usually, 500 rows).
Good side: this solution is only DML, not DDL. Only SQL and no PL-SQL.
Bad side : this solution prints max(pk) rows of output, i.e. is usually slower than the ALTER SEQUENCE solution.
Today, in Oracle 12c or newer, you probably have the column defined as GENERATED ... AS IDENTITY, and Oracle takes care of the sequence itself.
You can use an ALTER TABLE Statement to modify "START WITH" of the identity.
ALTER TABLE tbl MODIFY ("ID" NUMBER(13,0) GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 3580 NOT NULL ENABLE);
In my case I used an approach to reset sequence to zero and than setting from zero to max of target table:
DECLARE
last_val NUMBER;
next_val NUMBER;
BEGIN
SELECT MAX(id_field) INTO next_val FROM some_table;
IF next_val > 0 THEN
SELECT some_table_seq.nextval INTO last_val FROM DUAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY -' || last_val || ' MINVALUE 0';
SELECT some_table_seq.nextval INTO last_val FROM DUAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY ' || next_val;
SELECT some_table_seq.nextval INTO last_val FROM DUAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE some_table_seq INCREMENT BY 1 MINVALUE 1';
END IF;
END;
Apologies for not having a one-liner solution, since my program runs in Typeorm with node-oracle. However, I think the following SQL commands would help with this problem.
Get the LAST_NUMBER from your sequence.
SELECT SEQUENCE_NAME, LAST_NUMBER FROM ALL_SEQUENCES WHERE SEQUENCE_NAME = '${sequenceName}'
Get the value of your PK from the last row (in this case ID is the PK).
SELECT ID FROM ${tableName} ORDER BY ID DESC FETCH NEXT 1 ROWS ONLY
Lastly update LAST_NUMBER to the value + 1:
ALTER SEQUENCE ${sequenceName} RESTART START WITH ${value + 1}