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

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.

Related

Create view using stored procedure Oracle

I have a list of people and I want to create view for each person.
If I Have 1 person, my view would be:
CREATE VIEW PersonInfo AS (
SELECT * FROM People WHERE id = 1000
);
But in fact, I have thousands of people, I want to create a stored procedure in Oracle to create view for each person, But View names are duplicated when I use it. How can I handle that problem?
Thank you for helping in advance.
Your aim is interesting(presumably a related with training but not a real-world scenario), but a dynamic solution would be handled with a for loop by using EXECUTE IMMEDIATE of PL/SQL such as
SQL> CREATE OR REPLACE PROCEDURE Crt_People_Views AS
BEGIN
FOR p IN ( SELECT id FROM People )
LOOP
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW PersonInfo_'||p.id
||' AS SELECT * FROM People WHERE id = '||p.id;
END LOOP;
END;
/
SQL> EXEC Crt_People_Views; -- call the proc. which will create the desired views
What problem are you trying to solve by creating a view for every person?
Would it make more sense to create a single view that takes a parameter (person_id)?
Something like this?
CREATE OR REPLACE VIEW VIEW_ABC (parm1 INTEGER) AS
SELECT *
FROM XYZ
WHERE ….
Call like this.
Then, all we need do is,
SELECT *
FROM VIEW_ABC (101)
/
No probs. with bind variables. Nicely integrated as one would expect.

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

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

Delete all data from a table after selecting all data from the same table

All i want is to select all rows from a table and once it is selected and displayed, the data residing in table must get completely deleted. The main concern is that this must be done using sql only and not plsql. Is there a way we can do this inside a package and call that package in a select statement? Please enlighten me here.
Dummy Table is as follows:
ID NAME SALARY DEPT
==================================
1 Sam 50000 HR
2 Max 45000 SALES
3 Lex 51000 HR
4 Nate 66000 DEV
Any help would be greatly appreciated.
select * from Table_Name;
Delete from Table_Name
To select the data from a SQL query try using a pipelined function.
The function can define a cursor for the data you want (or all the data in the table), loop through the cursor piping each row as it goes.
When the cursor loop ends, i.e. all data has been consumed by your query, the function can perform a TRUNCATE table.
To select from the function use the following syntax;
SELECT *
FROM TABLE(my_function)
See the following Oracle documentation for information pipelined functions - https://docs.oracle.com/cd/B28359_01/appdev.111/b28425/pipe_paral_tbl.htm
This cannot be done inside a package, because " this must be done using sql only and not plsql". A package is PL/SQL.
However it is very simple. You want two things: select the table data and delete it. Two things, two commands.
select * from mytable;
truncate mytable;
(You could replace truncate mytable; with delete from mytable;, but this is slower and needs to be followed by commit; to confirm the deletion and end the transaction.)
Without pl/sql it's not possible.
Using pl/sql you can create a function which will populate a row, and then delete
Here is example :
drop table tempdate;
create table tempdate as
select '1' id from dual
UNION
select '2' id from dual
CREATE TYPE t_tf_row AS OBJECT (
id NUMBER
);
CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
CREATE OR REPLACE FUNCTION get_tab_tf RETURN t_tf_tab PIPELINED AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
FOR rec in (select * from tempdate) LOOP
PIPE ROW(t_tf_row(rec.id));
END LOOP;
delete from tempdate ; commit;
END;
select * from table(get_tab_tf) -- it will populate and then delete
select * from tempdate --you can check here result of deleting
you can use below query
select * from Table_demo delete from Table_demo
The feature you seek is SERIALIZABLE ISOLATION LEVEL. This feature enables repeatable reads, which in particular guarantee that both SELECTand DELETEwill read and process the same identical data.
Example
Alter session set isolation_level=serializable;
select * from tempdate;
--- now insert from other session a new record
delete from tempdate ;
commit;
-- re-query the table old records are deleted, new recor preserved.

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;

How to get the objects from an oracle table of row objects

When dealing with an oracle table containing row objects I would expect that each row is an object and I can invoke functions on it or pass it to functions in any context.
As an example if I declare the following:
create type scd_type as object
(
valid_from date,
valid_to date,
member function get_new_valid_to return date
);
create type scd_type_table as table of scd_type;
create table scd_table of scd_type;
create procedure scd_proc (in_table in scd_type_table)
as
begin
... do stuff ...
end;
/
And now I try to call my proc with the table
begin
scd_proc (scd_table);
end;
/
I get an error. Even reading the rows into a nested table is not straight forward. I would expect it to work like this:
declare
temp_table scd_type_table;
begin
select * bulk collect into temp_table from scd_table;
... do stuff ...
end;
/
but instead I have to call the constructor for every line.
And last I cannot invoke functions in a merge statement even though it works in an update statement. Example:
update scd_table st
set st.valid_to = st.get_new_valid_to(); <--- Works.
merge into scd_table st
using (select sysdate as dateCol from dual) M
on (st.valid_from = M.dateCol)
when matched then update set st.valid_to = st.get_new_valid_to(); <--- Does not work.
So I guess there are three sub-questions here:
1) What is the easiest way to pass a table of row objects into a procedure expecting a nested table of the same type?
2) What is the easiest way to convert a table of row objects into a nested table of the same type?
3) Why can't I invoke functions on an object as part of a merge statement (but in an update statement)?
which all come down to the question of "How to extract objects from a table of row objects?".
I can't help but think you need to re-read the documentation on PL/SQL types.
You were close with the bulk collect code. Minor change given below:
declare
plsql_table scd_type_table;
begin
select VALUE(t) bulk collect into plsql_table from scd_table t;
-- do stuff
end;
/
I will admit, I have no idea why the merge fails but the update works.