Oracle trigger failed to access user_tab_columns - oracle

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;

Related

Create backup table concatenated with sysdate before deleting the rows in oracle procedure

I have created a package and defined a procedure to delete specific rows retrieved by the cursor.
Before the rows were deleted from the table, I want to take a backup of those records every time the package is compiled and I need the backup table to be created as tablename concatenated with sysdate.
ex: if table name is emp, backup table should be created as emp_2020_10_16
Below is the sample code I have created:
PROCEDURE DELETE_REC(
P_retcode NUMBER,
P_errorbuff VARCHAR2,
P_unit_id NUMBER,
P_join_date VARCHAR2
)
IS
CURSOR cur1
IS
SELECT unit_ID,dept_ID,join_DATE
FROM EMP MMT
WHERE MMT.dep_TYPE_ID IN (44,35)
AND MMT.unit_id = P_unit_id
AND MMT.join_date < to_date(P_join_date,'RRRR/MM/DD HH24:MI:SS');
BEGIN
--begin
-- EXECUTE IMMEDIATE 'Create table EMP_' || to_char(sysdate,'yyyy_mm_dd') || ' as select * from EMP MMT WHERE MMT.dep_TYPE_ID IN (44,35)
AND MMT.unit_id = P_unit_id
AND MMT.join_date < to_date(P_join_date,'RRRR/MM/DD HH24:MI:SS');
--
-- end;
/*Here i would like to create backup table like above before executing the below delete statement but i am not sure about the correct standards that i should be using for above dynamic statement*/
FOR val IN cur1
LOOP
DELETE
FROM EMP MMT
WHERE MMT.dept_ID= val.dept_id;
How can I backup the table using above dynamic statement in best possible way? I am still learning PL&SQL.
Maybe sth like this would help:
create table employees as select * from hr.employees;
--drop table emp_2020_10_18;
--drop table employees;
----------------
declare
vTabName varchar2(50);
nDept_id number := 10;
nCnt number := 0;
vSQL varchar2(1000);
begin
vTabName := 'emp_'||to_char(sysdate, 'yyyy_mm_dd');
-- check if table exists
begin
execute immediate 'select count(*) from emp_tmp' into nCnt;
exception when others then
nCnt := -1;
end;
-- if not exists create one
if nCnt = -1 then
execute immediate 'create table '|| vTabName||' as select * from employees where 1=2' ;
end if;
execute immediate 'insert into '|| vTabName ||' select * from employees where department_id = :nDept_id' using nDept_id;
delete from employees where department_id = nDept_id;
exception when others then
dbms_output.put_line(sqlerrm);
end;
/

Put Column Name in Variable and use it in output statement

Here is What i actually wanted to do, Fetch Data From a Table without knowing any columns but i.j.Column_Name gives an error, so i wanted to put it in a variable. After reading the comments i think it's not possible
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
CURSOR C2 IS SELECT Table_Name,Column_Name FROM user_tab_columns
WHERE data_type='VARCHAR2';
v_table Varchar2(256);
v_Col varchar2(200);
BEGIN
FOR i in C1 LOOP
FOR j in (SELECT Column_Name FROM user_tab_columns WHERE
Table_Name='Table_Name') LOOP
dbms_output.put_line(i.j.Column_Name);
END LOOP;
END LOOP;
END;
/
No, There is no Column Named v_Col
You can't refer to a field in a record (which is what the cursor loop is giving you) dynamically. If you need to do flexibly then you can use dbms_sql (possibly adapting this approach), but in the scenario you've shown you could use dynamic SQl to only get the column you want in the cursor:
-- dummy data
create table table_name (id number, column_name varchar2(10), other_col date);
insert into table_name values (1, 'Test 1', sysdate);
insert into table_name values (2, 'Test 2', sysdate);
DECLARE
CURSOR C1 IS SELECT * FROM Table_Name;
v_Cur sys_refcursor;
v_Col varchar2(200);
v_Val varchar2(4000);
BEGIN
v_Col:= 'Column_Name';
OPEN v_Cur for 'SELECT ' || v_Col || ' FROM Table_Name';
LOOP
FETCH v_Cur INTO v_Val;
EXIT WHEN v_Cur%notfound;
dbms_output.put_line(v_val);
END LOOP;
END;
/
Test 1
Test 2
PL/SQL procedure successfully completed.
The downside of this is that whatever the data type of the target column is, you have to implicitly convert it to a string; but you would be doing that in the dbms_output call anyway. So if you change the column you want to print:
v_Col:= 'Other_Col';
then the output from my dummy data would be:
2018-08-23
2018-08-23
PL/SQL procedure successfully completed.
where the date value is being implicitly formatted as a string using my current NLS session settings.
You could get more advanced by checking the data type in user_tab_columns and changing the dynamic query and/or the fetch and handling, but it isn't clear what you really need to do.

plsql get table in 'before alter' trigger

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

PL SQL- Using Dynamic SQL to Generate Delete Statements

I want to create a stored procedure using PL SQL that allows me to find all tables that contain a specific column, and then delete records from those tables that have a specific value in that column.
For example, I want to find all tables that have the column "year" and then delete all records from all of those tables that have the year "2012"(this year will be a parameter that will be entered upon execution)
My attempt at this has been to create a stored procedure, use a cursor to get all of the tables that have this column of "year" and then loop through that cursor using Dynamic SQL which will generate my Delete Statements that I can execute.
CREATE OR REPLACE PROCEDURE year_deletion
(
p_year NUMBER --Input of the year for records to be deleted
)
IS
CURSOR c1 --Cursor that will find all tables that have the YEAR column
IS
SELECT owner, table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP';
BEGIN
FOR i IN c1 LOOP --Loop through all of the tables that the cursor found, generate a SQL statement for each table that will delete all of the records that have the year of p_year
EXECUTE IMMEDIATE ('SELECT * FROM' ||i.table_name|| 'WHERE YEAR = '||p_year||';');
END LOOP;
END;
Disclaimer: I am using a Select * From instead of a DELETE * From for testing purposes, I will change this when it this procedure executes correctly.
So far this stored procedure compiles correctly, but gives me an error during execution that a FROM keyword was expected but not found. Is this the best method to use for my purpose?
Is must be like this:
EXECUTE IMMEDIATE 'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' USING p_year;
Note the space after FROM and before WHERE.
You cannot simply replace DELETE by SELECT ... for testing because for SELECT you must have an INTO clause.
Your entire procedure can be like this
CREATE OR REPLACE PROCEDURE year_deletion(p_year IN NUMBER) IS
CURSOR c1 IS
SELECT owner, table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP';
res NUMBER;
BEGIN
FOR i IN c1 LOOP
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' INTO res USING p_year;
DBMS_OUTPUT.PUT_LINE (res ||' rows will be deleted from table '||i.table_name );
EXECUTE IMMEDIATE
'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' USING p_year;
END LOOP;
END;
Hello you can try the below code. It will surely help you out.
CREATE OR REPLACE PROCEDURE year_deletion(
p_year IN NUMBER --Input of the year for records to be deleted
)
IS
BEGIN
FOR i IN (SELECT owner,
table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP')
LOOP --Loop through all of the tables that the cursor found, generate a SQL statement for each table that will delete all of the records that have the year of p_year
EXECUTE IMMEDIATE 'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = '||p_year;
END LOOP;
END;

Create table if it does not exist, and enter one row after creating

I need to create a table if it does not exist, and when it is created add a single row to it.
I'm new to oracle and PL/SQL so I basically need an equivalent of the following T-SQL:
IF OBJECT_ID('my_table', 'U') IS NULL
BEGIN
CREATE TABLE my_table(id numeric(38,0), date datetime)
INSERT INTO my_table
VALUES (NULL, 0)
END
if you want to check table creation
DECLARE count NUMBER;
BEGIN
count := 0;
SELECT COUNT(1) INTO count from user_tables WHERE table_name= 'MY_TABLE';
IF COL_COUNT = 0 THEN
EXECUTE IMMEDIATE 'create table ....';
END IF;
END;
/
A checking for DML .please note you have to sepcify your pk columns and values.
DECLARE count NUMBER;
BEGIN
count := 0;
SELECT COUNT(1) INTO count from MY_TABLE WHERE id= 0 and name='Something';
IF COL_COUNT = 0 THEN
EXECUTE IMMEDIATE 'insert into MY_TABLE (id,name) values(0,''something'') ';
END IF;
END;
/
also note I recomand to specify columns when you insert into a table
Another approach is to use exception logic. I changed field names and types according to Oracle rules
declare
eAlreadyExists exception;
pragma exception_init(eAlreadyExists, -00955);
begin
execute immediate 'CREATE TABLE my_table(id number, dateof date)';
execute immediate 'INSERT INTO my_table VALUES (NULL, sysdate)';
exception when eAlreadyExists then
null;
end;
but may be it is not a good idea to create tables dynamically
In my opinion, you should not be creating objects on the fly. You should think about your design before implementing it.
Anyway, if you really want to do it this way, then you need to do it programmatically in PL/SQL (ab)using EXECUTE IMMEDIATE.
However, I would prefer the CTAS i.e. create table as select if you want to create a table ta once with a single row. For example,
SQL> CREATE TABLE t AS SELECT 1 id, SYSDATE dt FROM DUAL;
Table created.
SQL> SELECT * FROM t;
ID DT
---------- ---------
1 29-MAY-15
SQL>
The table is created permanently.
If you are looking for a temporary table, which you could use to store session specific data , then look at creating Global temporary table.
From documentation,
Use the CREATE GLOBAL TEMPORARY TABLE statement to create a temporary
table. The ON COMMIT clause indicates if the data in the table is
transaction-specific (the default) or session-specific
You can use NOT EXISTS with select statement:
IF NOT EXISTS(SELECT 1 FROM my_table) THEN
CREATE TABLE my_table(id NUMBER, date date);
COMMIT;
INSERT INTO my_table(id, date) values (NULL, O);
COMMIT;
END IF;
UPDATE
According to the comment, I cannot use Exist directly in PL/SQL. So this is another way to do it:
begin
select case
when exists(select 1
from my_table)
then 1
else 0
end into l_exists
from dual;
if (l_exists = 1)
then
-- anything
else
EXECUTE IMMEDIATE 'CREATE TABLE my_table(id NUMBER, date date)';
EXECUTE IMMEDIATE 'INSERT INTO my_table(id, date) values (NULL, O)';
end if;
end;

Resources