Create Table from Query IN PLSQL - oracle

Since complex query takes long time to execute so I want to create a table to keep for future use, I don't want to execute it again. so my idea is that I want to create a table by insert query result into it.
The below is my sample code. It is not working it is just my idea. Thanks
DECLARE
with tab_emp as (
select * from employees
),
tab_dept as
(select * from departments)
procedure create_tab_from_query IS
begin
EXECUTE IMMEDIATE ('create table mytest as select * from '|| tab_emp || ' where 1=0');
end create_tab_from_query;
BEGIN
create_tab_from_query;
dbms_output.put_line(abc);
END;
/

This works:
SQL> with t2 as (select * from t) select * from t2;
L A
--------- ------------------------------------------------------
RUS Русский
But you cannot use WITH in a CREATE TABLE statement as you would like to use.
This cannot work:
SQL> with t2 as (select * from t) create table t3 as select * from t2;
with t2 as (select * from t) create table t3 as select * from t2
*
ERROR at line 1:
ORA-00928: missing SELECT keyword
You should try something simpler without WITH clause:
SQL> create table t3 as select * from t;
Table created.
In your case your PL/SQL code could be simplified this way:
SQL> --
SQL> BEGIN
2 EXECUTE IMMEDIATE ('
3 create table mytest as select * from employees where 1=0');
4 dbms_output.put_line('abc');
5 END;
6 /
abc
PL/SQL procedure successfully completed.

Related

Avoiding "select into" error in PL/SQL when filtering a table with a variable

In the following minimal PL/SQL query example, I can't get the query to work, in order to filter records from a table based on a certain date, which is stored in a variable.
The goal obviously is to end up with different query results, by changing the date variable to some other moment, different from sysdate.
This query does succeed in printing the calculation date variable's content, but the select query afterwards fails with ORA-06550: line 6, column 3 (so on 'select *' below) : PLS-00428: an INTO clause is expected in this SELECT statement.
DECLARE
v_calculation_date date := sysdate;
BEGIN
dbms_output.put_line(v_calculation_date);
select *
from t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
END;
OK, with a SELECT INTO then... This again prints the calculation date variable's content just fine, but the select query afterwards again fails, with the same error:
DECLARE
v_calculation_date date;
BEGIN
select sysdate into v_calculation_date from dual; -- dual is a dummy table with only one row and one column, just what we need here.
dbms_output.put_line(v_calculation_date);
select *
from t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
END;
I already have a SELECT INTO, why is the normal SELECT, which follows afterwards, then refused?
For the sake of completeness: this is a minimal query example for easy reproduction of the problem. My actual query is more complex, I eventually want something like this to work:
DECLARE
v_calculation_date date;
BEGIN
DROP TABLE t1;
CREATE TABLE t1 as (
WITH intermediary1 AS (
-- The actual query from above, as a subquery
SELECT *
FROM t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
)
)
/* other drops, creates, inserts ... */
END
What is wrong in my query?
Execution info: the SQL dialect is PL/SQL, the IDE is PL/SQL Developer version 13.0, the database server runs Oracle Database 12c Enterprise Edition Release 12.2.
In PL/SQL, every SELECT (that isn't part of a cursor) must have an INTO clause. Therefore, it's not enough that one of your SELECTs has it.
As of your final code (the one that contains CREATE TABLE): that won't work. DDL can't be executed from PL/SQL, unless you use dynamic SQL (execute immediate). Therefore, all your "other drops, creates, ..." will also have to be dynamic.
Note that we usually do not create objects dynamically; create table at SQL level. Then, if you want to populate it with different data, do so (either from SQL or PL/SQL). First delete "old" data - you can use delete or truncate (but it is DDL so - as you already know by now - it requires execute immediate), and then re-populate the table.
Dynamic SQL is difficult to debug. Don't use it if you don't have to.
If you're concerned about your own data so that other users wouldn't interfere, consider creating a global temporary table (or private; depending on database version you use).
[EDIT, based on your comments]
I'm afraid you're misinterpreting reality. DDL can't be executed in PL/SQL unless it is dynamic SQL. Your "final" code is an anonymous PL/SQL block (I'm talking about the last code you posted, the one that begins with the DECLARE, contains DROP and CREATE table t1) so it can not execute those statements as you posted.
Here's a demo. I don't have your tables so I'll mimic what you're doing, using Scott's sample DEPT table instead on my 11gXE database.
Does the WITH factoring clause work? Yes.
SQL> with intermediary1 as (
2 -- The actual query from above, as a subquery
3 select *
4 from dept
5 where 1 = 1
6 )
7 select * from intermediary1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Does it work with the CREATE TABLE? Not on my 11gXE:
SQL> create table t1 as (
2 with intermediary1 as (
3 -- The actual query from above, as a subquery
4 select *
5 from dept
6 where 1 = 1
7 )
8 select * from intermediary1
9 );
)
*
ERROR at line 9:
ORA-32034: unsupported use of WITH clause
I tried it on apex.oracle.com which runs 18cEE - doesn't work either.
Maybe your database version supports it, can't tell as I don't have any higher database version to try it on.
But OK, doesn't matter, that's easily fixed by moving a CTE into a subquery:
SQL> create table t1 as
2 select *
3 from (-- The actual query from above, as a subquery
4 select *
5 from dept
6 where 1 = 1
7 );
Table created.
So, everything is OK with the query itself, it won't cause my PL/SQL block to fail because of it.
OK, so - let's try it, the way you say you use frequently.
SQL> declare
2 v_calculation_date date;
3 begin
4 drop table t1;
5
6 create table t1 as
7 select *
8 from (-- The actual query from above, as a subquery
9 select *
10 from dept
11 where 1 = 1
12 );
13 /* other drops, creates, inserts ... */
14 end;
15 /
drop table t1;
*
ERROR at line 4:
ORA-06550: line 4, column 3:
PLS-00103: Encountered the symbol "DROP" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
SQL>
Whooops! There's an error on DROP. Let's remove it and leave CREATE TABLE:
SQL> declare
2 v_calculation_date date;
3 begin
4 -- drop table t1;
5
6 create table t1 as
7 select *
8 from (-- The actual query from above, as a subquery
9 select *
10 from dept
11 where 1 = 1
12 );
13 /* other drops, creates, inserts ... */
14 end;
15 /
create table t1 as
*
ERROR at line 6:
ORA-06550: line 6, column 3:
PLS-00103: Encountered the symbol "CREATE" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
SQL>
Whoops #2! Error on CREATE TABLE. Looks like your way doesn't really work. Let's try it my way, using dynamic SQL:
SQL> declare
2 v_calculation_date date;
3 begin
4 execute immediate 'drop table t1';
5
6 execute immediate (
7 'create table t1 as
8 select *
9 from (-- The actual query from above, as a subquery
10 select *
11 from dept
12 where 1 = 1
13 )'
14 );
15 /* other drops, creates, inserts ... */
16 end;
17 /
PL/SQL procedure successfully completed.
SQL> select * from t1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Now it works.
I'd be more than happy if you demonstrate the way you're doing it without dynamic SQL. Please, post your own SQL*Plus session (just like I did). Or, if you use some GUI, post a screenshot, no problem.
You do not need a PL/SQL block for your query and can simplify it to:
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -24) <= some_date
AND some_date < TRUNC(SYSDATE, 'MM');
If you want it in a PL/SQL block then you want to use SELECT ... INTO ... if the query is going to return a single row:
DECLARE
v_calculation_date DATE := SYSDATE;
v_col1 T.COL1%TYPE;
v_col2 T.COL2%TYPE;
v_some_date T.SOME_DATE%TYPE;
BEGIN
SELECT col1, col2, some_date
INTO v_col1, v_col2, v_some_date
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM');
-- Do something with the variables.
DBMS_OUTPUT.PUT_LINE( v_col1 || ', ' || v_col2 || ', ' || v_some_date );
END;
/
If you can return multiple rows then use SELECT ... BULK COLLECT INTO ... or use a cursor:
DECLARE
v_calculation_date DATE := SYSDATE;
BEGIN
FOR cur IN (
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM')
)
LOOP
-- Do something with the cursor.
DBMS_OUTPUT.PUT_LINE( cur.col1 || ', ' || cur.col2 || ', ' || cur.some_date );
END LOOP;
END;
/
db<>fiddle here
Again, for your more complicated query, you do not need PL/SQL (and cannot have DDL statements in a PL/SQL context):
DROP TABLE t1;
CREATE TABLE t1 as
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -24) <= some_date
AND some_date < TRUNC(SYSDATE, 'MM');
Or, if you want to use PL/SQL then do the DDL statements in the SQL context and then switch to PL/SQL for the DML statements:
DROP TABLE t1;
CREATE TABLE t1 AS
SELECT * FROM t WHERE 1 = 0;
-- or
-- TRUNCATE TABLE t1;
DECLARE
v_calculation_date DATE := SYSDATE;
BEGIN
INSERT INTO t1
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM');
END;
/

Oracle - trigger after update

I have trigger:
CREATE OR REPLACE TRIGGER TR.TR_OUTPUT_EHT_UPDATE_OPE
AFTER DELETE
ON TR.TR_JN_OUTPUT_EHT
FOR EACH ROW
DECLARE
BEGIN
UPDATE TR.TR_BP tbp
SET tbp.DONE= 2, tbp.DATA_WYK = SYSDATE
WHERE EXISTS (SELECT ARCH.NR
FROM TR.TR_KB_OUTPUT_ARCH ARCH
WHERE ARCH.NR = tbp.NRKK)
AND NOT EXISTS (SELECT OUTPUT.NR_SERYJNY
FROM TR.TR_JN_OUTPUT OUTPUT
WHERE OUTPUT.NR = tbp.NRKK);
END;
/
After that I want to get the "NRKK" of just updated row and update another table which is connected by this "NRKK":
UPDATE P.TR_OPE OPE
SET OPE.DATA = SYSDATE,
OPE.STATUS = OPE.STATUS + 1
WHERE OPE.NRKK = "NRKK"
I tried to do:
SELECT tbp.NRKK INTO v_nrkk FROM TR.TR_BP tbp WHERE tbp.DATA = (SELECT
MAX(tbp.DATA) FROM TR.TR_BP tbp1);
but I have errors: ORA 04091, ORA 06512 and ORA 04088.
EDITED:
I created another trigger:
CREATE OR REPLACE TRIGGER TR.TR_OP_E
AFTER UPDATE ON TR.TR_BP
FOR EACH ROW
DECLARE
v_nrkk TR.TR_BP.NRKK % TYPE;
BEGIN
SELECT tbp.NRKK INTO v_nrkk FROM TR.TR_BPtbp WHERE tbp.DATA = (SELECT
MAX(tbp.DATA) FROM TR.TR_BP tbp1);
IF v_nrkk is NOT NULL THEN
UPDATE P.TR_OPE OPE
SET OPE.DATA = SYSDATE, OPE.STATUS = 1
WHERE OPE.ID_BR = '00100300'
AND OPE.NRKK = v_nrkk;
END IF;
END;
/
But I have still errors...
As mentioned by #Renato Afonso, you can avoid mutating table error via using a COMPOUND TRIGGER. See below demo;
--Tables
CREATE TABLE TABLE_1 (data NUMBER);
/
CREATE TABLE TABLE_2 (DATA1 NUMBER);
/
--Record in table_1
SQL> select * from TABLE_1;
DATA
----------
1
--Record in table_2
SQL> select * from TABLE_2;
DATA1
----------
5
Trigger:
CREATE OR REPLACE TRIGGER TR_OP_E FOR
INSERT OR UPDATE OR DELETE
ON TABLE_1
COMPOUND TRIGGER
v_nrkk TABLE_1.DATA%TYPE;
AFTER STATEMENT
IS
BEGIN
--Selecting from the same table on which trigger is created
SELECT tbp.data
INTO v_nrkk
FROM Table_1 tbp
WHERE tbp.DATA = (SELECT MAX (tbp.DATA)
FROM Table_1 tbp1);
IF v_nrkk IS NOT NULL
THEN
UPDATE TABLE_2 OPE
SET OPE.DATA1 = v_nrkk;
END IF;
END
AFTER STATEMENT;
END;
/
Execution:
SQL> UPDATE TABLE_1 SET DATA = 2;
1 row updated.
SQL> COMMIT;
Commit complete.
SQL> select * from TABLE_1;
DATA
----------
2
SQL> select * from TABLE_2;
DATA1
----------
2

Execute select/insert statement within an IF clause Oracle

I need to execute some statements within the IF clause only if a table exists.
But the issue I am facing is, even when the condition is false, the statements are getting executed.
DECLARE
count_matching_row NUMBER := 0;
count_matching_tbl NUMBER := 0;
BEGIN
SELECT COUNT(*)
INTO count_matching_tbl
FROM user_tables
WHERE LOWER(table_name) = 'tab1';
IF(count_matching_tbl = 1)
THEN
SELECT COUNT (*)
INTO count_matching_row
FROM test1
WHERE ID IN (SELECT ID FROM tab1);
IF(count_matching_row = 0)
THEN
INSERT INTO review_case
SELECT
DISTINCT ID, d,e
FROM tab1
WHERE ID IS NOT NULL;
INSERT INTO review_case_payer
SELECT
a,b,c
FROM tab1
WHERE a IS NOT NULL;
COMMIT;
END IF;
END IF;
END;
/
Whenever I execute these statements, if the table 'tab1' exists it works fine.
If the table tab1 does not exist I get the error
"ORA-06550: line 13, column 14:
PL/SQL: ORA-00942: table or view does not exist"
I get similar errors for each line where I try to access table "tab1"
I tried with ref cursor but still the same, I cannot use it for insert statements.
Your error is due to the fact that you're using a table that may not exist; this error is thrown because the script has compile problems, not data problems, so the way you try to use the IF is not enough to handle your situation.
You need to use some dynamic SQL to handle an object that could not exist; for example, see the following.
If the table does not exist, nothing will be done:
SQL> select * from tab1;
select * from tab1
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> declare
2 vCountTab number;
3 begin
4 select count(1)
5 into vCountTab
6 from user_tables
7 where table_name = 'TAB1';
8
9 if vCountTab = 1 then
10 execute immediate 'insert into TAB1 values (1, 2)';
11 end if;
12 end;
13 /
PL/SQL procedure successfully completed.
If the table exists, the insert will be done:
SQL> create table tab1(a number, b number);
Table created.
SQL> declare
2 vCountTab number;
3 begin
4 select count(1)
5 into vCountTab
6 from user_tables
7 where table_name = 'TAB1';
8
9 if vCountTab = 1 then
10 execute immediate 'insert into TAB1 values (1, 2)';
11 end if;
12 end;
13 /
PL/SQL procedure successfully completed.
SQL> select * from tab1;
A B
---------- ----------
1 2
SQL>

Dynamically selecting partitions

I have a table with a few hundred partitions and I am generally interested on the latest 35.
Accordingly I am trying to create views which would access these dynamically. i.e. always use the latest in case ones are created.
The query:
select PARTITION_NAME,
PARTITION_POSITION,
NUM_ROWS,
AVG_ROW_LEN
from all_tab_partitions
where
table_name = 'MY_TABLE'
AND PARTITION_NAME <> 'P_LAST'
AND PARTITION_POSITION < (SELECT MAX(PARTITION_POSITION)
FROM all_tab_partitions) - 35
order by 2 DESC
;
Seems to return me the partition names I'm interested, however, I don't manage to use it's results to select the partitions. e.g.:
CREATE OR REPLACE VIEW MY_VIIEW AS
WITH t AS ( [Above query] )
SELECT * FROM
MY_TABLE PARTITION (SELECT /*+ FIRST_ROWS(1) */ PARTITION_NAME
from t);
(not the actual view, just an example)
So how do I do that? How do I create a view which will acess always the latest partition (execpt of "MAX")?
I am using Oracle 10g
thanks
You can do it using PL/SQL only
create or replace package my_table_ is
type t_records is table of my_table%rowtype;
function getpart(c_parts sys_refcursor) return t_records pipelined;
end;
create or replace package body my_table_ is
function getpart(c_parts sys_refcursor) return t_records pipelined is
v_partition all_tab_partitions.partition_name%type;
v_row my_table%rowtype;
c_mytab sys_refcursor;
begin
loop
fetch c_parts into v_partition;
exit when c_parts%notfound;
open c_mytab for 'select * from my_table partition ('||v_partition||')';
loop
fetch c_mytab into v_row;
exit when c_mytab%notfound;
pipe row (v_row);
end loop;
end loop;
end;
end;
Now you can
select * from table(my_table_.getpart(cursor(<QUERY_RETURNING_PARTITION_NAMES>)));
May be you can construct view's query using batch of union all statements with partition name in each statement, e.g.
create view p as
select * from my_table partition (part1)
union all
select * from my_table partition (part1)
...
union all
select * from my_table partition (part35)
Ok... I don't think your can use the Partition-Names, but you can use the Starting-Values of the Partitions to select the Data matching these Partitions...
So you View would look like this:
SELECT * FROM my_table WHERE date_col > get_part_limit( 'my_table', 35 ):
Where date_col is the column you use for partitioning - and get_part_limit is a stored function you write like this:
...
BEGIN
SELECT high_value FROM all_tab_partitions
INTO local_var
WHERE table_name = parameter_name
AND PARTITION_POSITION = MAX... - 35
EXECUTE IMMEDIATE 'SELECT '||local_var||' FROM DUAL' INTO local_return_value;
RETURN local_return_value;
END;
partitions are designed to be transparent for the data, so when you write a query, you simply don't know how your data is stored.
I see only one possibility to hit a particular partition: your WHERE clause should match values to the partitioned columns of latest (or latest 5) partition.
Next question is to build this WHERE clause on the fly. You already know that there is plenty of information in oracle dictionary. So you will read that and create a constructor to convert metadata conditions back into SQL.
irl we do exactly the same thing and use falco's solution like.
Here is my code:
create or replace function longToDate( myOwner varchar2,
mytable_name in varchar2,
mypartition_name in varchar2
) return date
as
cDate date;
cvar varchar2(1024);
rq varchar2(1024);
infiniteValue EXCEPTION;
PRAGMA EXCEPTION_INIT(infiniteValue, -00904);
begin
select high_value into cvar FROM dba_tab_partitions t where t.table_owner=myOwner and table_name=mytable_name and partition_name=mypartition_name;
rq:='select '||cvar||' from dual';
execute immediate rq into cDate;
return cdate;
EXCEPTION
WHEN infiniteValue
then return'01 jan 3000';
when others
then return null;
end longToDate;
Ant the view is something like this
create or replace view last_35 as
with maxdate as
(select longToDate(p.table_owner,p.table_name,p.partition_name) mydate,
rank()over(order by p.partition_position desc) mypos,
p.* from all_tab_partitions p
where p.table_name='MY_TABLE'
)
select /*+full(a)*/* from MY_TABLE a, maxdate
where MY_TABLE.partition_name>maxdate.mydate
and maxdate.mypos=35

Oracle dropping and recreating synonyms

When we use CTAS like this:
create table big2 as select * from big1;
drop table big1;
rename big2 to big1;
If there are synonym existing on big1, do we need to drop synonym on big1 prior to delete and recreate them? Or is this not necessary?
No. Becuase Synonym is just another name you give to an object ( either within your schema or not). See this code below.
(BTW, shouldn't you rename table t2 to t1 directly?? Does your CTAS have other where conditions, which you are not showing here?)
SQL> create table t1 as
2 select * from scott.emp;
Table created.
SQL> select count(*) from t1;
COUNT(*)
----------
14
SQL> select count(*) from t2;
select count(*) from t2
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> create synonym s1 for t1;
Synonym created.
SQL> create table t2 as
2 select * from t1;
Table created.
SQL> drop table t1;
Table dropped.
SQL> alter table t2 rename to t1;
Table altered.
SQL> select count(*) from t1;
COUNT(*)
----------
14
SQL> select count(*) from s1;
COUNT(*)
----------
14

Resources