PL/SQL: Creation and usage of a temporary table in script - sql

On an Oracle 19c DB I need to sample on customers whilst keeping a uniorm distribution to model the moving of customers.
There are (on purpose) multiple rows with the same customers but changeing adderssses to model moving. Thus, for the sampling i need to group first.
Now what I got is that for the SAMPLE clause the source has to be materialized. So in the PL/SQL script I generate a temporary table that i want to use afterwards with SAMPLE.
But even a simple SELECT INTO afterwards doens't work.
set SERVEROUT on;
DECLARE
v_cust_name VARCHAR2(100);
cmd_creation VARCHAR2(500):='CREATE PRIVATE TEMPORARY TABLE ORA$PTT_temp_cust AS(
SELECT cust_id, MIN(name) as name
FROM customers
GROUP BY cust_id)';
BEGIN
EXECUTE IMMEDIATE cmd_creation;
dbms_output.put_line('temp created');
SELECT name
INTO v_cust_name
FROM (SELECT *
FROM ORA$PTT_temp_cust SAMPLE(5))
WHERE ROWNUM =1;
EXECUTE IMMEDIATE 'DROP TABLE ORA$PTT_temp_cust';
dbms_output.put_line('temp dropped');
END;
What I get is this
ORA-06550: line 15, column 18:
PL/SQL: ORA-00942: table or view does not exist
The table gets created. That far I got when I only executed the String for the creation and nothing else. Then I can access the table in a desired script from a different point.
Does this have to do with the compiling? Is there some different way to solve this?

Your PL/SQL Block simple does not compile as the table you query does not exists at the time of compilation.
You must perform event the query with execute immediate
Simplified Example
DECLARE
v_cust_name VARCHAR2(100);
cmd_creation VARCHAR2(500):='CREATE PRIVATE TEMPORARY TABLE ORA$PTT_temp_cust AS select * from dual';
BEGIN
EXECUTE IMMEDIATE cmd_creation;
dbms_output.put_line('temp created');
EXECUTE IMMEDIATE 'SELECT DUMMY FROM ORA$PTT_temp_cust' INTO v_cust_name;
dbms_output.put_line('name' || v_cust_name);
EXECUTE IMMEDIATE 'DROP TABLE ORA$PTT_temp_cust';
dbms_output.put_line('temp dropped');
END;
/
An other caveat the can lead to ORA-00942: table or view does not existin your setup is that you performs commit in your script.
The default definition of the ON COMMIT clause is DROP DEFINITION so you must use
CREATE PRIVATE TEMPORARY TABLE ORA$PTT_temp_cust
ON COMMIT PRESERVE DEFINITION
...

Dynamic SQL is evil. The fact that you created the table using dynamic SQL (your 1st execute immediate) doesn't mean that Oracle "predicts" you'll actually succeed with that and "presume" that statements that follow are correct. Nope - that table doesn't exist yet, so everything has to be moved to dynamic SQL.
Something like this (disregard changes in table and column names I used and global vs. private temporary table; this is 11gXE):
SQL> DECLARE
2 v_cust_name VARCHAR2 (100);
3 cmd_creation VARCHAR2 (500)
4 := 'CREATE global TEMPORARY TABLE PTT_temp_cust AS
5 SELECT empno, MIN(ename) as name
6 FROM emp
7 GROUP BY empno';
8 BEGIN
9 EXECUTE IMMEDIATE cmd_creation;
10
11 EXECUTE IMMEDIATE '
12 SELECT max(name)
13 FROM (SELECT *
14 FROM PTT_temp_cust SAMPLE(5))
15 WHERE ROWNUM = 1'
16 INTO v_cust_name;
17
18 EXECUTE IMMEDIATE 'DROP TABLE PTT_temp_cust';
19
20 DBMS_OUTPUT.put_line ('Result = ' || v_cust_name);
21 END;
22 /
Result =
PL/SQL procedure successfully completed.
SQL>
I got no result, though - but you should (at least, I hope so).

Related

How to Move a Stored Procedure in Oracle SQL from one Schema to Another?

I am trying to understand what steps I need to undertake in order to move a stored procedure from one schema and into another. The schema that this is currently sitting in is going to be made redundant and I have been asked to move all tables and procedures. I have no trouble with tables but never done anything with procedures hence want to make sure I don't miss anything out.
What I have currently done is look through the procedure and made a list of what its actually doing i.e. dropping/creating and inserting data into tables.
After this I wasn't sure if it was just a case of copying the procedure code and then creating a new procedure on the new schema with the same code and then compiling it.
I would really appreciate it if somebody could advise if I am missing anything in the steps that I am undertaking just to ensure I don't mess things up.
There is no way to "move" an object from one schema to another.
The only practible way I see here is copying the source code and then executing it in the new schema. As #pmdba wrote as comment, you should watch out for schema names like "MYSCHEMA"."TABLENAME" and other references.
If you got too much to copy you may consider writing a block where you automatically read the data of the old schema and create it automatically in the new one.
You can get the data of (nearly) everything with, i.e. procedures:
select * from all_source where owner = 'OLDSCHEMANAME' and type = 'PROCEDURE';
and use it like this:
begin
....
select listagg(text, '') within group (order by line) into proc_code
from all_source
where owner = 'OLDSCHEMANAME'
and type = 'PROCEDURE'
group by name;
execute immediate 'create or replace ' || proc_code; -- perhaps you need to remove the last ';' here
...
end;
Please note that this code is only meant as hint and doesn't need to be taken exactly that way. Also, you may still get errors due to non existing objects, wrong schema references etc..
To get the ddl of a table one may use select dbms_metadata.get_ddl('TABLE','Table_name','Schema_Name') from dual;.
By googling dbms_metadata.get_ddl you might get more info on the DBMS_METADATA-package and how to use it correctly.
As already stated, there is no mechanism to copy one object ( procedure , function or package, etc ) to another schema. One alternative is using all_source, but I prefer DBMS_METADATA because allows you to transfer all dependencies, like for example privileges. Imagine I need to copy a procedure but I need to keep the privileges, with this package I can get everything.
Example
SQL> create procedure myschema1.my_procedure ( p1 number )
2 as
3 var1 number := p1;
4 begin
5 select 1 into var1 from dual;
6 end;
7 /
Procedure created.
SQL> grant execute on myschema1.my_procedure to myuser ;
Grant succeeded.
Now, let's imagine we want to copy the procedure and its privileges to another schema
SQL> set long 99999999 set lines 200 pages 400
SQL> select dbms_metadata.get_ddl('PROCEDURE','MY_PROCEDURE','MYSCHEMA1') from dual ;
DBMS_METADATA.GET_DDL('PROCEDURE','MY_PROCEDURE','MYSCHEMA1')
--------------------------------------------------------------------------------
CREATE OR REPLACE EDITIONABLE PROCEDURE "MYSCHEMA1"."MY_PROCEDURE" ( p1 number )
as
var1 number := p1;
begin
select 1 into var1 from dual;
end;
But, imagine you don't want quotation and neither the editionable argument
SQL> select
replace(dbms_metadata.get_ddl('PROCEDURE','MY_PROCEDURE','MYSCHEMA1','11.2.0'),'"','') as ddl from dual ;
DDL
--------------------------------------------------------------------------------
CREATE OR REPLACE PROCEDURE MYSCHEMA1.MY_PROCEDURE ( p1 number )
as
var1 number := p1;
begin
select 1 into var1 from dual;
end;
Then to get the final command with the new schema owner, we use regexp_replace to replace the first occurrence
SQL> select regexp_replace(replace(dbms_metadata.get_ddl('PROCEDURE','MY_PROCEDURE','MYSCHEMA1','11.2.0'),'"',''),'MYSCHEMA1','MYSCHEMA2',1,1)
2 as ddl from dual ;
DDL
--------------------------------------------------------------------------------
CREATE OR REPLACE PROCEDURE MYSCHEMA2.MY_PROCEDURE ( p1 number )
as
var1 number := p1;
begin
select 1 into var1 from dual;
end;
Finally, we can get all privileges by
SQL> select dbms_metadata.get_dependent_ddl( 'OBJECT_GRANT' , 'MY_PROCEDURE' , 'MYSCHEMA1' ) from dual ;
DBMS_METADATA.GET_DEPENDENT_DDL('OBJECT_GRANT','MY_PROCEDURE','MYSCHEMA1')
--------------------------------------------------------------------------------
GRANT EXECUTE ON "MYSCHEMA1"."MY_PROCEDURE" TO "MYUSER"
Remember to apply at session level before to start some settings to enhance dbms_metadata output:
begin
DBMS_METADATA.set_transform_param (DBMS_METADATA.session_transform, 'SQLTERMINATOR', true);
DBMS_METADATA.set_transform_param (DBMS_METADATA.session_transform, 'PRETTY', true);
end;

Wrong number or types of arguments

Create or replace procedure sp_create_tables as
Lv_str varchar2(1000);
Begin
For I in (select distinct(deptno) from emp) loop
Lv_str:='create table deptno'||I||' select * from emp where
1=2';
Execute immediate lv_str;
End loop;
End;
From what I can understand, your question is probably "why the procedure throws this compilation error"
PLS-00306: wrong number or types of arguments in call to '||'
The reason is that the implicit cursor loop variable I refers to the set of records from the query and not the deptno itself. In order to refer to the deptno, you should use <loop_variable>.deptno. Also 2 other things you should change: The as keyword is missing and DISTINCT is a keyword and you are using it as a function, which works because of default parentheses, but is not the right way to use it.
Create or replace procedure sp_create_tables as
Lv_str varchar2(1000);
Begin
For I in (select distinct dept from emp) loop
Lv_str:='create table deptno'||I.deptno||' as select * from emp where
1=2';
Execute immediate lv_str;
End loop;
End;
In this line of code I is an implicit rowtype variable, with a data structure defined by the projection of the driving select statement:
For I in (select distinct(deptno) from emp) loop
But you are attempting to reference it in your dynamic SQL as though it were an attribute. You need to use a column name instead.
Create or replace procedure sp_create_tables as
Lv_str varchar2(1000);
Begin
For I in (select distinct (deptno) from emp) loop
Lv_str:='create table deptno'|| I.deptno ||
' as select * from emp where 1=2';
Execute immediate lv_str;
End loop;
End;
Incidentally there is a bug in your dynamic SQL statement. The correct syntax is CREATE TABLE ... AS SELECT .... Dynamic SQL is hard because the compiler can't validate the bits of code in strings. Consequently what should be compilation errors manifest themselves as runtime errors. You will find it helpful to instrument your code with some logging (or dbms_output.put_line()) to record the assembled statement before it runs. It makes debugging a lot easier.
" i have got a error saying -01031 insufficient priviliges"
So what this means is your authorisation to create a table was granted through a role. The Oracle security model does not allow us to build PL/SQL programs - or views - using privileges granted through a role. This includes PL/SQL executing DDL through dynamic SQL. You need a DBA user to grant CREATE TABLE to your user directly.

Select from table that does not exist

I have a question regarding ORACLE, I wrote a PLSQL CODE that checks if a table exists, if it exists then I select something from this table..pseudocode is like:
if (table exists)
Select from table where....
the problem is that I always get an error if the table does not exist, even if the if condition is never met and the select statement is never executed.
I think it is because my code is checked at compile time: "select from.." and then it prints an error if the table does not exist. How can I solve such an issue?.. here is how my code looks like (I used generic names):
DECLARE
v_table_exists NUMBER;
BEGIN
SELECT NVL(MAX(1), 0)
INTO v_table_exists
FROM ALL_TABLES
WHERE TABLE_NAME = 'TABLE_TEST';
IF v_table_exists = 1 THEN
INSERT INTO MY_TABLE(COLUMN1, COLUMN2, COLUMN3, COLUMN4)
SELECT 1234,
5678,
T.COLUMN_TEST1,
T.COLUMN_TEST2
FROM TABLE_TEST T
WHERE T.FLAG = 1;
END IF;
END;
The issue is exactly in the fact that your procedure con not be compiled as it refers to a non existing object; you may need some dynamic SQL for this; for example:
create or replace procedure checkTable is
vCheckExists number;
vNum number;
begin
-- check if the table exists
select count(1)
into vCheckExists
from user_tables
where table_name = 'NON_EXISTING_TABLE';
--
if vCheckExists = 1 then
-- query the table with dynamic SQL
execute immediate 'select count(1) from NON_EXISTING_TABLE'
into vNum;
else
vNum := -1;
end if;
dbms_output.put_line(vNum);
end;
The procedure compiles even if the table does not exist; if you call it now, you get:
SQL> select count(1) from NON_EXISTING_TABLE;
select count(1) from NON_EXISTING_TABLE
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> exec checkTable;
-1
PL/SQL procedure successfully completed.
Then, if you create the table and call the procedure again:
SQL> create table NON_EXISTING_TABLE(a) as select 1 from dual;
Table created.
SQL> exec checkTable;
1
PL/SQL procedure successfully completed.
The same way I showed a SELECT, you can do an UPDATE or whatever SQL query you need; if you do something different from a SELECT, the INTO clause has to be removed.
For example, say you need to insert into a different table, the above code should be edited this way:
if vCheckExists = 1 then
execute immediate 'insert into target(a, b, c) select a, 1, 100 from NON_EXISTING_TABLE';
end if;
Everything will need to be done in Dynamic SQL (DBMS_SQL) or EXECUTE_IMMEDIATE otherwise your code will never compile (or package will be invalided) if table does not exists.
DBMS_SQL Example
EXECUTE_IMMEDIATE Example
According to this article, in Oracle Database Server static SQL is indeed checked at compile time to ensure referenced objects exist.
So I advise you to use dynamic SQL instead of static SQL, through a varchar for example.

Create Populate, Then Delete a Table from Within a View (Oracle)

I have a view that compares the data in two tables (tableYesterday and tableToday) and outputs a result based on the comparison. Lets call this view: ViewComp.
I have a need to do more than this. WIthin a single view, I actually need to:
create or replace tableToday and pre-populate it.
execute the ViewComp query.
replace tableYesterday with tableToday.
I have researched things like nested views but I could not find a way to perform 1 and 3 from within the view. I would be grateful for any ideas. Thank you.
This is almost certainly a bad idea. Views shouldn't "do" anything.
For those extremely rare cases where this is required, it can be done with the tricks below. You will definitely want to document this code, to explain to other people what you're doing and why.
Sample schema and objects
--Create table.
create table tableYesterday(a number, b number);
--Create abstract data type to hold one row of data to be returned by the view.
create or replace type ViewComp_type is object
(
a number,
b number
);
--Create nested table to hold multiple rows of data to be returned by the view.
create or replace type ViewComp_nt is table of ViewComp_type;
Function to return results
--Create function to return data.
create or replace function ViewComp_function return ViewComp_nt authid current_user as
--This pragma is necessary for a function that will perform DML.
pragma autonomous_transaction;
v_results ViewComp_nt;
v_name_already_exists exception;
pragma exception_init(v_name_already_exists, -955);
begin
--Try to create today's table. Ignore errors if it exists.
begin
execute immediate 'create table tableToday(a number, b number)';
exception when v_name_already_exists then
execute immediate 'truncate table tableToday';
end;
--Populate today's table.
execute immediate 'insert into tableToday values(1,1)';
--Get the difference.
execute immediate q'[
select cast(collect(ViewComp_type(a,b)) as ViewComp_nt)
from
(
select * from tableToday
minus
select * from tableYesterday
)
]' into v_results;
--Remove yesterday's data.
execute immediate 'truncate table tableYesterday';
--Replace it with today's data.
execute immediate 'insert into tableYesterday select * from tableToday';
commit;
--Return the difference.
return v_results;
end;
/
Create the view to return the function's data
create or replace view ViewComp as
select * from table(ViewComp_function);
Test Run
--First execution:
select * from ViewComp;
A B
- -
1 1
--Second execution:
select * from ViewComp;
A B
- -
You can't do such things in view. You can either create PL/SQL procedure to perform your steps or create materialised view.

Create table and call it from sql

I have a PL/SQL function which creates a new temporary table. For creating the table I use execute immediate. When I run my function in oracle sql developer everything is ok; the function creates the temp table without errors. But when U use SQL:
Select function_name from table_name
I get an exceptions:
ORA-14552: cannot perform a DDL, commit or rollback inside a query or DML
ORA-06512: at "SYSTEM.GET_USERS", line 10
14552. 00000 - "cannot perform a DDL, commit or rollback inside a query or DML "
*Cause: DDL operations like creation tables, views etc. and transaction
control statements such as commit/rollback cannot be performed
inside a query or a DML statement.
Update
Sorry, write from tablet PC and have problems with format text. My function:
CREATE OR REPLACE FUNCTION GET_USERS
(
USERID IN VARCHAR2
)
RETURN VARCHAR2
AS
request VARCHAR2(520) := 'CREATE GLOBAL TEMPORARY TABLE ';
BEGIN
request := request || 'temp_table_' || userid ||
'(user_name varchar2(50), user_id varchar2(20), is_administrator varchar2(5)') ||
' ON COMMIT PRESERVE ROWS';
EXECUTE IMMEDIATE (request);
RETURN 'true';
END GET_USERS;
The error is explicit:
ORA-14552: cannot perform a DDL, commit or rollback inside a query or DML
In Oracle, you can't commit inside a query. A likely explanation is that it would make no sense since a query in Oracle is atomic (either succeeds entirely or makes no change) and this couldn't work if you commit in the middle of a DML. For a select query, all rows must be returned from a single logical point-in-time and if you commit in the middle of a select you would have inconsistent results.
Since DDL in Oracle issue an implicit commit, you can't make DDL inside a query.
This should not be a problem in your case though: SQL server-like temporary tables are not equivalent to the GLOBALLY temporary table in Oracle. There is a reason why temp tables in Oracle are always prefixed with GLOBALLY: they are visible to all sessions although the data in the temporary table is private to each session.
In Oracle creating a temporary table is a relatively expensive operation and you should not create individual temporary tables: all sessions should that do the same job should use the same common structure. Instead of creating multiple temporary tables, in Oracle you should create the table once and reuse it in all procedures. If you are going to need it later, why drop it?
In any case, if you decide to do multiple DDL that depend upon a SELECT, you could do it in a PLSQL block instead of a SELECT query:
DECLARE
l VARCHAR2(100);
BEGIN
FOR cc IN (SELECT col FROM tab) LOOP
l := create_temp_table(cc.col);
END LOOP;
END;
I tested below solution on Oracle 10g XE, it works for me.
Create function:
CREATE OR REPLACE FUNCTION GET_USERS
(
USERID IN VARCHAR2
)
RETURN VARCHAR2
AS
request VARCHAR2(255) := 'CREATE GLOBAL TEMPORARY TABLE ';
BEGIN
request := request || 'temp_table_' || userid ||
'(user_name varchar2(50), user_id varchar2(20), is_administrator varchar2(5))' ||
' ON COMMIT PRESERVE ROWS';
EXECUTE IMMEDIATE request;
RETURN 'true';
END GET_USERS;
Run function:
SET SERVEROUTPUT ON
DECLARE
RESULT VARCHAR(255);
BEGIN
RESULT:=gET_USERS('ADMIN3');
dbms_output.put_line(result);
END;
and select from temporary table:
SELECT * FROM temp_table_admin3;