plsql get table in 'before alter' trigger - oracle

I have a table ident, and I also have a table ident_hist, which just keeps a log from the table ident. The table ident gets altered a lot, so I want to add the new columns to ident_hist dynamically as well. I have created a procedure which does that:
create or replace procedure prc_create_hist_tabel(p_naam_hist_tabel in varchar2, p_naam_tabel in varchar2) is
cursor c is
select 'alter table ' || p_naam_hist_tabel || ' add ' || column_name || ' ' || data_type || case when data_type = 'DATE' then null else '(' || data_length || ')' end lijn
from user_tab_columns
where TABLE_NAME = upper(p_naam_tabel)
and column_name not in (select column_name from user_tab_columns where table_name = upper(p_naam_hist_tabel));
v_dummy number(1);
begin
begin
select 1 into v_dummy
from user_tab_columns
where TABLE_NAME = upper(p_naam_hist_tabel)
group by 1;
exception when no_data_found then
execute immediate 'create table ' || p_naam_hist_tabel || ' (wijziger varchar2(60) default user, wijzigdatum date default sysdate, constraint pk_' || p_naam_hist_tabel || ' primary key (wijziger, wijzigdatum))';
end;
for i in c
loop
execute immediate i.lijn;
end loop;
end;
My question is, how can I check in my DDL trigger if I'm altering the tabel ident?
I want to do something like this:
create or replace trigger ident_hist_trig before alter on ident
begin
prc_create_hist_tabel('ident_hist', 'ident');
end;
When I try to compile the trigger, I get this error message:
ORA-30506: system triggers cannot be based on tables or views
How can I make my DDL trigger check if I'm altering my table ident? I only want to fire the trigger it when I'm altering the table ident, not any other table.

30506, 00000, "system triggers cannot be based on tables or views"
Cause: An attempt was made to base a system trigger on a table or a
view.
Action: Make sure the type of the trigger is compatible with the base
object.
System triggers are not associated with individual objects.
You can create DDL trigger before before create or alter or drop on SCHEMA(User/Owner). Then you can filter the object names and the DDL types(DROP, ALTER).
Here Tom has explained about this in detail.
Writting DDL_EVENT Triggers

Related

Oracle trigger failed to access user_tab_columns

I am using Oracle 19c, and need to retrieve information from user_tab_columns in a trigger. There is no error when the trigger fires, however there is nothing returns back when query this system table.
create table mysche1.tb_test (id number(1), comment varchar2(20));
insert into mysche1.tb_test value (1, 'This is first line');
create or replace trigger mySche1.AUT_tb_test
after update on
mySche1.tb_test for each row
declare
cursor vco_col_nm
is
select column_name
form user_tab_columns
where table_name = 'tb_test'
colList varchar2(2048);
begin
for colNm in vco_col_nm loop
colList := colList || colNm.column_name || ',';
end loop;
mySche1.pack1.proc1 (colList); --I print out the colist in the PLSQL, which displays nothing.
end;
update mySche1.tb_test set comment = 'second' where id =1;

how to provide multiple bind variable for execute immediate inside pl/sql block

I have a table which contains the metadata of the table. The task is to periodically delete a specific set of tables, provided the information for where condition and how many days the data is retained are present.If a user needs to delete a data on daily basis, he simply enter his table name in audit log. The procedure will do the rest. The example is shown below.
Table structure:
CREATE TABLE delete_tbl_list (
id NUMBER NOT NULL,
table_name VARCHAR2(100) NOT NULL,
column_name VARCHAR2(100) NOT NULL,
day_retented NUMBER NOT NULL,
where_clause VARCHAR2(2000)
);
the day_retended is the number which will tell on how many days the data can hold.
select * from delete_tbl_list
ID TABLE_NAME COLUMN_NAME DAY_RETENTED WHERE_CLAUSE
---------- -----------------------------------------------
1 audit_log log_TS 60
So if i need to delete a table taking log_ts(timestamp) as column with 60days period as retention. The table in query needs to do
delete * from audit_log where log_ts<systimestamp -60
Now i need to do it using bulk delete and more dynamic and hence i wrote the procedure below,
create procedure dynamic_mass_delete as
TYPE tbl_rec_rowid IS TABLE OF ROWID INDEX BY PLS_INTEGER;
lv_del_exec_rec tbl_rec_rowid;
v_limit PLS_INTEGER := 10000;
m_date date:=sysdate;
total_records_deleted number:=0;
l_where delete_tbl_list.where_clause%type;
l_sql varchar2(2000);
TYPE ref_cur_type IS REF CURSOR;
delete_content ref_cur_type;
BEGIN
for i in (select table_name,COLUMN_NAME,DAY_RETENTED,WHERE_CLAUSE from delete_tbl_list) loop
DBMS_OUTPUT.PUT_LINE('tablename..'||i.table_name);
l_where:='';
m_date:=m_date-i.day_retented;
if i.where_clause is not null then
l_where:=' and '||i.where_clause;
end if;
OPEN delete_content FOR 'SELECT rowid from ' || i.table_name ||' where '|| i.COLUMN_NAME || ' <= to_timestamp('''||m_date||''')'||l_where;
LOOP
total_records_deleted := 0;
FETCH delete_content BULK COLLECT INTO lv_del_exec_rec LIMIT v_limit;
FORALL j IN lv_del_exec_rec.first..lv_del_exec_rec.last
execute immediate 'DELETE FROM :1 where rowid=:2 'using i.table_name,lv_del_exec_rec(j);
total_records_deleted := total_records_deleted + SQL%rowcount;
DBMS_OUTPUT.PUT_LINE('Delete count..'||total_records_deleted);
EXIT WHEN delete_content%notfound;
END LOOP;
CLOSE delete_content;
end loop;
EXCEPTION
when others then
DBMS_OUTPUT.PUT_LINE('Error-->'||SQLERRM);
END;
/
Now i getting error in the delete query stating invalid table name, i was not able to write dbms_output inside for all statment. Is it possible to use multiple bind variable inside a pl/sql procedure.
The error which i get is ,
Error-->ORA-00942: table or view does not exist
PL/SQL procedure successfully completed.
The table very much exists, but it is throwing error, i was not able to print inside the forall block too.
Switch to
execute immediate 'DELETE FROM ' || i.table_name ||' where rowid = ' || lv_del_exec_rec(j);
You can simplify your code as follows:
BEGIN
FOR TBL_DETAILS IN (
SELECT
TABLE_NAME,
COLUMN_NAME,
DAY_RETENTED,
WHERE_CLAUSE
FROM
DELETE_TBL_LIST
) LOOP
DBMS_OUTPUT.PUT_LINE('tablename..' || TBL_DETAILS.TABLE_NAME);
EXECUTE IMMEDIATE 'DELETE FROM '
|| TBL_DETAILS.TABLE_NAME
|| ' WHERE '
|| TBL_DETAILS.COLUMN_NAME
|| ' < SYSTIMESTAMP - '
|| TBL_DETAILS.DAY_RETENTED
|| CASE
WHEN TBL_DETAILS.WHERE_CLAUSE IS NOT NULL THEN ' AND ' || TBL_DETAILS.WHERE_CLAUSE
END;
DBMS_OUTPUT.PUT_LINE('Delete count..' || SQL%ROWCOUNT);
END LOOP;
END;
/
Hope, This will help you in creating simpler code.
Cheers!!

Create procedure in oracle DB for finding and deleting foreign key constraints for specific tables

I am new to Oracle, I am trying to write a procedure in Oracle to delete foreign key constraints for a table. I have already done this for MySQL and its working. I am not sure with the syntax, apologies for that, but my query is working If I run it individually. I want to do the same thing(removing foreign key constraints) for multiple tables and don't want to write queries multiple times. hence first I am finding the foreign keys associated with that table , storing them in cursor and later removing all of the foreign keys by creating and executing drop constraints query associated with that table. following code is giving multiple errors to me.
CREATE OR REPLACE PROCEDURE removeConstraintsForTable(vTableName IN varchar2) IS
BEGIN
cName VARCHAR(2048);
sql_stmt VARCHAR2(2048);
CURSOR cur IS
SELECT DISTINCT CONSTRAINT_NAME
FROM ALL_CONSTRAINTS WHERE OWNER= sys_context('userenv','current_schema')
AND TABLE_NAME = vTableName AND CONSTRAINT_TYPE='R';
BEGIN
OPEN cur;
LOOP
FETCH cur INTO cName;
EXIT WHEN cur%notfound;
sql_stmt := CONCAT(CONCAT(CONCAT('ALTER TABLE ',vTableName),CONCAT(' DROP FOREIGN KEY ',cName)),';');
SELECT sql_stmt FROM dual;
INSERT INTO TEMP(Name) VALUES(sql_stmt);
COMMIT;
END LOOP;
END
/
CALL removeConstraintsForTable('table1');
CALL removeConstraintsForTable('table2');
CALL removeConstraintsForTable('table3');
CALL removeConstraintsForTable('table4');
COMMIT;
You have an extra BEGIN right at the start of your procedure, and the final END is missing a semicolon. You shouldn't really be using VARCHAR, and you could declare the cName variable using the data dictionary anyway; however an implicit loop will be simpler, as will using the concatenation operator || instead of nested CONCAT() calls, and the generated statement should not end in a semicolon:
create or replace procedure removeconstraintsfortable(p_table_name in varchar2) is
sql_stmt varchar2(2048);
begin
for rec in (
select owner, constraint_name
from all_constraints
where owner = sys_context('userenv','current_schema')
and table_name = p_table_name
and constraint_type = 'R'
)
loop
sql_stmt := 'ALTER TABLE "' || rec.owner || '"."' || p_table_name || '"'
|| ' DROP CONSTRAINT "' || rec.constraint_name || '"';
insert into temp(name) values(sql_stmt);
end loop;
commit;
end;
/
As pointed out in comments, the generated statement should be drop constraint.
I'm not sure why you're inserting into a table or where you execute the statement, but you can do it all in one if you prefer:
create or replace procedure removeconstraintsfortable(p_table_name in varchar2) is
sql_stmt varchar2(2048);
begin
for rec in (
select owner, constraint_name
from all_constraints
where owner = sys_context('userenv','current_schema')
and table_name = p_table_name
and constraint_type = 'R'
)
loop
sql_stmt := 'ALTER TABLE "' || rec.owner || '"."' || p_table_name || '"'
|| ' DROP CONSTRAINT "' || rec.constraint_name || '"';
dbms_output.put_line(sql_stmt);
execute immediate sql_stmt;
end loop;
end;
/
The dbms_output call just shows you the generated statement(s), before execute immediate executes it, well, immediately.
Quick demo; very basic table set-up:
create table t42 (id number primary key);
create table t43 (id number references t42 (id));
select table_name, constraint_name, constraint_type
from all_constraints
where table_name in ('T42', 'T43');
TABLE_NAME CONSTRAINT_NAME C
------------------------------ ------------------------------ -
T43 SYS_C00138153 R
T42 SYS_C00138152 P
Then call the procedure which shows the generated statement:
set serveroutput on
exec removeConstraintsForTable('T43');
ALTER TABLE "STACKOVERFLOW"."T43" DROP CONSTRAINT "SYS_C00138153"
PL/SQL procedure successfully completed.
and then check the constraint has gone:
select table_name, constraint_name, constraint_type
from all_constraints
where table_name in ('T42', 'T43');
TABLE_NAME CONSTRAINT_NAME C
------------------------------ ------------------------------ -
T42 SYS_C00138152 P
I fixed a number of syntax issues for you. Try this.
CREATE OR REPLACE PROCEDURE removeConstraintsForTable(vTableName IN varchar2) IS
cName VARCHAR2(30); -- identifiers are max 30 chars
sql_stmt VARCHAR2(2048);
CURSOR cur IS
SELECT DISTINCT CONSTRAINT_NAME
FROM USER_CONSTRAINTS
WHERE TABLE_NAME = vTableName AND CONSTRAINT_TYPE='R';
BEGIN
OPEN cur;
LOOP
FETCH cur INTO cName;
EXIT WHEN cur%notfound;
sql_stmt := 'ALTER TABLE ' || vTableName || ' DROP CONSTRAINT ' || cName;
INSERT INTO RANGERADMIN1.TEMP(Name) VALUES(sql_stmt);
END LOOP;
COMMIT;
END removeConstraintsForTable;
/
call removeConstraintsForTable('table1');
call removeConstraintsForTable('table2');
call removeConstraintsForTable('table3');
call removeConstraintsForTable('table4');
-- COMMIT; -- not necessary

When does user_tab_columns get updated?

In another question I tried to create a hist table, which keeps a log from the given table. With the answers in that question, I tried to create something new.
Since it is not possible to create a system trigger on tables or views, I created a DDL trigger like this:
create or replace trigger ident_hist_trig after alter on schema
declare
v_table varchar2(30);
begin
select upper(ora_dict_obj_name) into v_table from dual;
if (v_table = 'Z_IDENT') then
prc_create_hist_tabel('z_ident_hist', 'z_ident');
elsif (v_table = 'D_IDENT') then
prc_create_hist_tabel('d_ident_hist', 'd_ident');
elsif (v_table = 'X_IDENT') then
prc_create_hist_tabel('x_ident_hist', 'x_ident');
else
null;
end if;
end;
/
The procedure prc_create_hist_tabel looks like this:
create or replace procedure prc_create_hist_tabel(p_naam_hist_tabel in varchar2, p_naam_tabel in varchar2) is
cursor c is
select 'alter table ' || p_naam_hist_tabel || ' add ' || column_name || ' ' || data_type || case when data_type = 'DATE' then null else '(' || data_length || ')' end lijn
from user_tab_columns
where TABLE_NAME = upper(p_naam_tabel)
and column_name not in (select column_name from user_tab_columns where table_name = upper(p_naam_hist_tabel));
v_dummy number(1);
cursor trig is
select column_name || ',' kolom, ':old.' || column_name || ',' old
from user_tab_columns
where table_name = upper(p_naam_tabel);
v_trigger_sql varchar2(32767);
begin
begin
select 1 into v_dummy
from user_tab_columns
where TABLE_NAME = upper(p_naam_hist_tabel)
group by 1;
exception when no_data_found then
execute immediate 'create table ' || p_naam_hist_tabel || ' (wijziger varchar2(60) default user, wijzigdatum date default sysdate, constraint pk_' || p_naam_hist_tabel || ' primary key (wijziger, wijzigdatum))';
end;
dbms_output.put_line('BBB');
for i in c
loop
begin
dbms_output.put_line(i.lijn);
execute immediate i.lijn;
exception when others then
dbms_output.put_line(i.lijn);
end;
end loop;
v_trigger_sql := 'create or replace trigger ' || p_naam_tabel || '_hist_trig after update on ' || p_naam_tabel || ' for each row begin insert into ' || p_naam_hist_tabel || ' (';
for v_lijn in trig
loop
v_trigger_sql := v_trigger_sql || v_lijn.kolom;
end loop;
v_trigger_sql := substr(v_trigger_sql, 1, length(v_trigger_sql) - 1);
v_trigger_sql := v_trigger_sql || ') values (';
for v_lijn in trig
loop
v_trigger_sql := v_trigger_sql || v_lijn.old;
end loop;
v_trigger_sql := substr(v_trigger_sql, 1, length(v_trigger_sql) - 1);
v_trigger_sql := v_trigger_sql || '); end;';
execute immediate v_trigger_sql;
end;
/
In short what that function does, is maintain the history table. If it doesn't exist, it will create one, and if it exists, it will add the new columns to it. The procedure also creates a new trigger which will write the old values into the history table after update.
But when I alter one of the tables x_ident, z_ident or d_ident, the cursor c will return nothing (I can check that with the print when I loop through it). Although when execute the select after I altered my table, then I do get results.
The results I get from altering the table d_ident are these:
BBB
d_ident: Table altered.
But I guess it should be the other way around, I think that the procedure prc_create_hist_tabel is executed before the alter table actually goes off, and I guess I should get something like this:
d_ident: Table altered.
BBB
Any help would be apreciated. I tried to create a trigger on insert on user_tab_columns, but that gave me ORA-25001: cannot create this trigger type on views.
I tried with a sleep command as well, but that didn't work either.
This won't work. Even if you were able to get the column that is being added to the table in your trigger, if you tried to actually do DDL in a trigger, you'd get an error that DDL isn't allowed in a trigger.
I'd expect that the right way to approach this would be to make the call to prc_create_hist_tabel as part of your promotion scripts. Reasonable systems don't add columns to tables willy-nilly. The DDL is part of a promotion that exists in source control and gets deployed after testing. If your promotion scripts failed to modify the history table, you'd find out during testing that you missed a step and the change would never go to production. Having changes happen automatically means that they're not in change control which makes it more difficult to do a build from change control.
If you are determined to do this automatically, your trigger would need to submit a job, realistically using dbms_job not the newer dbms_scheduler, that calls the procedure. That job would run after the transaction the DDL trigger is a part of committed. At that point, the column would be visible in dba_tab_columns. And your job is free to do DDL.

Drop Schema in Oracle: SQL Error: No more data to read from socket

I'm trying to drop a schema in oracle 11g on our dev environment and I get back SQL Error: No more data to read from socket. There is no load on the schema as it's just a dev db. It's a small db without anything crazy going on. I see this error all the time. Restarting the instance sometimes resolves the problem. I can't seem to find any information that would point to a solution. Thanks!
I understand that this message often arises due to a bug. Also, when it appears an entry in your alert log and/or a trace file will contain more detail on what the error might actually be. To find your trace file for the session run:
select U_DUMP.value
|| '/'
|| DB_NAME.value
|| '_ora_'
|| V$PROCESS.SPID
|| nvl2(V$PROCESS.TRACEID, '_' || V$PROCESS.TRACEID, null)
|| '.trc'
"Trace File"
from V$PARAMETER U_DUMP
cross join V$PARAMETER DB_NAME
cross join V$PROCESS
join V$SESSION
on V$PROCESS.ADDR = V$SESSION.PADDR
where U_DUMP.NAME = 'user_dump_dest'
and DB_NAME.NAME = 'db_name'
and v$session.audsid=sys_context('userenv','sessionid');
A dba at my company gave me this one. It's
CREATE OR REPLACE PROCEDURE "SYS"."DROP_SCHEMA_FAST" (pSchema IN
VARCHAR2)
IS
cnt NUMBER(5) := 0;
sql1 varchar2(4000);
x PLS_INTEGER;
--disable constraints:
cursor cur1 is select 'alter table ' || OWNER ||'.'||table_name||' disable constraint '||constraint_name sql2
from all_constraints where owner=pSchema and status='ENABLED'
and table_name not like 'BIN$%' and constraint_name not like 'SYS_%' and constraint_name not like '%PK%';
cursor cur2 is select 'alter table ' || OWNER ||'.'||table_name||' disable constraint '||constraint_name sql2
from all_constraints where owner=pSchema and status='ENABLED'
and table_name not like 'BIN$%' and constraint_name not like 'SYS_%';
--truncate all tables:
cursor cur3 is select 'truncate table ' || OWNER ||'.'||table_name sql2 from all_tables where owner=pSchema
and table_name not like 'BIN$%';
BEGIN
SELECT COUNT(*) INTO cnt FROM dba_users WHERE UPPER(username) = UPPER(pSchema);
IF (cnt <= 0) THEN
RETURN;
END IF;
sql1 := 'ALTER USER ' || UPPER(pSchema) || ' ACCOUNT LOCK';
EXECUTE IMMEDIATE sql1;
--disable constraints:
FOR ao_rec IN cur1 LOOP
EXECUTE IMMEDIATE ao_rec.sql2;
END LOOP;
FOR ao_rec IN cur2 LOOP
EXECUTE IMMEDIATE ao_rec.sql2;
END LOOP;
--truncate all tables:
FOR ao_rec IN cur3 LOOP
EXECUTE IMMEDIATE ao_rec.sql2;
END LOOP;
--drop schema:
sql1 := 'DROP USER ' || UPPER(pSchema) || ' CASCADE';
EXECUTE IMMEDIATE sql1;
exception when others then null;
END;
Also had this problem, got fixed by setting "PLScope identifiers:" to "None" in Tools->Preferences ->Database->PL/SQL Compiler

Resources