How to insert data into table. But table creating at runtime, is it possible or not? - oracle

create or replace procedure p1(p_deptno in number)
is
type t is table of emp%rowtype
index by binary_integer;
v_emp t;
begin
execute immediate 'create table test as select * from emp where ename is null';
select * bulk collect into v_emp from emp where deptno=p_deptno;
for i in v_emp.first..v_emp.last
loop
insert into test(empno, ename, job, mgr, hiredate, sal, comm, deptno) values (v_emp(i).empno, v_emp(i).ename, v_emp(i).job, v_emp(i).mgr, v_emp(i).hiredate, v_emp(i).sal, v_emp(i).comm, v_emp(i).deptno);
end loop;
exception
when value_error then
dbms_output.put_line('Give proper deptno');
end p1;
/
I am getting this error:
PL/SQL: SQL Statement ignored
PL/SQL: ORA-00942: table or view does not exist

If you're creating the table at runtime you must use dynamic SQL to perform the insert. Another problem is that you're sucking that data into an in-memory table, then performing the inserts one-by-one. I suggest something like:
create or replace procedure p1(p_deptno in number) is
type t is table of emp%rowtype
index by binary_integer;
v_emp t;
begin
execute immediate 'create table test as select * from emp where ename is null';
execute immediate
'insert into test
select *
from emp
where deptno = :1' USING p_deptno;
exception
when value_error then
dbms_output.put_line('Give proper deptno');
end p1;
You can still do the inserts one-by-one using dynamic SQL, but it seems like a waste of time and effort.
Best of luck.

When creating a stored procedure in Oracle all referenced objects (in your case table "test" and "emp") in that stored procedure are checked in advance at compile time for appropriate grants/ownership of the referenced object. As the object "test" doesn't exist at compile time there naturally aren't any appropriate grants/ownership for the owner of the stored procedure and you get the error that you specified. To remedy the situation you can put the insert statement in "EXECUTE IMMEDIATE" statement just as #Bob Jarvis stated , this way the check doesn't happen at compile time but at run time and the check succeedes because the object is created at run time and exists along with appropriate grant/ownership.
execute immediate 'insert into test select * from emp where deptno = '|| p_deptno;

Related

Procedure to Create Backup Table For multiple table each having different Where condition

Create or replace procedure PROC AS
V_TABLE_NAME VARCHAR2(255);
V_LIST SYS_REFCURSOR;
DATE_VALUE_INS VARCHAR2(10);
BEGIN
DATE_VALUE_INS:=TO_CHAR(SYSDATE,'YYMMDD');
OPEN V_LIST FOR
SELECT NAME FROM DW.table_name_list ;
LOOP
FETCH V_LIST
INTO V_TABLE_NAME;
EXIT WHEN V_LIST%NOTFOUND;
EXECUTE IMMEDIATE 'CREATE TABLE Schema.'||V_TABLE_NAME||'_'||DATE_VALUE_INS||' AS SELECT * FROM DW.'||V_TABLE_NAME;
END LOOP;
CLOSE V_LIST;
end;
I have created this Proc which takes value from a table which has Table_name and create Backup using Execute Immediate.
Now the requirement has changed that i only need to create backup for partial records (i.e. where clause on each table )
I have 6 tables as such .
New Approach i am thinking is :
EXECUTE IMMEDIATE 'CREATE TABLE Schema.'||V_TABLE_NAME||'_'||DATE_VALUE_INS||' AS SELECT * FROM DW.'||V_TABLE_NAME where some condition;
But the problem becomes all 6 have different column to filter on.
My Ask is How should I change my design of proc to Adjust this new Requirement.
6 tables? Why bother? Create a procedure which - depending on table name passed as a parameter - in IF-THEN-ELSE runs 6 different CREATE TABLE statements.
On the other hand, another approach would be to create backup tables in advance (at SQL level), add BACKUP_DATE column to each of them, and - in procedure - just perform INSERT operation which doesn't require dynamic SQL at all.
For example:
create table emp_backup as select * from emp where 1 = 2;
alter table emp_backup add backup_date date;
create or replace procedure p_backup (par_table_name in varchar2) is
begin
if par_table_name = 'EMP' then
insert into emp_backup (empno, ename, job, sal, backup_date)
select empno, ename, job, sal, trunc(sysdate)
from emp
where deptno = 20; --> here's your WHERE condition
elsif par_table_name = 'DEPT' then
insert into dept_backup (...)
select ..., trunc(sysdate)
from dept
where loc = 'DALLAS';
elsif ...
...
end if;
end;
/
Doing so, you'd easier access backup data as you'd query only one table, filtered by BACKUP_DATE. That's also good if you have to search for some data that changed several days ago, but you don't know exact day. What would you rather do: query 10 tables (and still not find what you're looking for), or query just one table and find that info immediately?

Table exists in stored procedure while used in select but not when Used in Insert statement

My stored procedure is like this:
create or replace procedure tpk.sp_Test_proc
IS
err_code NUMBER;
err_msg VARCHAR (500);
v_tbl_cnt NUMBER;
v_tbl_valid NUMBER;
Begin
SELECT COUNT(*) INTO v_tbl_cnt FROM USER_TABLES
WHERE TABLE_NAME IN (UPPER('Tbl1'),UPPER('tbl2'),UPPER('tbl3'));
IF(v_tbl_cnt =3) THEN
EXECUTE IMMEDIATE 'TRUNCATE TABLE Tbl1';
EXECUTE IMMEDIATE 'TRUNCATE TABLE Tbl2';
EXECUTE IMMEDIATE 'TRUNCATE TABLE Tbl3';
EXECUTE IMMEDIATE 'DROP TABLE Tbl1';
EXECUTE IMMEDIATE 'DROP TABLE Tbl2';
EXECUTE IMMEDIATE 'DROP TABLE Tbl3';
EXECUTE IMMEDIATE
'CREATE global temporary TABLE tbl1
( Id Integer... )'
Insert into tbl1
Select * from another_schema.Dw_table /* In this line it throws error Table does not exist */
end if;
end;
I tired same table with store procedure only to fetch the data its working there but when I used in Insert statement it throws an error
PL/SQL: ORA-00942 table or view does not exist.
I am totally confused - what's wrong here?
Select * from another_schema.Dw_table
You don't have a privilege to select from that table. Even if you think you do (granted via a role), it won't work in stored procedures - you have to grant it directly to user you're connected to.
Besides, there's no point in truncating tables first, and dropping them next. Just drop them.
Furthermore, there's rarely need to create tables dynamically (the way you do it), especially global temporary tables. Create them once, use them many times. No dropping. No (re)creating them in PL/SQL.

Can I directly define a trigger in all_triggers table on a table?

I am performing an archival process on a huge database and it involves deleting the production active table and renaming another table to be the new production table. When dropping the production active table, the triggers also get deleted. So I am just taking a backup of the triggers defined on my table using
select * from all_triggers where table_name=mytablename;
My question is, can I directly copy these triggers in to the all_triggers table after I rename my other table to be the new production active table? Will the triggers still work?
Same question for defining indexes and constraints too.
Copying the triggers from one table to another can be done by copying DDL, and not updating all_triggers table. This can be done by using DBMS_METADATA.
The closest practical example I found here: Copy Triggers when you Copy a Table
The following script can be amended as per your need:
declare
p_src_tbl varchar2(30):= 'PERSONS'; --your table name
p_trg_tbl varchar2(30):= 'PSN2'; --your trigger name
l_ddl varchar2(32000);
begin
execute immediate 'create table '||p_trg_tbl||' as select * from '||p_src_tbl||' where 1=2';
for trg in (select trigger_name from user_triggers where table_name = p_src_tbl) loop
l_ddl:= cast(replace(replace(dbms_metadata.get_ddl( 'TRIGGER', trg.trigger_name),p_src_tbl,p_trg_tbl),trg.trigger_name,substr(p_trg_tbl||trg.trigger_name, 1, 30)) as varchar2);
execute immediate substr(l_ddl, 1, instr(l_ddl,'ALTER TRIGGER')-1);
end loop;
end;
/
No, you cannot directly manipulate data dictionary tables. You can't insert data directly into all_triggers (the same goes for any data dictionary table). I guess you probably could given enough hacking. It just wouldn't work and would render your database unsupported.
The correct way to go is to script out your triggers and reapply them later. If you want to do this programmatically, you can use the dbms_metadata package. If you want to get the DDL for each of the triggers on a table, you can do something like
select dbms_metadata.get_ddl( 'TRIGGER', t.trigger_name, t.owner )
from all_triggers t
where table_owner = <<owner of table>>
and table_name = <<name of table>>
To replicate your scenario i have prepared below snippet. Let me know if this helps.
--Simple example to copy Trigger from one table to another
CREATE TABLE EMP_V1 AS
SELECT * FROM EMP;
--Creating Trigger on Old Table for Example purpose
CREATE OR REPLACE TRIGGER EMP_OLD_TRIGGER
AFTER INSERT OR UPDATE ON EMP FOR EACH ROW
DECLARE
LV_ERR_CODE_OUT NUMBER;
LV_ERR_MSG_OUT VARCHAR2(2000);
BEGIN
dbms_output.put_line('Your code for data Manipulations');
--Like Insert update or DELETE activities
END;
-- To replace this trigger for emp_v2 table
set serveroutput on;
DECLARE
lv_var LONG;
BEGIN
FOR i IN (
SELECT OWNER,TRIGGER_NAME,DBMS_METADATA.GET_DDL('TRIGGER','EMP_OLD_TRIGGER') ddl_script FROM all_triggers
WHERE OWNER = 'AVROY') LOOP
NULL;
lv_var:=REPLACE(i.ddl_script,'ON EMP FOR EACH ROW','ON EMP_V1 FOR EACH ROW');
dbms_output.put_line(substr(lv_var,1,INSTR(lv_var,'ALTER TRIGGER',1)-1));
EXECUTE IMMEDIATE 'DROP TRIGGER '||I.TRIGGER_NAME;
EXECUTE IMMEDIATE lv_var;
END LOOP;
END;
--Check if DDL manipulation has been done for not
SELECT OWNER,TRIGGER_NAME,DBMS_METADATA.GET_DDL('TRIGGER','EMP_OLD_TRIGGER') ddl_script FROM all_triggers
WHERE OWNER = 'AVROY';
---------------------------------OUTPUT----------------------------------------
"
CREATE OR REPLACE TRIGGER "AVROY"."EMP_OLD_TRIGGER"
AFTER INSERT OR UPDATE ON EMP_V1 FOR EACH ROW
DECLARE
LV_ERR_CODE_OUT NUMBER;
LV_ERR_MSG_OUT VARCHAR2(2000);
BEGIN
dbms_output.put_line('Your code for data Manipulations');
--Like Insert update or DELETE activities
END;
"
-----------------------------OUTPUT----------------------------------------------

FORALL+ EXECUTE IMMEDIATE + INSERT Into tbl SELECT

I have got stuck in below and getting syntax error - Please help.
Basically I am using a collection to store few department ids and then would like to use these department ids as a filter condition while inserting data into emp table in FORALL statement.
Below is sample code:
while compiling this code i am getting error, my requirement is to use INSERT INTO table select * from table and cannot avoid it so please suggest.
create or replace Procedure abc(dblink VARCHAR2)
CURSOR dept_id is select dept_ids from dept;
TYPE nt_dept_detail IS TABLE OF VARCHAR2(25);
l_dept_array nt_dept_detail;
Begin
OPEN dept_id;
FETCH dept_id BULK COLLECT INTO l_dept_array;
IF l_dept_array.COUNT() > 0 THEN
FORALL i IN 1..l_dept_array.COUNT SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'INSERT INTO stg_emp SELECT
Dept,''DEPT_10'' FROM dept_emp'||dblink||' WHERE
dept_id = '||l_dept_array(i)||'';
COMMIT;
END IF;
CLOSE dept_id;
end abc;
Why are you bothering to use cursors, arrays etc in the first place? Why can't you just do a simple insert as select?
Problems with your procedure as listed above:
You don't declare procedures like Procedure abc () - for a standalone procedure, you would do create or replace procedure abc as, or in a package: procedure abc is
You reference a variable called "dblink" that isn't declared anywhere.
You didn't put end abc; at the end of your procedure (I hope that was just a mis-c&p?)
You're effectively doing a simple insert as select, but you're way over-complicating it, plus you're making your code less performant.
You've not listed the column names that you're trying to insert into; if stg_emp has more than two columns or ends up having columns added, your code is going to fail.
Assuming your dblink name isn't known until runtime, then here's something that would do what you're after:
create Procedure abc (dblink in varchar2)
is
begin
execute immediate 'insert into stg_emp select dept, ''DEPT_10'' from dept_emp#'||dblink||
' where dept_id in (select dept_ids from dept)';
commit;
end abc;
/
If, however, you do know the dblink name, then you'd just get rid of the execute immediate and do:
create Procedure abc (dblink in varchar2)
is
begin
insert into stg_emp -- best to list the column names you're inserting into here
select dept, 'DEPT_10'
from dept_emp#dblink
where dept_id in (select dept_ids from dept);
commit;
end abc;
/
There appears te be a lot wrong with this code.
1) why the execute immediate? Is there any explicit requirement for that? No, than don't use it
2) where is the dblink variable declared?
3) as Boneist already stated, why not a simple subselect in the insert statement?
INSERT INTO stg_emp SELECT
Dept,'DEPT_10' FROM dept_emp#dblink WHERE
dept_id in (select dept_ids from dept );
For one, it would make the code actually readable ;)

Oracle Execute Immediate with DDL and Nested table

I have a problem trying to use an Execute Immediate statement containing a CREATE TABLE statement and a user defined Table Type. I get error ORA-22905 on Oracle 11g.
Is there any workaround to solve this issue?
CREATE TYPE MY_TABLE_TYPE AS TABLE OF VARCHAR2(30);
/
DECLARE
MT MY_TABLE_TYPE;
BEGIN
SELECT * BULK COLLECT INTO MT FROM DUAL;
-- Two steps
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE1 (A VARCHAR2(30))';
EXECUTE IMMEDIATE 'INSERT INTO MY_TABLE1 SELECT * FROM TABLE(:T)' USING MT; -- OK
-- One step
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE2 AS SELECT * FROM TABLE(:T)' USING MT; -- ERROR ORA-22905
END;
The real code for the SELECT * FROM TABLE(:T) is dynamic (main table name is temporary) and slow. That's why I try to avoid creating the table in two steps (as done with MY_TABLE1). Also with two steps I can't use SELECT * but I have to specify all the columns (variable amount and over 100 columns).
There is likely a way to completely avoid this issue. Skip the bulk collect and use a simple CREATE TABLE MY_TABLE AS SELECT * FROM DUAL; That may be an over-simplification of the real logic to gather the data. But there is almost always a way to bypass a bulk collect and store the data directly in an object with just SQL.
If a PL/SQL solution is truly needed, the error ORA-22905: cannot access rows from a non-nested table item can be avoided by creating an object type and creating the table based on that type. This may not solve the performance issue, but at least this avoids the need to re-specify all the columns in the table DDL.
CREATE TYPE MY_TABLE_OBJECT IS OBJECT
(
A VARCHAR2(30)
);
CREATE TYPE MY_TABLE_TYPE2 AS TABLE OF VARCHAR2(30);
DECLARE
MT MY_TABLE_TYPE2;
BEGIN
SELECT * BULK COLLECT INTO MT FROM DUAL;
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE2 OF MY_TABLE_OBJECT';
EXECUTE IMMEDIATE 'INSERT INTO MY_TABLE2 SELECT * FROM TABLE(:T)' USING MT;
END;
/

Resources