Create Table in a Loop inside Stored Procedure Oracle SQL - oracle

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.

Related

Oracle error when selecting into temp table

My ultimate goal is far more complex than this, but this is the cut down version of what is causing my error. I want to put some rows into a temporary table (actually several temp tables, but I can't get by this first hurdle). Here is my PL/SQL;
DECLARE
type L1_store is table of MyTable%rowtype;
L1 L1_store;
BEGIN
select
* bulk collect
into L1
from MyTable
WHERE 1=1
and length(MyColumn1) = 2;
select
L1.MyColumn1
,L1.MyColumn2
from L1;
END;
And here is the error I get;
ORA-06550: line 19, column 6:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 16, column 1:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
The line numbers may be incorrect as I have edited the actual PL/SQL for clarity
****EDIT****
OK, so I originally accepted the answer offered below as it looks like it answers my question, and I can see how I could use it. However, for clarity, here is my ultimate goal, in case there is a better answer than the one I have in my head.
If I was just doing this in SQL I would do something like;
with L1 as
(select * from table),
L2 as
(select * from anothertable)
select L1.Column
from L1
left join L2 on L1.somecolumn = L2.somecolumn
I don't know if this helps or hinders, but thanks all in anticipation of your continued patience.
type L1_store is table of MyTable%rowtype; is not a temporary table; it is a collection data type declared in the PL/SQL scope and cannot be used in the SQL scope. Similarly, %ROWTYPE is a PL/SQL construct.
If you want to use a collection in SQL then declare it in the SQL scope:
CREATE TYPE mytable_data is OBJECT (
mycolumn1 VARCHAR2(50),
mycolumn2 NUMBER,
mycolumn3 DATE
);
CREATE TYPE mytable_data_table IS TABLE OF mytable_data;
so for some test data:
CREATE TABLE MyTable(
mycolumn1 VARCHAR2(50),
mycolumn2 NUMBER,
mycolumn3 DATE
);
INSERT INTO MyTable VALUES ( 'AA', 42, SYSDATE );
Then you can do:
DECLARE
L1 mytable_data_table;
BEGIN
select mytable_data( mycolumn1, mycolumn2, mycolumn3 )
bulk collect into L1
from MyTable
WHERE length(MyColumn1) = 2;
FOR i IN 1 .. L1.COUNT LOOP
DBMS_OUTPUT.PUT_LINE( L1(i).mycolumn1 || ' ' || l1(i).mycolumn2 );
END LOOP;
END;
/
Which outputs:
AA 42
db<>fiddle here
The problem here seems to be the second select in the block. If you're trying to select the data from the record, I would be inclined to put it into a FOR loop. I.e, something like this:
for i in 1 .. L1.count() LOOP
dbms_output.put_line (L1(i).MyColumn1);
end loop;
You could assign the value of MYColumn1 to a variable or use it in some other way.

Parameters wrong assignment (PL/SQL, ORACLE)

I'm trying to run below pl/sql block but I'm getting error:
set serveroutput on
declare
rowBefore VARCHAR2(32000);
myschema VARCHAR2(32000) := 'abc';
mytable VARCHAR2(32000) := 'table1';
param1 VARCHAR2(32000) := 'Tom';
begin
select count(*) into rowBefore from myschema.mytable where colA = param1;
--select count(*) into rowBefore from abc.table1 where colA = 'Tom';
DBMS_OUTPUT.PUT_LINE(rowBefore);
End;
How to correctly use my all parameters into select statement?
Update error:
Error report -
ORA-06550: linia 7, kolumna 50:
PL/SQL: ORA-00942: tabela lub perspektywa nie istnieje
ORA-06550: linia 7, kolumna 5:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
You can't use a variable as an identifier in a SQL statement. It is looking for a table that is actually called mytable - not one with the value of the variable with that name as you expect. And the same for the schema.
From the documentation:
If the statement is a SELECT statement, the PL/SQL compiler removes the INTO clause.
The PL/SQL compiler sends the statement to the SQL subsystem.
The SQL subsystem checks the syntax of the statement.
If the syntax is incorrect, the compilation of the PL/SQL unit fails. If the syntax is correct, the SQL subsystem determines the names of the tables and tries to resolve the other names in the scope of the SQL statement.
...
It tries to resolve other names, e.g. functions; but table names (and schema names, and column names) have to be valid to the SQL parser.
You have to use dynamic SQL to create a string that contains the statement, concatenating in the identifiers:
execute immediate 'select count(*) from ' || myschema ||'.'|| mytable
|| ' where colA = :value'
into rowBefore using param1;
If you print out that generated statement with dbms_output you should see it matching the commented-out version, except for the use of a bind variable for the columns value. That would also help highlight any errors - like not including whitespace between the concatenated parts of the statement, which is a common and easy mistake.
It's often a good idea to build the statement as a string variable to make such debugging easier:
declare
...
v_stmt varchar2(4000):
begin
v_stmt := 'select count(*) from ' || myschema ||'.'|| mytable
|| ' where colA = :value';
-- for debugging
dbms_output.put_line(v_stmt);
-- and run it
execute immediate vStmt into rowBefore using param1;
...
If you are really getting the identifiers passed in by a user or client you should validate them to avoid unexpected errors and SQL injection attacks. Also, if the identifiers might be case sensitive (i.e. schemas/tables created with quoted identifiers) then you may need to add double-quotes to the generated statement, and make sure the variables are the correct case.
error is ORA-942: table or view does not exists. run first below query and see if error still occurs.
-- should you use abc.table1, instead of myschema.mytable?
select count(*) rowBefore from myschema.mytable where colA = 'a';
if there is still error, then fix the SELECT statement first before you use it in the PL/SQL block.

Why EXECUTE IMMEDIATE is needed here?

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

Oracle : Stored procedure how to to_char expression in string variable

I am trying to create table dynamically in Oracle stored procedure.
I have created create table sql in the string variable.
Below is the sql snapshot and error.
Procedure is getting compiled without any issue. But when trying to execute the procedure, I am getting runtime error. I am getting the issue because of to_char expression.
CREATE OR REPLACE PROCEDURE TESTPROC12(P_TMP_Table IN VARCHAR2) AUTHID CURRENT_USER IS
V_SQL_STMT1 varchar2(1000);
V_TMP_Table varchar2(100);
BEGIN
V_TMP_Table := concat('TMP', to_char(sysdate,'MMDDYYYYHH24MISSSSS'));
V_SQL_STMT1 := 'CREATE TABLE '|| V_TMP_Table||' AS
SELECT * from TMP_STMTSENT2 where rowid in (select min(rowid) from '|| P_TMP_Table||'
group by CUSTOMER_RELATIONSHIP_ID, to_char(''STATEMENT_PROCESSED_DATE'',''MM/DD/YYYY''))';
EXECUTE IMMEDIATE V_SQL_STMT1;
END;
Procedure - I need to use to_char.
EXEC TESTPROC12('TMP_STMTSENT2')
Error starting at line 20 in command:
EXEC TESTPROC12('TMP_STMTSENT2')
Error report:
ORA-01722: invalid number
ORA-06512: at "EDLVY.TESTPROC12", line 15
ORA-06512: at line 1
01722. 00000 - "invalid number"
*Cause:
*Action:
The generated and executed statement could look like the following:
CREATE TABLE 10222013023309188 AS SELECT * from TMP_STMTSENT2 where rowid in (select min(rowid) from qeoiwqeoiwq group by CUSTOMER_RELATIONSHIP_ID, to_char('STATEMENT_PROCESSED_DATE','MM/DD/YYYY'))
The problem could be with 'STATEMENT_PROCESSED_DATE', which is not a date. Try sysdate instead and see if it works.
Take a look at the docs. You are passing the string 'STATEMENT_PROCESSED_DATE' to to_char(). Maybe you want to pass the column or value STATEMENT_PROCESSED_DATE instead?
A good way to do what you are pusuing is using Oracle Temporary Tables
http://docs.oracle.com/cd/B28359_01/server.111/b28310/tables003.htm#i1006400
http://www.dba-oracle.com/t_temporary_tables_sql.htm
http://www.oracle-base.com/articles/misc/temporary-tables.php

Oracle Bind Variable giving error

SET SERVEROUTPUT ON
VARIABLE dept_id NUMBER
DECLARE
max_deptno NUMBER(3);
dept_name departments.department_name%TYPE :='Education';
BEGIN
SELECT MAX(department_id)
INTO max_deptno
FROM departments;
DBMS_OUTPUT.PUT_LINE ('The maximum department no is : ' || max_deptno);
:dept_id:=(max_deptno+10);
INSERT INTO departments (department_name, department_id,location_id)
VALUES(dept_name, :dept_id, NULL);
DBMS_OUTPUT.PUT_LINE ('The number of rows affected : ' || SQL%ROWCOUNT);
END;
/
Error report:
ORA-01400: cannot insert NULL into ("SYSTEM"."DEPARTMENTS"."DEPARTMENT_ID")
ORA-06512: at line 10
01400. 00000 - "cannot insert NULL into (%s)"
*Cause:
*Action:
The maximum department no is : 190
I am getting this error while trying to execute the bind variable in oracle statment. But if i put some value instead of bind variable, i get this insert statement right. What am I doing wrong here?
I think the value of the bind variable is only set when the pl/sql block is finished. And it probably has to terminate normally.
One solution is to use max_deptno+10 in the insert insead of :dept_if. A better solution is to create another pl/sql variable and use that in the insert statement.
new_dept_id := max_deptno+10;
:dept_id := new_dept_id;
You also have to change the INSERT statement:
INSERT INTO departments (department_name,department_id,location_id)
VALUES(dept_name, new_dept_id, NULL);
I think this error is obtained because you use bind variable without using set autoprint on in start program.

Resources