Mutating Table error in row level trigger - plsql

Write a database trigger to
Halt the transaction between the the time 6pm to 10am on table
Give the appropriate message if the record exceed more than 10
Delete data older than 2 months from Mon - Sat and date shouldn't be 1st of the month.
(i.e. leave Sunday & 1st OF THE MONTH DATA older THAN 2 months)
I tried this code,
CREATE OR REPLACE TRIGGER EMP_INFO_BFT BEFORE
INSERT OR DELETE OR UPDATE ON EMP_INFO
FOR EACH ROW
DECLARE l_TIME NUMBER(10);
l_RECORD NUMBER;
BEGIN
l_TIME:=TO_CHAR(SYSDATE,'HH24');
IF l_TIME NOT BETWEEN 18 AND 10 THEN
RAISE_APPLICATION_ERROR(-20003,'TIME ALREADY OVER.....TRANSACTION NOT ALLOWED NOW');
END IF;
DELETE
FROM EMP_INFO
WHERE TRUNC (HIRE_DATE ) < ADD_MONTHS (TRUNC (SYSDATE), -2)
AND TO_CHAR (TRUNC (HIRE_DATE ), 'DY') != 'SUN'
AND TO_CHAR (TRUNC (HIRE_DATE ), 'DD') != '01';
SELECT COUNT(*) INTO l_RECORD FROM EMP_INFO;
IF l_RECORD>=10 THEN
RAISE_APPLICATION_ERROR(-20005,'10 RECORD ALLOWED IN EMP_INFO TABLE');
END IF;
END;
I got mutating error while inserting.

Mutating error
Check this link which basically says:
"If a trigger does result in a mutating table error, the only real option is to rewrite the trigger as a statement-level trigger.
Mutating table errors only impact row level triggers."
Your code doesn't reference :NEW or :OLD, so changing it to a statement level trigger should be easy, just remove the FOR EACH ROW.
PS
I don't think your BETWEEN will work.
Changing
IF l_TIME NOT BETWEEN 18 AND 10 THEN
to
IF l_TIME BETWEEN 10 AND 18 THEN
NULL;
ELSE
would fix it.

Make this trigger as autonomous trigger, check the following code:
CREATE OR REPLACE TRIGGER EMP_INFO_BFT BEFORE
INSERT OR DELETE OR UPDATE ON EMP_INFO
FOR EACH ROW
DECLARE l_TIME NUMBER(10);
l_RECORD NUMBER;
pragma autonomous_transaction;
BEGIN
l_TIME:=TO_CHAR(SYSDATE,'HH24');
IF l_TIME NOT BETWEEN 18 AND 10 THEN
RAISE_APPLICATION_ERROR(-20003,'TIME ALREADY OVER.....TRANSACTION NOT ALLOWED NOW');
END IF;
DELETE
FROM EMP_INFO
WHERE TRUNC (HIRE_DATE ) < ADD_MONTHS (TRUNC (SYSDATE), -2)
AND TO_CHAR (TRUNC (HIRE_DATE ), 'DY') != 'SUN'
AND TO_CHAR (TRUNC (HIRE_DATE ), 'DD') != '01';
commit;
SELECT COUNT(*) INTO l_RECORD FROM EMP_INFO;
IF l_RECORD>=10 THEN
RAISE_APPLICATION_ERROR(-20005,'10 RECORD ALLOWED IN EMP_INFO TABLE');
END IF;
END;

Related

Error in executing plsql procedure (value larger than specified precision allowed for this column)

Here is my procedure where i want to insert values with the conditions like emp id start with sequence 131, phone number must be 13 digits, hire date not greater than sys date and salary not greater than 50,000. while executing im facing error . how to solve this.
create or replace procedure empinsert AS
cursor employee is select length(PHONE_NUMBER),HIRE_DATE,SAL from emp;
e_mobile_no emp.phone_number%type;
e_hire_date emp.hire_date%type;
e_salary emp.sal%type;
Begin
open employee ;
loop
fetch employee into e_mobile_no,e_hire_date,e_salary;
if ( e_mobile_no = 13 and e_hire_date < sysdate and e_salary <=50000 ) then
INSERT INTO EMP (EMP_ID,
EMP_NAME,
EMAIL,
PHONE_NUMBER,
HIRE_DATE,
JOB_ID,
SAL)
values(empinc.nextval, 'ramji','ramji#gmail.com','8975432109875','10/07/2021','JUN_TECH',40000);
else
exit;
end if;
end loop;
end;
/

Need to delete all tables based on their references i.e FK and PK

I need to create a script to clean up all the objects in the schema 'myschema'.
Order will be like
All tables(FK PK order)
How do I achieve this in one PLSQL block.
set serveroutput on;
declare
lv_str varchar2(1000);
begin
for c in(select distinct a.table_name as table_name, b.table_name as parent_table_name
from
all_constraints a
left outer join all_constraints b on a.r_constraint_name = b.constraint_name and a.owner = b.owner) loop
lv_str :='DROP TABLE '||c.table_name;
--lv_str :='DROP TABLE '||c.parent_table_name;
dbms_output.put_line(lv_str);
end loop;
end;
This is a script I use to clear Scott's schema. Why? I use it for testing purposes and, as time goes by, it turns into a mess. I don't pay much attention about what I remove and in which order; I simply run it 2-3 times and everything's gone. See if you can use it, improve it if you want.
SET SERVEROUTPUT ON;
DECLARE
l_user VARCHAR2 (30) := 'SCOTT';
l_str VARCHAR2 (200);
BEGIN
IF USER = l_user
THEN
FOR cur_r IN (SELECT object_name, object_type
FROM user_objects
WHERE object_name NOT IN ('EMP',
'DEPT',
'BONUS',
'SALGRADE'))
LOOP
BEGIN
l_str :=
'drop '
|| cur_r.object_type
|| ' "'
|| cur_r.object_name
|| '"';
DBMS_OUTPUT.put_line (l_str);
EXECUTE IMMEDIATE l_str;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
END LOOP;
END IF;
END;
/
PURGE RECYCLEBIN;
SELECT * FROM user_objects;
How to drop foreign key constraints first, and then drop tables:
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(200);
3 begin
4 for cur_r in (select table_name, constraint_name
5 from user_constraints
6 where constraint_type = 'R')
7 loop
8 l_str := 'alter table ' || cur_r.table_name ||
9 ' drop constraint ' || cur_r.constraint_name;
10 dbms_output.put_line(l_str);
11
12 execute immediate l_str;
13 end loop;
14
15 --
16
17 for cur_r in (select table_name from user_tables where table_name not in ('EMP', 'DEPT'))
18 loop
19 l_str := 'drop table ' || cur_r.table_name;
20
21 dbms_output.put_line(l_str);
22
23 execute immediate l_str;
24 end loop;
25 end;
26 /
alter table TDET drop constraint SYS_C00105662
drop table BONUS
drop table SALGRADE
drop table TEST
drop table MYTABLE
drop table TABLEB
drop table TABLEA
drop table EMPLOYEES
drop table T_PRINT
drop table TMAS
drop table TDET
PL/SQL procedure successfully completed.
SQL>

DBMS_OUTPUT to a table with timestamp

I am deleting a massive table and want to delete the table in batches. I am deleting records older than 467 days. I want to insert the dbms_output status in the following procedure to be written to a table that has two columns such as the number of records to be deleted with timestamp columns:
CREATE OR REPLACE PROCEDURE delete_tab (tablename IN VARCHAR2, nrows IN NUMBER ) IS
sSQL1 VARCHAR2(2000);
sSQL2 VARCHAR2(2000);
nCount NUMBER;
BEGIN
DBMS_OUTPUT.enable (100000);
nCount := 0;
sSQL1:='delete from '|| tablename ||
' where ROWNUM < ' || nrows || ' and where cast(time_stamp as date) < sysdate - 467';
sSQL2:='select count(ROWID) from ' || tablename ||
' where cast(time_stamp as date) < sysdate - 467';
EXECUTE IMMEDIATE sSQL2 INTO nCount;
LOOP
EXECUTE IMMEDIATE sSQL1;
nCount := nCount-nrows;
DBMS_OUTPUT.PUT_LINE('Existing records to be deleted: ' || to_char(nCount));
commit;
EXIT WHEN nCount = 0;
END LOOP;
END delete_tab;
/
Let me know how I can add an insert statement within the block to write the progress.
Besides the missing Insert there are a couple other minor issues and a major issue with this code.
ssql1:='delete from '|| tablename ||
' where ROWNUM < ' || nrows || ' and where cast(time_stamp as date) < sysdate - 467';
If the above were valid then when executed it nrows-1 (not nrows as I suspect you are thinking) every time as long as that many rows still exist. However, it is not valid; the where clause is invalid. The proper format for the where clause is
"Where <condition> and <condition> ..."; do not repeat where.
ssql2:='select count(ROWID) from ' || tablename ||
' where cast(time_stamp as date) < sysdate - 467';
There is no reason to count the rowids, every row has exactly 1. ROWID is pseudo column telling Oracle basically where on the disk the row is located. You have probably heard that it is the fastest way to retrieve a specific - that is true - but it is not true selecting rowid itself is faster, it will if anything, be slower. It would require a full table scan and calculation of its value for every row in the table. The optimizer may notice what is happening and change this to select count() but why hope for that just start with count().
Now we get to the worst issue. Unless the total number of rows to delete is an exact multiple of the parameter nrows then the procedure becomes a never ending loop until it throws the exception ORA-22054: underflow error.
This is because result as the only exit condition is "exit when ncount = 0". Suppose you have 1002 rows to delete and nrows is 1000. The first iteration deletes 999 then reduces ncount to 2 (1002-1000). Then the second iteration deletes another 999 and reduces ncount to -998 (2-1000). The procedure continues iterating until eventually ncount gets so small it cannot be held any longer (something like -1*10^39 - 1).
You could change the exit condition to "exit when nCount < 1". But even that is not necessary, no need for the code to calculate the exit condition at all. Let Oracle tell it. The delete statement returns SQL%rowcount that contains the number of rows processed by the last DML statement. If no are processed it returns 0. So the exist condition becomes a simple "exit when sql%rowcount = 0". Lesson: always be careful with equal 0 conditions.
Taking all this into account the procedure becomes. (Also changed variable names as I do not like ssql1, ssql2 and reusing a variable for 2 things. When using such I get confused as to which is which, esp on large procedures.):
create or replace procedure delete_tab
( tablename in varchar2
, nrows in number
)
is
delete_sql varchar2(2000);
count_sql varchar2(2000);
deleted_count number;
expect_delete number;
begin
dbms_output.enable (100000);
count_sql := 'select count(*) from ' || tablename ||
' where time_stamp < current_timestamp - 467';
dbms_output.put_line('COUNT_SQL: ' || count_sql
);
execute immediate count_sql into expect_delete;
dbms_output.put_line('Existing records to be deleted: ' || to_char(expect_delete));
delete_sql := 'delete from '|| tablename ||
' where rownum <= ' || nrows ||
' and cast(time_stamp as date) < sysdate - 467';
dbms_output.put_line('DELETE_SQL: '
|| delete_sql
);
deleted_count := 0;
loop
execute immediate delete_sql;
exit when sql%rowcount = 0;
deleted_count := deleted_count+sql%rowcount;
commit;
end loop;
insert into delete_log( table_name, date_deleted, rows_deleted)
values (tablename, sysdate, deleted_count) ;
commit; -- commit the deletes in final loop and insert
end delete_tab;
See demo (includes example of danger with ncount = 0 and procedure has additional trace information). BTW in a production run my commit interval (nRows) would something like 500000 or larger.

Use cursor in LOOP in new QUERY

"I missing the forest through the trees..."
I want to query each column of a table which I retrieve in a FOR LOOP, but the inner query doesn't return the right thing.
Seems that the inner query not use the current column_name.
DECLARE
v_max_TS TIMESTAMP;
BEGIN
FOR cols IN (SELECT column_name FROM all_tab_cols WHERE table_name = '<tablename>')
LOOP
SELECT
MAX(CURR_TIMESTAMP) INTO v_max_TS
FROM <tablename>
WHERE cols.column_name IS NOT NULL
ORDER BY TO_TIMESTAMP(CURR_TIMESTAMP,'MM/DD/YYYY HH24:MI:SS') DESC;
dbms_output.put_line(cols.column_name || ' ' || v_max_TS);
END LOOP;
END;
Apart from the fact that your query doesn't make much sense (as Boneist wrote as a comment), that won't work as you need to use dynamic SQL (execute immediate) for such a purpose.
Here's an example based on Scott's schema. Have a look, adjust it if necessary.
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(200); -- will hold the SELECT statement
3 v_max varchar2(30);
4 begin
5 for cols in (select column_name
6 from all_tab_cols
7 where table_name = 'DEPT'
8 )
9 loop
10 l_str := 'select max(' || cols.column_name ||') from dept';
11 execute immediate l_str into v_max;
12 dbms_output.put_line(cols.column_name ||': '|| v_max);
13 end loop;
14 end;
15 /
DEPTNO: 40
DNAME: SALES
LOC: NEW YORK
PL/SQL procedure successfully completed.
SQL>

Oracle 10g simple query error

Oracle SQL Developer complains about next SQL though I can't seem to find the reason:
IF to_number(to_char(sysdate, 'HH24')) > 6 THEN
IF to_number(to_char(sysdate, 'HH24')) < 9 THEN
SELECT 1 FROM dual;
ELSE
SELECT (CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) FROM t_job WHERE to_char(start_time, 'yyyy/mm/dd') = to_char(sysdate, 'yyyy/mm/dd');
END IF;
ELSE
SELECT (CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END) FROM t_job WHERE to_char(start_time, 'yyyy/mm/dd') = to_char(sysdate, 'yyyy/mm/dd');
END IF;
What's the mistake in provided query?
Error report:
Error starting at line 7 in command:
ELSE
Error report:
Unknown Command
(CASEWHENRESULT='SUCCESS'THEN1ELSE0END)
---------------------------------------
1
Error starting at line 9 in command:
END IF
Error report:
Unknown Command
There are a couple of problems with your code (which is PL/SQL, not just SQL):
1) You are missing the begin and end around the block.
2) Your selects need an into clause
try:
DECLARE
l_result number;
BEGIN
IF to_number(to_char(sysdate, 'HH24')) > 6 THEN
IF to_number(to_char(sysdate, 'HH24')) < 9 THEN
SELECT 1 INTO l_result FROM dual;
ELSE
SELECT (CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END)
INTO l_result
FROM t_job WHERE to_char(start_time, 'yyyy/mm/dd') = to_char(sysdate, 'yyyy/mm/dd');
END IF;
ELSE
SELECT (CASE WHEN result = 'SUCCESS' THEN 1 ELSE 0 END)
INTO l_result
FROM t_job WHERE to_char(start_time, 'yyyy/mm/dd') = to_char(sysdate, 'yyyy/mm/dd');
END IF;
dbms_output.put_line('result is '||l_result);
END;
Since this is a PL/SQL block, your SELECT statements would need to select the data into some local variable or they would need to be used in a cursor. Which approach you want would depend on how many rows in T_JOB could potentially match the criteria you're specifying. Assuming all three statements would return exactly 1 row, you could do something like this (code simplified to avoid repeating the same query twice)
DECLARE
l_some_local_variable PLS_INTEGER;
BEGIN
IF( to_number( to_char( sysdate, 'HH24' ) ) > 6 and
to_number( to_char( sysdate, 'HH24' ) ) < 9 )
THEN
SELECT 1
INTO l_some_local_variable
FROM dual;
ELSE
SELECT (CASE WHEN result = 'SUCCESS'
THEN 1
ELSE 0
END)
INTO l_some_local_variable
FROM t_job
WHERE trunc( start_time ) = trunc( sysdate );
END IF;
END;
Of course, once you populate the data in your local variable, you would need to actually do something with the value. Potentially, you may want to create a function that returns the local variable rather than using an anonymous PL/SQL block as I have done here.

Resources