Create 'TABLE AS' in PL/SQL block - oracle

My query is simple but PL/SQL code block is expecting 'INTO' statement.
Here is my query:
DECLARE
yesterdays_date DATE := SYSDATE-1;
start_date DATE :='01/JAN/2013';
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE P2P_DATA';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN
--if table not found DO NOTHING AND MOVE ON
--dbms_output.put_line('HELLO');
NULL;
END IF;
---------------------------create new table here-------------------
CREATE TABLE P2P_DATA AS
SELECT
GM_NAME,
NEW_SKILL,
WEEK_DATE,
TOR_MWF
FROM TEST_TABLE
WHERE WEEK_DATE BETWEEN start_date AND yesterdays_date;
END;
it gives the COMPILE time error:
FOUND CREATE: EXPECTING END SELECT or (BEGIN CASE CLOSE CONTINUE DECLARE ... ETC)
I am simply setting the dates in the declaration block and then creating a new table as a result of the select statement. any ideas how to accomplish this task?

This does the same thing but avoids the execute immediate and overhead of creating and dropping tables.
DECLARE
yesterdays_date DATE := SYSDATE-1;
start_date DATE :='01/JAN/2013';
BEGIN
--clear out old data------
DELETE from P2P_DATA;
---------------------------insert new data here-----------------
INSERT INTO P2P_DATA
SELECT
GM_NAME,
CASE WHEN SUBSTR(CST_NAME,0,5) = 'A'
THEN 'BVG1' ELSE SUBSTR(CST_NAME,0,5) END AS CST_NAME,
NEW_SKILL,
WEEK_DATE,
TOR_MWF,
TOR_MA,
TOR_DL
FROM TEST_TABLE
WHERE WEEK_DATE BETWEEN start_date AND yesterdays_date;
END;
--do your commit outside the transaction just in case
Even better just create a view and avoid the whole table thing when all you want is a subset.
CREATE VIEW VW_P2P_DATA
AS
SELECT
GM_NAME,
CASE WHEN SUBSTR(CST_NAME,0,5) = 'A'
THEN 'BVG1' ELSE SUBSTR(CST_NAME,0,5) END AS CST_NAME,
NEW_SKILL,
WEEK_DATE,
TOR_MWF,
TOR_MA,
TOR_DL
FROM TEST_TABLE
WHERE WEEK_DATE BETWEEN TO_DATE('01/JAN/2013','DD/MON/YYYY') AND SYSDATE-1;

You can not do DDL queries within a PL/SQL block; if you need to do so, you have to use dinamic SQL; for example, you could use:
execute immediate '
CREATE TABLE P2P_DATA AS
SELECT
GM_NAME,
NEW_SKILL,
WEEK_DATE,
TOR_MWF
FROM TEST_TABLE
WHERE WEEK_DATE BETWEEN start_date AND yesterdays_date;
';
However, please consider solutions different from create/drop tables on the fly if you can

Actually, I'll put this as an answer instead of a comment ...
I'd recommend avoiding the PL/SQL entirely ... just do the following:
truncate table P2P_DATA;
insert into P2P_DATA
SELECT
GM_NAME,
NEW_SKILL,
WEEK_DATE,
TOR_MWF
FROM TEST_TABLE
WHERE WEEK_DATE BETWEEN to_date('01/JAN/2013','dd/MON/yyyy') AND SYSDATE-1;
Simpler, cleaner, and faster. ;)

Related

creating table if not exist pl/sql with oracle 12c

i need to create a script that do this thing:
check if the table exists, if exists truncate(or drop and create) table else create that table. i try to search on internet but some code work for half or not work at all.
this is one of script i found on internet
DECLARE
val INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO val FROM user_tables WHERE table_name = 'tabella';
IF val = 1 THEN
EXECUTE IMMEDIATE ('TRUNCATE TABLE tabella');
ELSE
EXECUTE IMMEDIATE ('CREATE TABLE tabella(idTabella INTEGER NOT NULL, campo VARCHAR(50)');
END IF;
END;
You don't need to check if the table exists; just try to truncate the table and if the table does not exist then catch the exception and create the table:
DECLARE
table_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT( table_not_exists, -942 );
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE tabella';
DBMS_OUTPUT.PUT_LINE( 'truncated' );
EXCEPTION
WHEN table_not_exists THEN
EXECUTE IMMEDIATE 'CREATE TABLE tabella ( idTabella INTEGER NOT NULL, campo VARCHAR(50) )';
DBMS_OUTPUT.PUT_LINE( 'created' );
END;
/
db<>fiddle
Since table names are saved as upper case strings, you should check them the same case. There are to ways you can do this:
You can write your table name in upper case like:
SELECT COUNT(*) INTO val FROM user_tables WHERE table_name = 'TABELLA';
You can lower case your table_name column so you can input it lower case all the time:
SELECT COUNT(*) FROM user_tables WHERE LOWER(table_name) = 'tabella';
Hope this helps.
The database defaults all names to UPPER_CASE. When you execute
CREATE TABLE tabella(...
the database actually creates the table with a name of TABELLA. This is fine - unless you quote the name of an object, by surrounding it in double-quotes, the database will automatically convert all names to upper case, and everything will work just fine. The only time you need to remember to use UPPER_CASE names is when you're accessing system tables or views, such as USER_TABLES. If you change the query in your script to
SELECT COUNT(*) INTO val FROM user_tables WHERE table_name = 'TABELLA';
I think you'll find it works just fine.

how to execute delete statement inside plsql block and call it in a procedure

I have written below pl sql block and trying to create a procedue. But i am getting warnings and not able to execute the procedue.
Please suggest if something i am missing \
Please let me know if this question is duplicate as i am not able to get the exact link to refer
create or replace PROCEDURE EmployeeProc
IS
BEGIN
delete from Employeetable where EmplId in (
select EmployeeId FROM EmployeeMstrTbl where JoiningDate between to_date('2019-01-01','YYYY-MM-DD') and to_date('2019-02-28','YYYY-MM-DD'));
commit;
DBMS_OUTPUT.PUT_LINE('Deleted '||SQL%ROWCOUNT ||' records from Employeetable');
END;
Error: Object Invalid
Try using cursor
CREATE OR REPLACE PROCEDURE EMPLOYEEPROC IS
CURSOR C1 IS
SELECT EMPLOYEEID
FROM EMPLOYEEMSTRTBL
WHERE JOININGDATE BETWEEN TO_DATE('2019-01-01','YYYY-MM-DD') AND TO_DATE('2019-02-28','YYYY-MM-DD'));
BEGIN
FOR I IN C1 LOOP
DELETE FROM EMPLOYEETABLE
WHERE EMPLID=I.EMPLOYEEID;
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE('DELETED '||SQL%ROWCOUNT ||' RECORDS FROM EMPLOYEETABLE');
END;
Your code works just fine.
CREATE TABLE Employeetable
(
EmplId NUMBER
);
CREATE TABLE EmployeeMstrTbl
(
EmployeeId NUMBER,
JoiningDate DATE
);
CREATE OR REPLACE PROCEDURE EmployeeProc
IS
BEGIN
DELETE FROM Employeetable
WHERE EmplId IN
(SELECT EmployeeId
FROM EmployeeMstrTbl
WHERE JoiningDate BETWEEN TO_DATE ('2019-01-01',
'YYYY-MM-DD')
AND TO_DATE ('2019-02-28',
'YYYY-MM-DD'));
COMMIT;
DBMS_OUTPUT.PUT_LINE (
'Deleted ' || SQL%ROWCOUNT || ' records from Employeetable');
END;
EXEC EmployeeProc;
DROP TABLE Employeetable;
DROP TABLE EmployeeMstrTbl;
DROP PROCEDURE EmployeeProc;
Script output:
Table created.
Table created.
Procedure created.
PL/SQL procedure successfully completed.
Table dropped.
Table dropped.
Procedure dropped.
DBMS Output:
Deleted 0 records from Employeetable
Maybe you have a typo in a table name, column name or something similar.
I suggest that you try to execute your delete statement first to check if it works.
Not sure if perhaps you mistyped something, or if it is to do with how you have it set up. But even if it is a mistype and it could work, it's not nice, so do a loop.
i = 0;
FOR r in (select * FROM EmployeeMstrTbl where JoiningDate between to_date('2019-01-01','YYYY-MM-DD') and to_date('2019-02-28','YYYY-MM-DD'))
LOOP
DELETE FROM Employeetable where EmplId = r.EmployeeId;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Deleted '|| i ||' records from Employeetable');
Because this will work, and more importantly, its easier to understand. Keeping code short and abbreviated has become much less important nowadays since the size of the code is almost never the problem, but keeping it easy to understand is extremely important so that it can be maintained in the future.

Can I directly define a trigger in all_triggers table on a table?

I am performing an archival process on a huge database and it involves deleting the production active table and renaming another table to be the new production table. When dropping the production active table, the triggers also get deleted. So I am just taking a backup of the triggers defined on my table using
select * from all_triggers where table_name=mytablename;
My question is, can I directly copy these triggers in to the all_triggers table after I rename my other table to be the new production active table? Will the triggers still work?
Same question for defining indexes and constraints too.
Copying the triggers from one table to another can be done by copying DDL, and not updating all_triggers table. This can be done by using DBMS_METADATA.
The closest practical example I found here: Copy Triggers when you Copy a Table
The following script can be amended as per your need:
declare
p_src_tbl varchar2(30):= 'PERSONS'; --your table name
p_trg_tbl varchar2(30):= 'PSN2'; --your trigger name
l_ddl varchar2(32000);
begin
execute immediate 'create table '||p_trg_tbl||' as select * from '||p_src_tbl||' where 1=2';
for trg in (select trigger_name from user_triggers where table_name = p_src_tbl) loop
l_ddl:= cast(replace(replace(dbms_metadata.get_ddl( 'TRIGGER', trg.trigger_name),p_src_tbl,p_trg_tbl),trg.trigger_name,substr(p_trg_tbl||trg.trigger_name, 1, 30)) as varchar2);
execute immediate substr(l_ddl, 1, instr(l_ddl,'ALTER TRIGGER')-1);
end loop;
end;
/
No, you cannot directly manipulate data dictionary tables. You can't insert data directly into all_triggers (the same goes for any data dictionary table). I guess you probably could given enough hacking. It just wouldn't work and would render your database unsupported.
The correct way to go is to script out your triggers and reapply them later. If you want to do this programmatically, you can use the dbms_metadata package. If you want to get the DDL for each of the triggers on a table, you can do something like
select dbms_metadata.get_ddl( 'TRIGGER', t.trigger_name, t.owner )
from all_triggers t
where table_owner = <<owner of table>>
and table_name = <<name of table>>
To replicate your scenario i have prepared below snippet. Let me know if this helps.
--Simple example to copy Trigger from one table to another
CREATE TABLE EMP_V1 AS
SELECT * FROM EMP;
--Creating Trigger on Old Table for Example purpose
CREATE OR REPLACE TRIGGER EMP_OLD_TRIGGER
AFTER INSERT OR UPDATE ON EMP FOR EACH ROW
DECLARE
LV_ERR_CODE_OUT NUMBER;
LV_ERR_MSG_OUT VARCHAR2(2000);
BEGIN
dbms_output.put_line('Your code for data Manipulations');
--Like Insert update or DELETE activities
END;
-- To replace this trigger for emp_v2 table
set serveroutput on;
DECLARE
lv_var LONG;
BEGIN
FOR i IN (
SELECT OWNER,TRIGGER_NAME,DBMS_METADATA.GET_DDL('TRIGGER','EMP_OLD_TRIGGER') ddl_script FROM all_triggers
WHERE OWNER = 'AVROY') LOOP
NULL;
lv_var:=REPLACE(i.ddl_script,'ON EMP FOR EACH ROW','ON EMP_V1 FOR EACH ROW');
dbms_output.put_line(substr(lv_var,1,INSTR(lv_var,'ALTER TRIGGER',1)-1));
EXECUTE IMMEDIATE 'DROP TRIGGER '||I.TRIGGER_NAME;
EXECUTE IMMEDIATE lv_var;
END LOOP;
END;
--Check if DDL manipulation has been done for not
SELECT OWNER,TRIGGER_NAME,DBMS_METADATA.GET_DDL('TRIGGER','EMP_OLD_TRIGGER') ddl_script FROM all_triggers
WHERE OWNER = 'AVROY';
---------------------------------OUTPUT----------------------------------------
"
CREATE OR REPLACE TRIGGER "AVROY"."EMP_OLD_TRIGGER"
AFTER INSERT OR UPDATE ON EMP_V1 FOR EACH ROW
DECLARE
LV_ERR_CODE_OUT NUMBER;
LV_ERR_MSG_OUT VARCHAR2(2000);
BEGIN
dbms_output.put_line('Your code for data Manipulations');
--Like Insert update or DELETE activities
END;
"
-----------------------------OUTPUT----------------------------------------------

FORALL+ EXECUTE IMMEDIATE + INSERT Into tbl SELECT

I have got stuck in below and getting syntax error - Please help.
Basically I am using a collection to store few department ids and then would like to use these department ids as a filter condition while inserting data into emp table in FORALL statement.
Below is sample code:
while compiling this code i am getting error, my requirement is to use INSERT INTO table select * from table and cannot avoid it so please suggest.
create or replace Procedure abc(dblink VARCHAR2)
CURSOR dept_id is select dept_ids from dept;
TYPE nt_dept_detail IS TABLE OF VARCHAR2(25);
l_dept_array nt_dept_detail;
Begin
OPEN dept_id;
FETCH dept_id BULK COLLECT INTO l_dept_array;
IF l_dept_array.COUNT() > 0 THEN
FORALL i IN 1..l_dept_array.COUNT SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'INSERT INTO stg_emp SELECT
Dept,''DEPT_10'' FROM dept_emp'||dblink||' WHERE
dept_id = '||l_dept_array(i)||'';
COMMIT;
END IF;
CLOSE dept_id;
end abc;
Why are you bothering to use cursors, arrays etc in the first place? Why can't you just do a simple insert as select?
Problems with your procedure as listed above:
You don't declare procedures like Procedure abc () - for a standalone procedure, you would do create or replace procedure abc as, or in a package: procedure abc is
You reference a variable called "dblink" that isn't declared anywhere.
You didn't put end abc; at the end of your procedure (I hope that was just a mis-c&p?)
You're effectively doing a simple insert as select, but you're way over-complicating it, plus you're making your code less performant.
You've not listed the column names that you're trying to insert into; if stg_emp has more than two columns or ends up having columns added, your code is going to fail.
Assuming your dblink name isn't known until runtime, then here's something that would do what you're after:
create Procedure abc (dblink in varchar2)
is
begin
execute immediate 'insert into stg_emp select dept, ''DEPT_10'' from dept_emp#'||dblink||
' where dept_id in (select dept_ids from dept)';
commit;
end abc;
/
If, however, you do know the dblink name, then you'd just get rid of the execute immediate and do:
create Procedure abc (dblink in varchar2)
is
begin
insert into stg_emp -- best to list the column names you're inserting into here
select dept, 'DEPT_10'
from dept_emp#dblink
where dept_id in (select dept_ids from dept);
commit;
end abc;
/
There appears te be a lot wrong with this code.
1) why the execute immediate? Is there any explicit requirement for that? No, than don't use it
2) where is the dblink variable declared?
3) as Boneist already stated, why not a simple subselect in the insert statement?
INSERT INTO stg_emp SELECT
Dept,'DEPT_10' FROM dept_emp#dblink WHERE
dept_id in (select dept_ids from dept );
For one, it would make the code actually readable ;)

Displaying command entered to run the trigger

I am trying to figure out how to display the insert statement, which the user enters. I want it to display after the "Please update the insert statement" text prints. From reading a ton of things online, I found out that you can display the previous command entered on oracle, by entering a "/" sign, and also by running this query 'SELECT * FROM gv$sql WHERE SQL_ID = IDENT_CURRENT('gv$sql')'. I tried using an execute immediately statement in the trigger, using dbms_output.put_line(/), and simply using t0_char('/'); in the query as you see below. Any tips?
set serveroutput on
CREATE or REPLACE trigger before_insert_t
before insert on reservations
for each row
DECLARE
rooms_remaining number(5,2);
BEGIN
select roooms_rem into rooms_remaining from reservations where roomno=:new.roomno;
if rooms_remaining = 0 then
dbms_output.put_line('Insertion now allowed because room ' || :new.roomno || ' is booked!' );
dbms_output.put_line('Please update the insert statement');
-- to_char('/');
dbms_output.put_line('insert into reservations values ' || :new.roomno );
-- EXECUTE IMMEDIATE sql_stmt;
end if;
END;
/
show errors
insert into reservations values (99,9);
CREATE or REPLACE trigger before_insert_t
before insert on TEST_TAB1
for each row
DECLARE
sql_insert varchar2(1000);
BEGIN
select sql_text into sql_insert
from (select sql_text
from v$sql
where upper(sql_text) like 'INSERT INTO TEST_TAB1%'
order by first_load_time desc)
where rownum=1;
dbms_output.put_line('Inserting into table SQL is '||sql_insert);
END;
/
SQL> set serveroutput on
SQL> insert into TEST_TAB1 values ('Hello');
1 rows inserted.
Inserting into table SQL is insert into TEST_TAB1 values (:"SYS_B_0")
You order by FIRST_LOAD_TIME and sort it in descending order and select the first row which would be the most recent INSERT statement in case there are multiple INSERTs.

Resources