Why EXECUTE IMMEDIATE is needed here? - oracle

I am a SQL Server user and I have a small project to do using Oracle, so I’m trying to understand some of the particularities of Oracle and I reckon that I need some help to better understand the following situation:
I want to test if a temporary table exists before creating it so I had this code here:
DECLARE
table_count INTEGER;
var_sql VARCHAR2(1000) := 'create GLOBAL TEMPORARY table TEST (
hello varchar(1000) NOT NULL)';
BEGIN
SELECT COUNT(*) INTO table_count FROM all_tables WHERE table_name = 'TEST';
IF table_count = 0 THEN
EXECUTE IMMEDIATE var_sql;
END IF;
END;
It works normally, so after I executed it once, I added an else statement on my IF:
ELSE
insert into test (hello) values ('hi');
Executed it again and a line was added to my test table.
Ok, my code was ready and working, so I dropped the temp table and tried to run the entire statement again, however when I do that I get the following error:
ORA-06550: line 11, column 19:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 11, column 7:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Then I changed my else statement to this and now it works again:
ELSE
EXECUTE IMMEDIATE 'insert into test (hello) values (''hi'')';
My question is why running individually I can simply use the insert instead of the EXECUTE IMMEDIATE and also why my SELECT statement right after BEGIN still works when all the rest appears to need EXECUTE IMMEDIATE to run properly?

The whole PL/SQL block is parsed at compile time, but the text within a dynamic statement isn't evaluated until runtime. (They're close to the same thing for an anonymous block, but still distinct steps).
Your if/else isn't evaluated until runtime either. The compiler doesn't know that the table will always exist by the time you do your insert, it can only check whether or not it exists at the point it parses the whole block.
If the table does already exist then it's OK; the compiler can see it, the block executes, your select gets 1, and you go into the else to do the insert. But if it does not exist then the parsing of the insert correctly fails with ORA-00942 at compile time and nothing in the block is executed.
Since the table creation is dynamic, all references to the table have to be dynamic too - your insert as you've seen, but also if you then query it. Basically it makes your code much harder to read and can hide syntax errors - since the dynamic code isn't parsed until run-time, and it's possible you could have a mistake in a dynamic statement in a branch that isn't hit for a long time.
Global temporary tables should not be created on-the-fly anyway. They are permanent objects with temporary data, specific to each session, and should not be created/dropped as part of your application code. (No schema changes should be made by your application generally; they should be confined to upgrade/maintenance changes and be controlled, to avoid errors, data loss and unexpected side effects; GTTs are no different).
Unlike temporary tables in some other relational databases, when you create a temporary table in an Oracle database, you create a static table definition. The temporary table is a persistent object described in the data dictionary, but appears empty until your session inserts data into the table. You create a temporary table for the database itself, not for every PL/SQL stored procedure.
Create the GTT once and make all your PL/SQL code static. If you want something closer to SQL Server's local temporary tables then look into PL/SQL collections.

PL/SQL: ORA-00942: table or view does not exist
It is compile time error, i.e. when the static SQL is parsed before even the GTT is created.
Let's see the difference between compile time and run time error:
Static SQL:
SQL> DECLARE
2 v number;
3 BEGIN
4 select empno into v from a;
5 end;
6 /
select empno into v from a;
*
ERROR at line 4:
ORA-06550: line 4, column 26:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 4, column 1:
PL/SQL: SQL Statement ignored
Dynamic SQL:
SQL> DECLARE
2 v number;
3 BEGIN
4 execute immediate 'select empno from a' into v;
5 end;
6 /
DECLARE
*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at line 4
In the 1st PL/SQL block, there was a semantic check at compile time, and you could see the PL/SQL: ORA-00942: table or view does not exist. In the 2nd PL/SQL block, you do not see the PL/SQL error.
Bottomline,
At compile time it is not known if the table exists, as it is
only created at run time.
In your case, to avoid this behaviour, you need to make the INSERT also dynamic and use EXECUTE IMMEDIATE. In that way, you can escape the compile time error and get the table created dynamically and also do an insert into it dynamically at run time.
Having said that, the basic problem is that you are trying to create GTT on the fly which is not a good idea. You should create it once, and use it the way you want.

I have modified your code a litle bit and it works as far as logic is concerned. But as exp[lained in earlier posts creating GTT on the fly at run time is not at all is a goood idea.
--- Firstly by dropping the table i.e NO TABLE EXISTS in the DB in AVROY
SET serveroutput ON;
DECLARE
table_count INTEGER;
var_sql VARCHAR2(1000) := 'create GLOBAL TEMPORARY table TEST (
hello varchar(1000) NOT NULL)';
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE AVROY.TEST'; --Added the line just to drop the table as per your comments
SELECT COUNT(*)
INTO table_count
FROM all_tables
WHERE table_name = 'TEST'
AND OWNER = 'AVROY';
IF table_count = 0 THEN
EXECUTE IMMEDIATE var_sql;
dbms_output.put_line('table created');
ELSE
INSERT INTO AVROY.test
(hello
) VALUES
('hi'
);
END IF;
END;
--------------------OUTPUT-----------------------------------------------
anonymous block completed
table created
SELECT COUNT(*)
-- INTO table_count
FROM all_tables
WHERE table_name = 'TEST'
AND OWNER = 'AVROY';
COUNT(*)
------
1
--------
-- Second option is without DROPPING TABLE
SET serveroutput ON;
DECLARE
table_count INTEGER;
var_sql VARCHAR2(1000) := 'create GLOBAL TEMPORARY table TEST (
hello varchar(1000) NOT NULL)';
BEGIN
--EXECUTE IMMEDIATE 'DROP TABLE AVROY.TEST';
SELECT COUNT(*)
INTO table_count
FROM all_tables
WHERE table_name = 'TEST'
AND OWNER = 'AVROY';
IF table_count = 0 THEN
EXECUTE IMMEDIATE var_sql;
dbms_output.put_line('table created');
ELSE
INSERT INTO AVROY.test
(hello
) VALUES
('hi'
);
dbms_output.put_line(SQL%ROWCOUNT||' Rows inserted into the table');
END IF;
END;
-------------------------------OUTPUT-------------------------------------
anonymous block completed
1 Rows inserted into the table
---------------------------------------------------------------------------

Related

Create Table in a Loop inside Stored Procedure Oracle SQL

I am attempting to create a Oracle stored procedure which creates partitioned tables based off of a table containing the table names and the column to be partitioned with. A separate PL/SQL block iterates through the table and calls the procedure with the table name and the column name.
Procedure:
create or replace PROCEDURE exec_multiple_table_create (
table_name IN VARCHAR2,
column_name IN VARCHAR2
) IS
stmt VARCHAR2(5000);
tablename VARCHAR2(50);
columnname VARCHAR2(50);
BEGIN
tablename := table_name;
columnname := column_name;
-- DBMS_OUTPUT.PUT_LINE(tablename);
-- DBMS_OUTPUT.PUT_LINE(columnname);
stmt := 'create table '
|| TABLENAME
|| '_temp as (select * from '
|| COLUMNNAME
|| ' where 1=2)';
EXECUTE IMMEDIATE stmt
USING IN table_name, column_name;
stmt := 'alter table '
|| tablename
|| '_temp modify partition by range('
|| columnname
|| ')
(PARTITION observations_past VALUES LESS THAN (TO_DATE(''20000101'',''YYYYMMDD'')),
PARTITION observations_CY_2000 VALUES LESS THAN (TO_DATE(''20010101'',''YYYYMMDD'')),
PARTITION observations_CY_2001 VALUES LESS THAN (TO_DATE(''20020101'',''YYYYMMDD'')),
PARTITION observations_CY_2002 VALUES LESS THAN (TO_DATE(''20030101'',''YYYYMMDD'')),
PARTITION observations_CY_2003 VALUES LESS THAN (TO_DATE(''20040101'',''YYYYMMDD'')),
PARTITION observations_CY_2004 VALUES LESS THAN (TO_DATE(''20050101'',''YYYYMMDD'')),
PARTITION observations_CY_2005 VALUES LESS THAN (TO_DATE(''20060101'',''YYYYMMDD'')),
PARTITION observations_CY_2006 VALUES LESS THAN (TO_DATE(''20070101'',''YYYYMMDD'')),
PARTITION observations_CY_2007 VALUES LESS THAN (TO_DATE(''20080101'',''YYYYMMDD'')),
PARTITION observations_CY_2008 VALUES LESS THAN (TO_DATE(''20090101'',''YYYYMMDD'')),
PARTITION observations_CY_2009 VALUES LESS THAN (TO_DATE(''20100101'',''YYYYMMDD'')),
PARTITION observations_CY_2010 VALUES LESS THAN (TO_DATE(''20110101'',''YYYYMMDD'')),
PARTITION observations_FUTURE VALUES LESS THAN ( MAXVALUE ) )';
EXECUTE IMMEDIATE stmt
USING IN table_name, column_name;
RETURN;
END exec_multiple_table_create;
The PL/SQL block which is using the stored proc is:
BEGIN
FOR partition_item IN (
SELECT
table_name,
partition_column
FROM
partition_table
) LOOP
exec_multiple_table_create(partition_item.table_name, partition_item.partition_column);
END LOOP;
END;
Now, when I try executing the thing, this is what I am seeing:
Error report -
ORA-06550: line 9, column 9:
PLS-00905: object SCG_MYACCT_CUSTOMPC.EXEC_MULTIPLE_TABLE_CREATE is invalid
ORA-06550: line 9, column 9:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I have a feeling that I am missing something. Please let me know what it is. The table containing the reference data exists and contains data.
I have tried refreshing the table, rewriting and modifying the pl/sql block & the procedure code. Nothing seems to be working.
Thanks in advance.
UPDATE 1:
There was a glitch in the stored procedure where I needed to refer to the tablename rather than the columnname in the code above. However, I am getting a different error right now.
Error report -
ORA-06546: DDL statement is executed in an illegal context
ORA-06512: at "SCG_MYACCT_CUSTOMPC.EXEC_MULTIPLE_TABLE_CREATE", line 18
ORA-06512: at line 9
ORA-06512: at line 9
06546. 00000 - "DDL statement is executed in an illegal context"
*Cause: DDL statement is executed dynamically in illegal PL/SQL context.
- Dynamic OPEN cursor for a DDL in PL/SQL
- Bind variable's used in USING clause to EXECUTE IMMEDIATE a DDL
- Define variable's used in INTO clause to EXECUTE IMMEDIATE a DDL
*Action: Use EXECUTE IMMEDIATE without USING and INTO clauses to execute
the DDL statement.
Please help me out with this as well.
UPDATE 2:
I removed the USING part of the EXECUTE IMMEDIATE statement. That seemed to take care of the error I posted. Getting a different error with versions now:
Error starting at line : 1 in command -
BEGIN
FOR partition_item IN (
SELECT
table_name,
partition_column
FROM
partition_table
) LOOP
exec_multiple_table_create(partition_item.table_name, partition_item.partition_column);
END LOOP;
END;
Error report -
ORA-00406: COMPATIBLE parameter needs to be 12.2.0.0.0 or greater
ORA-00722: Feature "Conversion into partitioned table"
ORA-06512: at "SCG_MYACCT_CUSTOMPC.EXEC_MULTIPLE_TABLE_CREATE", line 37
ORA-06512: at line 9
ORA-06512: at line 9
00406. 00000 - "COMPATIBLE parameter needs to be %s or greater"
*Cause: The COMPATIBLE initialization parameter is not high
enough to allow the operation. Allowing the command would make
the database incompatible with the release specified by the
current COMPATIBLE parameter.
*Action: Shutdown and startup with a higher compatibility setting.

Trying to insert into a table while looping through pl SQL

I have a view that I need to loop through with all the inventory ids from another table. I keep receiving the following error: Error report -
ORA-06550: line 31, column 31:
PLS-00357: Table,View Or Sequence reference 'MISSINGINVENTORY' not allowed in this context
ORA-06550: line 31, column 2:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
This is my current code I am not sure what the problem would be? I ma fairly new to pl sql
DECLARE
CURSOR inventory_ids_c IS
SELECT DISTINCT fzrfbth_status status,
fzbfbth_inventory_id id
FROM fzbfbth#develop_sw,
fzrfbth#develop_sw
WHERE fzbfbth_inventory_id = fzrfbth_inventory_id
ORDER BY fzrfbth_status,
fzbfbth_inventory_id;
inventory_ids_rec inventory_ids_c%ROWTYPE;
BEGIN
OPEN inventory_ids_c;
FETCH inventory_ids_c INTO inventory_ids_rec;
LOOP
EXIT WHEN inventory_ids_c%NOTFOUND;
fzkfims.P_set_inventory_id#develop_sw(inventory_ids_rec.id);
DECLARE
CURSOR inventory_items IS
SELECT *
FROM fzvfims#develop_sw;
BEGIN
OPEN inventory_items;
LOOP
FETCH inventory_items INTO Missinginventory(last_inventory_date,
atype_title
,
owner, orgn_code, orgn_title, locn_code, room, bldg, sort_room,
ptag
,
manufacturer, model, serial_num, description, custodian, po,
acq_date,
amount,
ownership, schev_year, tag_type, inventory_id, condition,
asset_type
);
EXIT WHEN inventory_items%NOTFOUND;
END LOOP;
CLOSE inventory_items;
END;
END LOOP;
END;
It seems that you are trying to fetch from cursor directly into Missinginventory table (if it is a table). Well, that won't work. First fetch into cursor variables, then insert those variables into a table.
I don't know what this:
fzkfims.P_set_inventory_id#develop_sw(inventory_ids_rec.id);
does; looks like some procedure call. I guess it sets some kind of an "environment", but I don't know how it reflects to code you wrote.
Anyway: why do you use cursor loops? Can't you directly insert data into the table? That would be faster and - hopefully - simpler.
Also, if you declare something, do that at the same place, at the beginning of that PL/SQL procedure. If you scatter declarations all over your code, it is difficult to maintain it. Furthermore, see whether cursor FOR loops are an option as they are simpler to use - Oracle does a lot of things for you. How? Although you still have to write cursor's SELECT statement, you don't have to declare cursor variables, open cursor, fetch, take care about exiting the loop, close the cursor. I'll try to rewrite your code, have a look (as you can see, no declare section at all!).
begin
for cur_1 in (select distinct fzrfbth_status status,
fzbfbth_inventory_id id
from fzbfbth#develop_sw
join fzrfbth#develop_sw
on fzbfbth_inventory_id = fzrfbth_inventory_id
order by fzrfbth_status,
fzbfbth_inventory_id
)
loop
fzkfims.p_set_inventory_id#develop_sw(cur_1.id);
for cur_2 in (select *
from fzvfims#develop_sw
)
loop
-- I shortened it to just a few columns
insert into missinginventory
(last_inventory_date,
atype_title,
asset_type)
values (cur_2.last_inventory_date,
cur_2.atype_title,
cur_2.asset_type);
end loop;
end loop;
end;

Oracle WITH AS SELECT with GRANT

I'm using WITH to create like a temporary table and query upon it. But my SELECT(running on the temp table) has a function call with temp table as the input parameter. How to give function access to temp table created using WITH.
WITH TEMP_TABLE AS
(select * from schema1.main_table where col_datetime > sysdate - 4) -- to reduce the data main query executes upon
(
Select * FROM table(schema2.FUNCTION1(blah, blah, 'TEMP_TABLE', blah, blah))
);
Error below
ORA-00942: table or view does not exist
ORA-06512: at "schema2.FUNCTION1", line 143
ORA-06512: at line 1
00942. 00000 - "table or view does not exist"
*Cause:
*Action:
You are using a Common Table Expression (CTE). A CTE is temporarily created only in the context of the broader query and so the CTE's complete dataset of records are outside of the usable scope of functions. You can use individual values from the CTE and pass to a function but your function cannot see the full CTE. The CTE does not exist in a context accessible by the function.
You may consider posting a separate question describing what you hope to accomplish inside the function and we may be able to give you some pointers on an alternate method that fits the SQL specifications.
The PL/SQL WITH function can directly answer your question. You can't pass the WITH table, but you can use a WITH function to create a table, and then reference that table in your existing function.
This code requires Oracle 12.1. And please be aware of the potential for SQL injection with the names.
with
function create_temp_table return varchar2 is
pragma autonomous_transaction;
begin
execute immediate 'drop table temp_table';
execute immediate 'create table temp_table as select 2 a, 3 b from dual';
return 'temp_table';
end;
select * from table(function1(create_temp_table()))
/
Here's the sample schema to make the above SQL work:
create or replace type function1_rec is object(a number, b number);
create or replace type function1_nt is table of function1_rec;
create or replace function function1(p_table_name varchar2) return function1_nt is
v_results function1_nt;
begin
execute immediate 'select function1_rec(a,b) from '||p_table_name
bulk collect into v_results;
return v_results;
end;
/
Although I agree with Sam M that you might want to look into alternative methods. I have a feeling there is a simpler way to accomplish what you're looking for.

Store Procedure hit SQL Statement Ignored

create or replace PROCEDURE GEN_STATEMENT_SP(indexNM IN VARCHAR2, tableNM IN VARCHAR2) AS
BEGIN
DECLARE
uniqueSTMT VARCHAR2(30);
nonUniqueSTMT VARCHAR2(30);
charOn VARCHAR2(5);
tempfld VARCHAR2(500) ;
CURSOR chkTyp IS(SELECT ES_UNIQUENESS from sys.dba_ind_columns where INDEX_NAME = indexNM and TABLE_NAME = tableNM);
CURSOR tblColumn IS(SELECT INDEX_NAME,listagg(COLUMN_NAME, ',') WITHIN GROUP (ORDER BY COLUMN_POSITION) COLUMN_NAME FROM sys.dba_ind_columns where INDEX_NAME = indexNM and TABLE_NAME = tableNM GROUP BY INDEX_NAME);
BEGIN
uniqueSTMT := 'CREATE UNIQUE INDEX';
nonUniqueSTMT := 'CREATE INDEX';
charOn := 'on';
if chkTyp.ES_UNIQUENESS = 'UNIQUE' then
tempfield := uniqueSTMT || indexNM || charOn || tableNM || '(' || tblColumn.COLUMN_NAME || ')' ;
DBMS_OUTPUT.put_line(tempfld);
end if;
END;
END;
/
Error list:
LINE/COL ERROR
-------- ------------------------------------------------------------------
9/17 PL/SQL: SQL Statement ignored
9/48 PL/SQL: ORA-00942: table or view does not exist
10/20 PL/SQL: SQL Statement ignored
10/126 PL/SQL: ORA-00942: table or view does not exist
18/1 PL/SQL: Statement ignored
18/11 PLS-00225: subprogram or cursor 'CHKTYP' reference is out of scope
Hi all, when i try to create a sample store procedure above. I hit SQL Statement ignored.
Need some help on this, Thanks alot.
You don't have access to DBA_ views (owned by SYS). Do you really need those? Switch to USER_IND_COLUMNS instead. Once you make that code work, expand it further (if necessary).
As of CHKTYP being improperly used: you declared a cursor, but never did anything with it (opened, fetched, ..., closed). On the other hand, why do you use cursors? Those are SELECT statements that return a single value (unless I'm wrong), so they might have been ordinary SELECTs (but yes, you might have to handle possible NO-DATA-FOUND exception).
Furthermore, there's no column ES_UNIQUENESS in USER_IND_COLUMNS view; what is is supposed to do? It exists in USER_INDEXES, but is called UNIQUENESS, without the ES_ prefix.
Shortly, you need to rewrite that code. Take it step by step, test frequently. Once you're sure that the first step was successful, go to the next.

dropping a global temporary table

2 Separate questions.
I am using this script to drop a table [SOLVED]
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE_NAME';
DBMS_OUTPUT.PUT_LINE ('Global table TABLE_NAME Dropped');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Global table TABLE_NAME Doesn''t exist.');
END;
/
Is there anyway I can differentiate if table "doesn't exist" or it is being used in some other sessions (in that case it would locked and couldn't be deleted). I am not sure if I can see that table exists in user_tables. I am not fully aware of permissions.
I have added this code now
WHEN OTHERS THEN
i_code := SQLCODE;
v_errm := SUBSTR(SQLERRM, 1, 64);
if i_code = -942 THEN
DBMS_OUTPUT.PUT_LINE ('TABLE_NAME doesn''t exist. Script will continue to create it');
ELSE
DBMS_OUTPUT.PUT_LINE ('Error dropping temporary table. The error code is ' || i_code || '- ' || v_errm);
END IF ;
2. I see . at the end of each procedure like this
END PROCEDURE_NAME;
.
/
sho err;
I just don't understand why . is here. Is it syntax or what?
-- First Truncate temporary table
SQL> TRUNCATE TABLE test_temp1;
-- Then Drop temporary table
SQL> DROP TABLE test_temp1;
Step 1. Figure out which errors you want to trap:
If the table does not exist:
SQL> drop table x;
drop table x
*
ERROR at line 1:
ORA-00942: table or view does not exist
If the table is in use:
SQL> create global temporary table t (data varchar2(4000));
Table created.
Use the table in another session. (Notice no commit or anything after the insert.)
SQL> insert into t values ('whatever');
1 row created.
Back in the first session, attempt to drop:
SQL> drop table t;
drop table t
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use
So the two errors to trap:
ORA-00942: table or view does not exist
ORA-14452: attempt to
create, alter or drop an index on temporary table already in use
See if the errors are predefined. They aren't. So they need to be defined like so:
create or replace procedure p as
table_or_view_not_exist exception;
pragma exception_init(table_or_view_not_exist, -942);
attempted_ddl_on_in_use_GTT exception;
pragma exception_init(attempted_ddl_on_in_use_GTT, -14452);
begin
execute immediate 'drop table t';
exception
when table_or_view_not_exist then
dbms_output.put_line('Table t did not exist at time of drop. Continuing....');
when attempted_ddl_on_in_use_GTT then
dbms_output.put_line('Help!!!! Someone is keeping from doing my job!');
dbms_output.put_line('Please rescue me');
raise;
end p;
And results, first without t:
SQL> drop table t;
Table dropped.
SQL> exec p;
Table t did not exist at time of drop. Continuing....
PL/SQL procedure successfully completed.
And now, with t in use:
SQL> create global temporary table t (data varchar2(4000));
Table created.
In another session:
SQL> insert into t values (null);
1 row created.
And then in the first session:
SQL> exec p;
Help!!!! Someone is keeping from doing my job!
Please rescue me
BEGIN p; END;
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use
ORA-06512: at "SCHEMA_NAME.P", line 16
ORA-06512: at line 1
yes - the engine will throw different exceptions for different conditions.
you will change this part to catch the exception and do something different
EXCEPTION
WHEN OTHERS THEN
here is a reference
http://download.oracle.com/docs/cd/B10501_01/appdev.920/a96624/07_errs.htm
The DECLARE GLOBAL TEMPORARY TABLE statement defines a temporary table for the current connection.
These tables do not reside in the system catalogs and are not persistent.
Temporary tables exist only during the connection that declared them and cannot be referenced outside of that connection.
When the connection closes, the rows of the table are deleted, and the in-memory description of the temporary table is dropped.
For your reference http://docs.oracle.com/javadb/10.6.2.1/ref/rrefdeclaretemptable.html
Down the apache server by running below in putty
cd $ADMIN_SCRIPTS_HOME
./adstpall.sh
Drop the Global temporary tables
drop table t;
This will workout..

Resources