I have a materialized view(MV) which will refresh everyday at 23:00. It will select from a large transaction table (e.g. 100 million of records) and summarize the data which to be used in reporting purposes.
The MV is very simple just contain 4 columns and 7 records. Each time when user generate the report, it will always show the data as in the MV. Now user request to be able to view back the data of last year. Due to my MV always replace existing data, I am not able to achieve the user request.
My question
1. Is it possible to store the data in MV automatically in a persistent table?
2. Is it feasible to create a trigger to insert the data in MV to another table each time the MV is refresh complete?
Materialised Views do not have triggers. However, Updatable Materialized View does have triggers but it has a catch to it, it must be based upon a single table.
Based on more than one table
CREATE MATERIALIZED VIEW LOG ON EMP;
CREATE MATERIALIZED VIEW mv_test
REFRESH FAST WITH PRIMARY KEY
FOR UPDATE
AS
SELECT *
FROM emp em JOIN DEPT de ON EM.DEPTNO = DE.DEPTNO;
ORA-12013: updatable materialized views must be simple enough to do fast refresh
Based on single table
CREATE MATERIALIZED VIEW mv_test
REFRESH FAST WITH PRIMARY KEY
FOR UPDATE
AS
SELECT * FROM emp;
Materialized View created.
Trigger
CREATE OR REPLACE TRIGGER test_tg
BEFORE INSERT OR UPDATE OF ENAME, MGR
ON MV_TEST
REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
DECLARE
tmpVar NUMBER;
BEGIN
tmpVar := 0;
NULL;
-- do as per the logic
EXCEPTION
WHEN OTHERS
THEN
NULL;
-- Consider logging the error and then re-raise
RAISE;
END test_tg;
Trigger created.
If the requirement is to have historical data, why not consider a standard transactional table to have data persistence using a Stored Procedure which is possible to execute using scheduler jobs.
As you have cited querying a large table which has 100 million records, my reckoning would be to use FOLL ALL or BULK COLLECT or consider batch processing, needless to say; this is a different topic.
The below are pseudo codes for procedure and scheduler jobs, make changes as deemed necessary. Either use INSERT or MERGE
Procedure using INSERT
CREATE OR REPLACE PROCEDURE historical_records (p_emp_no emp.empno%TYPE)
IS
BEGIN
FOR rec IN ( SELECT ename, mgr, SUM (sal) tot_sal
FROM scott.emp
WHERE empno = p_emp_no
GROUP BY ename, mgr)
LOOP
INSERT INTO hist_table (empno,
ename,
mgr,
sal_tot)
VALUES (rec.empno,
rec.ename,
rec.mgr,
rec.tot_sal);
END LOOP;
END;
Procedure using MERGE
CREATE OR REPLACE PROCEDURE historical_records (p_emp_no emp.empno%TYPE)
IS
BEGIN
MERGE INTO hist_table trg
USING ( SELECT ename, mgr, SUM (sal) tot_sal
FROM scott.emp
WHERE empno = p_emp_no
GROUP BY ename, mgr) src
ON (trg.empno = src.empno)
WHEN MATCHED
THEN
UPDATE SET trg.ename = src.ename, trg.mgr = src.mgr
WHEN NOT MATCHED
THEN
INSERT (trg.empno, trg.ename, trg.sal_tot)
VALUES (src.empno, src.ename, src.tot_sal);
END;
Scheduler Job
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'HIST_PROC_JOB',
job_type => 'PLSQL_BLOCK',
JOB_ACTION => 'BEGIN historical_records; END;',
start_date => SYSDATE,
repeat_interval => 'FREQ=DAILY;BYHOUR=23;BYMINUTE=05',
end_date => NULL,
enabled => TRUE,
comments => 'Historical data insertion');
END;
/
Related
i have this table
TABLE "KEYWORD_RSLT"
( "ID" NUMBER PRIMARY KEY,
"SESSION_MONTH" VARCHAR2(40) NOT NULL ENABLE,
"PATIENT_NAME" VARCHAR2(50)
and i have this procedure that imports data to my table KEYWORD_RSLT.
create or replace PROCEDURE "PR_KEYWORD_SEARCH" (v_patient_id NUMBER, v_keyWord varchar2)
IS
BEGIN
delete from KEYWORD_RSLT;
insert into KEYWORD_RSLT (SESSION_MONTH, PATIENT_NAME)
select distinct
to_char(s.SESSION_DATE, 'MM-YYYY') as SESSION_MONTH,
p.FIRST_NAME ||' '||p.LAST_NAME as PATIENT_NAME
from SESSIONS s,
CLIENTS p
where s.CLIENTS_ID = p.ID
and (s.CRITICAL_POINT like LOWER ('%'||v_keyWord||'%') and s.CLIENTS_ID = v_patient_id
or s.ACTIONS like LOWER ('%'||v_keyWord||'%') and s.CLIENTS_ID = v_patient_id);
END PR_KEYWORD_SEARCH;
I want my primary key "ID" to take automatically the next available number starting from 1, but when my procedure deletes all data from this table, i want to start again from 1.
I tried with sequence ("SQ_KEYWORD_RSLT_INCREAMENT") and trigger but i can not reset this sequence from a new procedure using this code:
alter sequence SQ_KEYWORD_RSLT_INCREAMENT restart start with 1;
How can i fill my ID automatically from the beginning every time i delete all the data?
You say i can not reset this sequence but you don't say why so I'm assuming that you got an error. It is not possible to execute ddl statements in pl/sql directly, but it can be done using EXECUTE IMMEDIATE.
CREATE SEQUENCE koen_s START WITH 1;
Sequence KOEN_S created.
SELECT koen_s.NEXTVAL FROM DUAL;
NEXTVAL
----------
1
BEGIN
EXECUTE IMMEDIATE 'ALTER SEQUENCE koen_s RESTART START WITH 1';
END;
/
PL/SQL procedure successfully completed.
SELECT koen_s.NEXTVAL FROM DUAL;
NEXTVAL
----------
1
i have a pl/sql procedure to modify/delete records based on a checkbox selection in my Apex application:
delete from s_objectif_operation where id_operation=:p124_id_operation;
for a in (select distinct id from s_objectif
where id in (
SELECT trim(regexp_substr(:P124_S_OBJECTIF, '[^:]+', 1, LEVEL)) str
FROM dual
CONNECT BY instr(:P124_S_OBJECTIF, ':', 1, LEVEL - 1) > 0
))
loop
insert into s_objectif_operation
(id_s_objectif, id_operation)
values
(a.id, :p124_id_operation);
end loop;
for every modification, this procedure deletes all the records and insert the correct ones back so i had to remove the "cascade on delete" option the foreign key constraint to suspend any child record removal but now the procedure is not working.
maybe "raise an exception" can work?
There is no need to delete all the records and re-inserting only the checked ones. That is a brute force approach and it works but it does not capture the real user action.
As an alternative you can just delete/insert the changes. To do that, create an additional page item P124_S_OBJECTIF_OLD and set it to the value P124_S_OBJECTIF with a computation after header (or any pre-rendering processing point after P124_S_OBJECTIF has been initialized). In your pl/sql code use APEX_STRING.SPLIT to process the checkbox values and the MULTISET operator to decide which values have been touched.
Then your pl/sql process code could look like this.
DECLARE
l_objectif_old apex_t_varchar2;
l_objectif_new apex_t_varchar2;
l_objectifs_added apex_t_varchar2;
l_objectifs_removed apex_t_varchar2;
BEGIN
l_objectif_old := apex_string.split(:P124_S_OBJECTIF_OLD,':');
l_objectif_new := apex_string.split(:P124_S_OBJECTIF,':');
l_objectifs_added := l_objectif_new MULTISET EXCEPT l_objectif_old;
l_objectifs_removed := l_objectif_old MULTISET EXCEPT l_objectif_new;
-- add new
FOR i IN 1 .. l_objectifs_added.COUNT LOOP
INSERT INTO s_objectif_operation (id_s_objectif, id_operation)
VALUES (l_objectifs_added(i), :P124_ID_OPERATION);
END LOOP;
-- delete old
FOR i IN 1 .. l_objectifs_removed.COUNT LOOP
BEGIN
DELETE FROM s_objectif_operation
WHERE id = l_objectifs_removed(i);
EXCEPTION WHEN OTHERS THEN
-- this will fire if there are child records. Add your own code.
NULL;
END;
END LOOP;
END;
Note that you might have to tweak the insert and delete statement to match your data structure.
Hi friends am writing a stored procedure that selects and inserts data from one table to another table in the same database but different users, to speed up the insert am trying to use bulk collect and forall but the problem is am using dynamic SQL in the procedure that picks table name, it's corresponding primary key name and the date validation columns from another status table
For eg: consider the following query
Select empid from emp_tab where trunc(insert_time)=trunc(sysdate)
Here the PRIMARY KEY - empid, TABLENAME- emp_tab, date validation column - insert_time will be dynamically picked by the procedure during run time of the procedure from another status table but am not able incorporate the bulk collect and forall into dynamic SQL for bulk collect am getting
inconsistent data type
error and forall am getting
virtual column
error
Does bulk and forall supports dynamic sql????
If not then is there any other way to speed up the insert like forall
Any suggestions are welcome
Thanks in advance
Yes, they do. Despite BULK COLLECT being a powerful tool, it can be tricky with all its limitation. Here is a example using REF CURSOR, but you can build the TYPES according to your necessity.
-- create source data
CREATE TABLE temp_table_source AS
(SELECT ROWNUM id,
dbms_random.String('U', 10) name,
Round(dbms_random.Value(1, 7000)) salary
FROM dual
CONNECT BY LEVEL <= 5000 -- number of rows for the test
);
-- create destination table (empty)
CREATE TABLE temp_table_dest AS
SELECT * FROM temp_table_source WHERE 1 = 2;
-- Dynamic bulk anonymous block
DECLARE
v_stmt VARCHAR2(100);
v_insert VARCHAR2(100);
TYPE rec_source IS RECORD ( -- a type to represent your data
id temp_table_source.id%TYPE,
name temp_table_source.name%TYPE,
salary temp_table_source.salary%TYPE
);
TYPE t_source IS TABLE OF rec_source;
m_source t_source;
TYPE l_cursor_type IS REF CURSOR;
l_cursor l_cursor_type;
BEGIN
v_stmt := 'SELECT * FROM temp_table_source where 1 = :x';
v_insert := 'INSERT INTO temp_table_dest (id, name, salary) VALUES (:a, :b, :c)';
OPEN l_cursor FOR v_stmt USING 1; -- example of filtering
LOOP -- use limits to test performance, but the LOOP is optional
FETCH l_cursor BULK COLLECT INTO m_source LIMIT 1000;
--
FORALL i IN 1 .. m_source.COUNT
EXECUTE IMMEDIATE v_insert
USING m_source(i).id, m_source(i).name, m_source(i).salary;
--
COMMIT;
EXIT WHEN m_source.COUNT = 0;
END LOOP;
END;
/
-- See the result
Select count(1) from temp_table_dest;
If you need a dynamic TYPE as well, you will need to create it and drop at the end of your procedure instead of declare it in your package.
I need to delete data from many tables based on one parameter
The problem is that two tables are related to each other so in order to delete data properly i need to store id's somewhere.
-- i would like to store temp data
-- this one is only for convienience to avoid repeating same select many times
create table ztTaryfa as select zt_taryfa from tw_zbiory_taryfy
where 1=2;
-- this one is mandatory but I dont know how to make it work
Create table wnioskiId as select poli_wnio_id_wniosku from polisy
where 1=2;
Begin
-- fill temp tables
insert into ztTaryfa (
select zt_taryfa from tw_zbiory_taryfy
where zt_zbior = :zbiorId);
insert into wnioskiId (
select poli_wnio_id_wniosku from polisy
where poli_taryfa_id in ztTaryfa);
- regular deletion
delete from POLISY_OT where ot_poli_id in (
select poli_id from polisy
where poli_taryfa_id in ztTaryfa);
commit;
delete from DANE_RAPORTOWE where DR_RPU_ID in (
select RPU_ID from ROZLICZ_PLIK_UBEZP where RPU_ROZLICZ_PLIK_ID in (
select RP_ID from ROZLICZ_PLIK
where RP_ZBIOR_ID = :zbiorId ));
commit;
-- and here we go I need to delete data from POLISY first
delete from POLISY where poli_taryfa_id in ztTaryfa;
commit;
-- but by doing it I lose ids which i need here,
-- so I have to store them somehow and use them here.
delete from WNIOSKI where wnio_id in wnioskiId;
commit;
End;
-- and now lets get rid off temp tables
drop table ztTaryfa;
commit;
drop table wnioskiId;
commit;
To sum up i just need to know how to store somewhere between Begin and End a result of a select query which I can later use in delete statement.
Sounds but I tried so many different methods and all seems to not work.
What u see above is just a 1/3 of the script so I rly would like to make it all simple to use with one parameter.
Thanks you in advance.
You can use global types as simple as this:
create or replace type myrec is object (myid number);
create or replace type mytemp_collection is table of myrec;
declare
v_temp_collection mytemp_collection;
begin
v_temp_collection := mytemp_collection();
select myrec (t.field_type_id ) bulk collect into v_temp_collection from fs_field_types t
where mod(t.field_type_id+1,3)=0; -- for example
FOR i IN 1 .. v_temp_collection.count LOOP
DBMS_OUTPUT.put_line(v_temp_collection(i).myid);
End loop;
delete fs_field_types_back t where t.field_type_id in (select myid from table(v_temp_collection));
end;
Change select and where clause according to your business.
Let's have a look on my source code:
CREATE OR REPLACE PROCEDURE MAKE_COPY_OF_CLASSROOMS AUTHID CURRENT_USER AS
TYPE classrooms_table_type IS TABLE OF classrooms%ROWTYPE INDEX BY PLS_INTEGER;
classrooms_backup classrooms_table_type;
CURSOR classrooms_cursor IS
SELECT *
FROM classrooms
WHERE year = 1
ORDER BY name;
v_rowcnt PLS_INTEGER := 0;
BEGIN
OPEN classrooms_cursor;
FETCH classrooms_cursor
BULK COLLECT INTO classrooms_backup;
CLOSE classrooms_cursor;
EXECUTE IMMEDIATE 'CREATE TABLE classrooms_copy AS (SELECT * FROM classrooms WHERE 1 = 2)';
--COPY ALL STORED DATA FROM classrooms_backup TO classrooms_copy
END MAKE_COPY_OF_classrooms;
I'm stucked for hours on trying to insert data from "classrooms_backup" into the table "classrooms_copy", which is created by EXECUTE IMMEDIATE command. It's necessary to create table "classrooms_copy" via EXECUTE IMMEDIATE command. I tried to create another EXECUTE command with for loop in it:
EXECUTE IMMEDIATE 'FOR i IN classrooms_backup.FIRST..classrooms_backup.LAST LOOP
INSERT INTO classrooms_copy(id,room_id,year,name)
VALUES(classrooms_backup(i).id,classrooms_backup(i).room_id,classrooms_backup(i).year,classrooms_backup(i).name);
END LOOP;';
But it's road to the hell. I'm retrieving an invalid SQL statement error.
Thanks for your help!
There's no need for much PL/SQL here. Also, try to avoid the keyword CURSOR - there's almost always a better way to do it.
create or replace procedure make_copy_of_classrooms authid current_user as
begin
execute immediate '
create table classrooms_copy as
select *
from classrooms
where year = 1
order by name
';
end make_copy_of_classrooms;
/