Oracle Execute Immediate with DDL and Nested table - oracle

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;
/

Related

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.

How can we create dynamic type in Oracle?

I have a master table which contains information of lookup tables, base tables, master tables, primary columns, base columns. Using that information I am creating dynamic SQL statements and execute those via execute immediate.
As there are many records in the transaction, I am using bulk collect. To collect records in bulk, I want to create dynamic types in Oracle at run-time based on the columns available in master.
Below is code structure:
create or replace procedure pro_name(code varchar2,retmsg out varchar2)as
rec_master scr_master_data%rowtype;
type fstcurtype is ref cursor;
cur_fname fstcurtype;
v_sql varchar2(5000);
v_ran_sql varchar2(5000);
begin
SELECT *
INTO rec_master
FROM SCR_MASTER_DATA
WHERE SCREMBLING_CODE = P_SCREMBLING_CODE
AND ACTIVE ='Y';
v_sql := 'select distinct '||rec_master.primary _columns||','||rec_master.base_columns||' from '||rec_master.base_tables;
v_ran_sql:= 'select '||rec_master.LOOKUP_COLUMNS||' from (select /*+ parallel('||rec_master.LOOKUP_TABLES||',16) */ '||rec_master.LOOKUP_COLUMNS
||' from '||rec_master.LOOKUP_TABLES ||' order by dbms_random.value ) where rownum = 1';
OPEN CUR_FNAME for v_sql;
LOOP
FETCH CUR_FNAME BULK COLLECT INTO rec_fname LIMIT NVL(rec_master.ROWS_TO_COMMIT,10000) ;
.
.
.
.
exception
when others then
.
.
end;
i want to create a type based on variable v_sql. Whatever columns mentioned in that statement should be used for the type.
I tried to add below code after v_ran_sql as well but it was a waste of effort.
execute immediate 'type ty_name is record(row_id varchar2(500),fst_name varchar2(500),acc_number varchar2(500))';
execute immediate 'rec_fname ty_name';
in which row_id, fst_name, acc_number column name will come from the master table.
Is there any way around this?

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

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;

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----------------------------------------------

How to return a table without knowing its structure in advance?

everyone I need to create a function in oracle that accepts a table name and return a collection the content of which is based the table that accepted.
I've been doing some search, many examples are of the form: first, define a table type; then, fill a table of that type and return it.
But I won't know the structure of the collection that need to be returned until the function is called, so I can not define a table type at the time of programming.
How can I make it? Thanks:-)
What I want to do is this:
Say, I have three tables--TABLE_A, TABLE_B, TABLE_C--each of which has different columns. Now I need to create a function func(table_name) that accept the table name(TABLE_A, TABLE_B, or TABLE_C) and return a collection the content of which is determined by the table name passed to the function. As the three tables have different columns, I can't create a type like "TColumnData " which you created. So, how should write the function?
One approach is to use global temporary tables.
create or replace
procedure test_procedure(table_name varchar2) as
begin
declare
table_or_view_not_exist exception;
pragma exception_init(table_or_view_not_exist, -942);
begin
begin
EXECUTE IMMEDIATE 'truncate TABLE TEMP_TABLE';
EXECUTE IMMEDIATE 'DROP TABLE TEMP_TABLE';
exception when table_or_view_not_exist then
DBMS_OUTPUT.PUT_LINE('Table TEMP_TABLE not found. skipping drop..');
end;
EXECUTE IMMEDIATE
'CREATE GLOBAL TEMPORARY TABLE TEMP_TABLE ON COMMIT PRESERVE ROWS AS
SELECT * FROM ' || table_name || ' ';
end;
end test_procedure;
then run the procedure:
execute test_procedure('TABLE_A');
once the procedure has completed execution, you can verify by:
select * from temp_table;

Resources