I'm coding a Trigger to ensure only one type of money can be set as official. My intention is code a "BEFORE INSERT OR UPDATE" trigger. The INSERT section works fine but the problem is coding the UPDATING section because when I try to update the table I recieve ORA-04091 "mutanting table". Do you have any idea?
Table (Only one record can be set as 'Y'):
mon_id mon_description mon_official
----------------------------------------------
E EUR N
D DOL N
P PES Y
Trigger:
CREATE OR REPLACE TRIGGER mon_oficial_ins_trg
BEFORE
INSERT OR UPDATE
ON monedas
FOR EACH ROW
DECLARE
v_count NUMBER(8);
BEGIN
IF INSERTING THEN
SELECT COUNT(mon_oficial)
INTO v_count
FROM monedas
WHERE mon_oficial = 'Y';
IF v_count = 1 THEN
RAISE_APPLICATION_ERROR(
-20010, 'Only one record can be set as 'Y'');
END IF;
END IF;
IF UPDATING THEN
SELECT COUNT(:OLD.mon_oficial)
INTO v_count
FROM monedas
WHERE :OLD.mon_oficial = 'Y';
IF v_count = 1 AND :NEW.mon_oficial = 'Y' THEN
RAISE_APPLICATION_ERROR(
-20010, 'Only one record can be set as 'Y'');
END IF;
END IF;
END mon_oficial_ins_trg;
/
SHOW ERRORS;
In your code there are 2 mistake
first
SELECT COUNT(:OLD.mon_oficial)
INTO v_count
FROM monedas
WHERE :OLD.mon_oficial = 'Y';
part, for more information about mutanting error you can read this article
enter link description here
and second mistake, you have a incorrect logic in
IF v_count = 1 AND :NEW.mon_oficial = 'Y' THEN part because it can be our current row
try it
CREATE OR REPLACE TRIGGER mon_oficial_ins_trg
BEFORE
INSERT OR UPDATE
ON monedas
FOR EACH ROW
DECLARE
v_count NUMBER(8);
BEGIN
IF INSERTING THEN
SELECT COUNT(mon_oficial)
INTO v_count
FROM monedas
WHERE mon_oficial = 'Y';
IF v_count = 1 THEN
RAISE_APPLICATION_ERROR(
-20010, 'Only one record can be set as 'Y'');
END IF;
END IF;
IF UPDATING THEN
IF :NEW.mon_oficial = 'Y' then
for m in (SELECT *
FROM monedas
WHERE mon_oficial = 'Y'
and rownum=1) loop
IF :NEW.mon_id <> m.mon_id THEN
RAISE_APPLICATION_ERROR(
-20010, 'Only one record can be set as 'Y'');
END IF;
END IF;
end loop;
END IF;
END mon_oficial_ins_trg;
/
SHOW ERRORS;
This can be done quite simply with an AFTER INSERT OR UPDATE statement trigger. The same logic is applicable to both operations.
SELECT COUNT(*) INTO v_count FROM MONEDAS
WHERE MON_OFICIAL = 'Y';
IF v_count > 1 THEN
RAISE_APPLICATION_ERROR...
Another advantage of this approach: it allows the statement
UPDATE MONEDAS SET MON_OFICIAL = CASE MON_ID WHEN 'A' THEN 'Y' ELSE 'N' END;
to work without a problem when the row-level trigger might raise an error if the row with MON_ID = 'A' is updated to Y before the previous official currency is updated to N.
I think this problem would better be solved with a constraint, instead of a trigger. I am not the author of this answer, but I think it's relevant here. In the rest of the answers there was a link to a blog post that recommends avoiding triggers, but that link doesn't seem to work.
The answer is found here:
https://stackoverflow.com/a/182427
Here's the example provided in that answer by #tony-andrews:
create unique index only_one_yes on mytable
(case when col='YES' then 'YES' end);
Related
I am trying to write a code block where record insert if record already exist then update
table. i am trying If (sql%rowcount = 0) then but it's not working in cursor and more one records.
What I tried so far as the code block is
declare
remp_id varchar2(60);
remp_name varchar2(100);
rdesig varchar2(100);
rdept_no number;
rdesig_no number;
rdept_name varchar2(60);
cursor alfa is
select emp_code, emp_name, desig, dept_name, dept_no, desig_no
from emp
where emp_code between :first_code and :second_code;
begin
open alfa;
loop
fetch alfa
into remp_id, remp_name, rdesig, rdept_name, rdept_no, rdesig_no;
exit when alfa%notfound;
update att_reg_mo
set emp_code = remp_id,
emp_name = remp_name,
desig = rdesig,
dept_name = rdept_name,
dept_no = rdept_no,
desig_no = rdesig_no,
att_date = :att_date,
emp_att = :emp_att,
att_type = 'MA',
reg_date = :reg_date
where emp_code between :first_code and :second_code
and reg_date = :reg_date
and att_date = :att_date;
commit;
if (sql%rowcount = 0) then
insert into att_reg_mo
(emp_code,
emp_name,
desig,
dept_name,
att_date,
emp_att,
att_type,
reg_date,
dept_no,
desig_no)
values
(remp_id,
remp_name,
rdesig,
rdept_name,
:att_date,
:emp_att,
'MA',
:reg_date,
rdept_no,
rdesig_no);
end if;
commit;
end loop;
close alfa;
end;
when i am fire the trigger then record is insert but where need to update record it's update with null values
Or you could use something like that:
DECLARE
cursor test is
select 1 as v from dual
union
select 2 as v from dual;
n_var NUMBER;
BEGIN
for rec in test loop
BEGIN
select 1 into n_var from dual where rec.v=2;
DBMS_OUTPUT.PUT_LINE('Please Update Any Table');
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE('Please Insert Any Table');
END;
end loop;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Unexpected error');
END;
SQL%attribute always refers to the most recently run SELECT or DML statement. It refreshes as to start from zero after any of transaction statement such as COMMIT, ROLLBACK or SAVEPOINT is issued, for a session in which AUTOCOMMIT is presumed to be OFF by default. Therefore, you always get zero from SQL%ROWCOUNT which is just before the INSERT statement, and keeping inserting to the concerned table during every run of code block.
So, remove the f i r s t COMMIT, removing also keeps the atomicity of the whole transaction, from your code block.
Demo
Hi could you please suggest I am trying to check if count =2
When Condition meet (DA.matr_ID = '478') or (DA.matr_ID = '40') then
insert into table, but in any condition like (DA.matr_ID = '479') it insert the data which is wrong
CREATE OR REPLACE TRIGGER DEVICE_TMP_TR
AFTER INSERT OR UPDATE ON DEVICE_ATTRIBUTES_TMP
REFERENCING OLD AS old NEW AS new
FOR EACH ROW
DECLARE
pragma autonomous_transaction;
V_COUNT NUMBER;
BEGIN
SELECT COUNT(*)
INTO V_COUNT
FROM DEVICE_ATTRIBUTES_TMP DA
WHERE DA.DVS_ID = :new.DVS_ID
AND (DA.matr_ID = '40' or DA.matr_ID = '478');
IF V_COUNT = 2 THEN
INSERT Into APP_DELETED_TMP
(sdmid,appid,devis,des_nr,creation_date,creation_user,ant_id)
VALUES
('121213', '23', '45','63',SYSDATE,'hhdhSH',21);
COMMIT;
ELSE
RETURN;
END IF;
END;
CREATE TABLE cursor_example(
emp_id NUMBER(10) PRIMARY KEY,
emp_name VARCHAR2(30),
emp_salary NUMBER(6)
);
SELECT * FROM cursor_example;
INSERT INTO cursor_example VALUES(1234,'apple',1250);
INSERT INTO cursor_example VALUES(1235,'banana',1500);
INSERT INTO cursor_example VALUES(1236,'carrot',1750);
INSERT INTO cursor_example VALUES(1237,'donkey',2050);
INSERT INTO cursor_example VALUES(1238,'elixr',15075);
UPDATE cursor_example
SET emp_salary = emp_salary + (.25*emp_salary);
DECLARE affected_emp NUMBER(2);
BEGIN
UPDATE cursor_example
SET emp_salary = emp_salary + (.25*emp_salary);
IF sql%notfound THEN
DBMS_OUTPUT.PUTLINE('NO PEOPLE AFFECTED');
ELSEIF sql%found THEN
DBMS_OUTPUT.PUTLINE(affected_emp || 'PEOPLE AFFECTED');
ENDIF;
END;
The error message I got is :
ORA-06550: line 7, column 12: PLS-00103: Encountered the symbol "SQL"
when expecting one of the following: := . ( # % ;
Your actual error is caused by invalid syntax. In PL/SQL it's ELSIF not ELSEIF, although as you're not implementing a multi-branched switch you just need ELSE. Also END IF is two words. Furthermore it's dbms_output.put_line() not putline.
If you fix all those errors you routine will run.
However, sql%found tells us whether our DML hit any records but doesn't tell us how many records. So affected_emp will be null in your code.
The easiest way to find the number of records affected is sql%rowcount which gives us the number of records inserted, updated or deleted by the preceding DML statement. Zero means no records were changed.
Obviously you could just output that figure but here's how it looks in your code:
DECLARE
affected_emp NUMBER(2);
BEGIN
UPDATE cursor_example
SET emp_salary = emp_salary + (.25*emp_salary);
affected_emp := sql%rowcount;
IF affected_emp = 0 THEN
DBMS_OUTPUT.PUT_LINE('NO PEOPLE AFFECTED');
ELSE
DBMS_OUTPUT.PUT_LINE(affected_emp || ' PEOPLE AFFECTED');
END IF;
END;
You should use ELSIF (or ELSE) :
ELSIF sql%found THEN
DBMS_OUTPUT.put_line(affected_emp || 'PEOPLE AFFECTED');
END IF;
Or ELSE:
ELSE
DBMS_OUTPUT.put_line(affected_emp || 'PEOPLE AFFECTED');
END IF;
syntax for IF-THEN-ELSIF-ELSE in Oracle/PLSQL is:
IF condition1 THEN
{...statements to execute when condition1 is TRUE...}
ELSIF condition2 THEN
{...statements to execute when condition2 is TRUE...}
ELSE
{...statements to execute when both condition1 and condition2 are FALSE...}
END IF;
BEGIN
FOR v_LoadRec IN c_Load LOOP
SELECT count(1) INTO v_NO_OF_DAYS_RESP
from DIM_DATE
where DIM_DATE.TRADING_DAY_FLAG = 'Y' and
DIM_DATE_KEY <= TO_NUMBER(TO_CHAR(v_LoadRec.RESPONSE_DATE,'YYYYMMDD')) and
DIM_DATE_KEY >= TO_NUMBER(TO_CHAR(v_LoadRec.OPEN_DATE, 'YYYYMMDD'))
group by v_LoadRec.CALL_NUMBER;
IF SQL%NOTFOUND THEN
v_NO_OF_DAYS_RESP :='';
END IF;
SELECT count(1) INTO v_NO_OF_DAYS_RESO
from DIM_DATE
where DIM_DATE.TRADING_DAY_FLAG = 'Y' and
DIM_DATE_KEY <= TO_NUMBER(TO_CHAR(v_LoadRec.RESOLVE_DATE,'YYYYMMDD')) and
DIM_DATE_KEY >= TO_NUMBER(TO_CHAR(v_LoadRec.OPEN_DATE, 'YYYYMMDD'))
group by v_LoadRec.CALL_NUMBER;
IF SQL%NOTFOUND THEN
v_NO_OF_DAYS_RESO :='';
END IF;
END LOOP;
I have this block of SQL in my update procedure which gathers the count of trading days for each record and then inserts it into an integer variable named "v_NO_OF_DAYS_RESP" e.g. count of days between the open and response date of a call.
This works well except for when there is a null "RESPONSE_DATE" where it fails with the "ORA-01403: no data found" error. I understand why it's failing (because it of course has no record to insert) but I can't seem to figure out a way to get around it.
In these circumstances where the "RESPONSE_DATE" is found to be NULL, I would like the "v_NO_OF_DAYS_RESP" var to be set to NULL too (or even somehow have the SQL statement nested within an "IF" to possibly completely avoid running the calculation (SQL statement) when the "RESPONSE_DATE" is NULL).
*To put it really simple, I want the following:.. If the call does not yet have a response date, either don't run the SQL statement (calculation) or just set the var to Null
Any ideas or suggestions would be greatly appreciated.
Thanks - Kelvin
Handling exception will solve your problem:
BEGIN
FOR v_LoadRec IN c_Load LOOP
SELECT count(1) INTO v_NO_OF_DAYS_RESP
from DIM_DATE
where DIM_DATE.TRADING_DAY_FLAG = 'Y' and
DIM_DATE_KEY <= TO_NUMBER(TO_CHAR(v_LoadRec.RESPONSE_DATE,'YYYYMMDD')) and
DIM_DATE_KEY >= TO_NUMBER(TO_CHAR(v_LoadRec.OPEN_DATE, 'YYYYMMDD'))
group by v_LoadRec.CALL_NUMBER;
IF SQL%NOTFOUND THEN
v_NO_OF_DAYS_RESP :='';
END IF;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_NO_OF_DAYS_RESP :='';
END;
if (v_LoadRec.RESPONSE_DATE) is null Then
v_NO_OF_DAYS_RESP:='';
else
SELECT count(1) INTO v_NO_OF_DAYS_RESP
from DIM_DATE
where DIM_DATE.TRADING_DAY_FLAG = 'Y' and
DIM_DATE_KEY <= TO_NUMBER(TO_CHAR(v_LoadRec.RESPONSE_DATE, 'YYYYMMDD')) and
DIM_DATE_KEY >= TO_NUMBER(TO_CHAR(v_LoadRec.OPEN_DATE, 'YYYYMMDD'))
group by v_LoadRec.CALL_NUMBER;
IF SQL%NOTFOUND THEN
v_NO_OF_DAYS_RESP :='';
END IF;
end if;
Get the count of records in SELECT query. Then you can validate (count=0 or not).You can try like this.
result VARCHAR2(100);
result_count NUMBER;
BEGIN
SELECT count(<COLUMN_NAME>) INTO result_count FROM <TABLE_NAME> where empid = 12;
IF result_coun = 0 THEN
result := 'Value does not exist in the reference table';
END IF;
END;
I have this query and it's not updating into the database. The given "where" clause is valid. When I run the query independently, it works fine but in this Procedure it's not working. There is no exception or no error. Could you guys help me in figuring out where the problem is?
EXECUTE IMMEDIATE 'UPDATE ' || dest || ' SET COUNTRY_CODE = :v1 WHERE col_id = :v2'
USING l_vc_CountryCode, l_vc_ColId;
if SQL%ROWCOUNT > 1 THEN
inserts := inserts + 1;
counter := counter + 1;
IF counter > 500 THEN
counter := 0;
COMMIT;
END IF;
END IF;
I didn't write the commit code before. Just to clarity.
I suppose that col_id is the primary key. So in the update statement
EXECUTE IMMEDIATE 'UPDATE ' || dest || ' SET COUNTRY_CODE = :v1 WHERE col_id = :v2'
USING l_vc_CountryCode, l_vc_ColId;
you are always updating at most one row and thus the condition
SQL%ROWCOUNT > 1
is never true ( 1 is not > 1 )
So if you don't have any other commit statement in your procedure, you will never commit those updates.
By the way: what is the purpose of this
if SQL%ROWCOUNT > 1 THEN
inserts := inserts + 1;
counter := counter + 1;
IF counter > 500 THEN
counter := 0;
COMMIT;
END IF;
END IF;
why don't you just commit at the end of your work?
The following code works okay (ie updates the row).
I suspect your error is elsewhere.
For example, if you don't initialise COUNTER, the increment will still leave it as null and it will never commit.
Or, l_vc_ColId may be the wrong datatype and suffering from an invalid conversion.
declare
v_emp_id number := 7839;
v_name varchar2(4) := 'DING';
v_tab varchar2(3) := 'EMP';
begin
execute immediate 'update '||v_tab||
' set ename = :v_name Where empno = :v_emp_id'
using v_name, v_emp_id;
dbms_output.put_line('C:'||sql%rowcount);
end;
you may want to reconsider your design if your using dynamic sql to change the "dest" table in thousands of updates.
Much better to know your dest and use bind variables for the where conditions. then you can commit every x rows using mod or similar:
if (mod(v_ctr, 1000) = 0) then
commit;
end if;
But for your example, Marcin is correct, if you are updating only 1 row at a time, then
if SQL%ROWCOUNT > 1
will never be true;
EDIT:
A simple example knowing your "dest" table:
declare
cursor sel_cur is
select col1, col2, from sourceTable where col3 = 'X';
v_ctr pls_integer := 0;
begin
for rec in sel_cur
loop
v_ctr := v_ctr + 1;
-- implicit bind variables used
update destTable
set col1 = rec.col1,
col2 = rec.col2
where col3 = 'Z';
if (mod(v_ctr, 1000) = 0) then
commit;
end if;
end loop;
exception
when others then rollback;
raise;
end;
If using dynamic SQL, a simple example using explicit bind variables (USING clause) from Oracle docs:
CREATE OR REPLACE PROCEDURE raise_emp_salary (column_value NUMBER,
emp_column VARCHAR2, amount NUMBER) IS
v_column VARCHAR2(30);
sql_stmt VARCHAR2(200);
BEGIN
-- determine if a valid column name has been given as input
SELECT COLUMN_NAME INTO v_column FROM USER_TAB_COLS
WHERE TABLE_NAME = 'EMPLOYEES' AND COLUMN_NAME = emp_column;
sql_stmt := 'UPDATE employees SET salary = salary + :1 WHERE '
|| v_column || ' = :2';
EXECUTE IMMEDIATE sql_stmt USING amount, column_value;
IF SQL%ROWCOUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE('Salaries have been updated for: ' || emp_column
|| ' = ' || column_value);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('Invalid Column: ' || emp_column);
END raise_emp_salary;
/
For more reading, see here.
Hope this helps, happy coding
Execute immediate needs explicit commit. I guess you checked that ?