Oracle dba_snapshot_refresh_times use in PL SQL - oracle

Essentially I have an MVIEW that doesn't need to be refreshed every time I run my report so as a test I'm just checking the minute interval where if it hasn't been refreshed in the last 20 min, I would refresh it. So, if I run this script by itself, it works properly:
declare
LastRefresh float;
myvar varchar2(10) := 'NO';
BEGIN
SELECT ROUND(ABS((LAST_REFRESH - SYSDATE)*24*60),0)
INTO LastRefresh
FROM dba_snapshot_refresh_times
WHERE owner = 'ME'
AND NAME = 'MATV_ADDRESS';
IF (LastRefresh > 20)
THEN BEGIN myvar := 'YES'; END;
--// THIS IS WHERE I WOULD REFRESH THE MVIEW
END IF;
dbms_output.put_line(myvar);
END;
/
The problem I'm having is when I try to run this within an Oracle procedure, I get the "Table or View does not exists" on dba_snapshot_refresh_times. I perform the EXECUTE IMMEDIATE to invoke the query but can't seem to find a way to insert into a variable with this method. Any clean alternative ways I could do do this?
Did a bit of research where I could potentially just use some other form of "flag" where I'd populate a temp table in order to check the value, but I figured I'd ask about a cleaner way / solution.

It appears that you're programming something that Oracle offers itself; why wouldn't you set materialized view to automatically refresh every 20 minutes? You'd use
alter materialized view matv_address
refresh
next sysdate + 20 / (60 * 24);
As of your problem (if I correctly understood the problem): DBA_ views are visible to users with DBA privileges. It seems that user you're connected to doesn't have those privileges. Instead, you could use USER_SNAPSHOT_REFRESH_TIMES (for materialized views belonging to you), or ALL_SNAPSHOT_REFRESH_TIMES (for materialized views you have access to, but belong to other users as well).

Found my answer on this thread Materialized Views - Identifying the last refresh where I'm simply making use of an alternative table (all_mviews) to obtain the "Last Refresh Date" , so this appears to work within the Oracle procedure.
SELECT ROUND(ABS((LAST_REFRESH_DATE - SYSDATE)*24*60),0)
INTO LastRefresh
FROM all_mviews
WHERE owner = 'ME'
AND mview_name = 'MATV_ADDRESS';
IF (LastRefresh > 20) THEN
BEGIN
DBMS_SNAPSHOT.REFRESH(
LIST => 'MATV_ADDRESS'
,PUSH_DEFERRED_RPC => TRUE
,REFRESH_AFTER_ERRORS => FALSE
,PURGE_OPTION => 1
,PARALLELISM => 1
,HEAP_SIZE => 1
,ATOMIC_REFRESH => FALSE
,NESTED => FALSE);
END;
END IF;

Related

Can I make an update trigger using data from one table to update another?

I'm trying to get the suspension value in my studentstaff table to change from 'no' to 'yes' when the fine (a separate table) amount reaches >=10 for a specific person. I've also tried using IF but nothings seems to be working as I keep getting this error: ORA-04079: invalid trigger specification. "amount NUMBER(8);" is in the code as it was asking me to declare amount. I am using Oracle SQL. Thanks in advance.
CREATE TRIGGER new_suspension
AFTER UPDATE ON fine
FOR EACH ROW
amount NUMBER(8);
BEGIN
CASE
WHEN Amount >= 10 THEN
UPDATE studentstaff
SET suspensions = 'yes'
WHERE library_card_no = '419746';
END CASE;
END;
/
The best option for this type of trigger is to use the when condition on trigger as follows:
CREATE OR REPLACE TRIGGER new_suspension
AFTER UPDATE ON fine
FOR EACH ROW
WHEN (NEW.AMOUNY >= 10)
BEGIN
UPDATE studentstaff
SET suspensions = 'yes'
WHERE library_card_no = :new.LIBRARY_CARD_HOLDER;
END;
/
This trigger will be executed when the condition written in the WHEN clause is satisfied.
Without seeing your table definitions its hard, but here's a guess at what you might need
CREATE OR REPLACE
TRIGGER new_suspension
AFTER UPDATE ON fine
FOR EACH ROW
BEGIN
if :new.Amount >= 10 THEN -- ie, the incoming AMOUNT on the FINE table
UPDATE studentstaff
SET suspensions = 'yes'
WHERE library_card_no = :new.LIBRARY_CARD_HOLDER -- ie, the STUDENT being fined
end if;
END;
/
Hopefully that makes sense and is close to what you're aiming to achieve here. In this example, I'm assuming that your FINE table, contains the amount and the library card holder (ie, the student etc)

Is there a hint to generate execution plan ignoring the existing one from shared pool?

Is there a hint to generate execution plan ignoring the existing one from the shared pool?
There is not a hint to create an execution plan that ignores plans in the shared pool. A more common way of phrasing this question is: how do I get Oracle to always perform a hard parse?
There are a few weird situations where this behavior is required. It would be helpful to fully explain your reason for needing this, as the solution varies depending why you need it.
Strange performance problem. Oracle performs some dynamic re-optimization of SQL statements after the first run, like adaptive cursor sharing and cardinality feedback. In the rare case when those features backfire you might want to disable them.
Dynamic query. You have a dynamic query that used Oracle data cartridge to fetch data in the parse step, but Oracle won't execute the parse step because the query looks static to Oracle.
Misunderstanding. Something has gone wrong and this is an XY problem.
Solutions
The simplest way to solve this problem are by using Thorsten Kettner's solution of changing the query each time.
If that's not an option, the second simplest solution is to flush the query from the shared pool, like this:
--This only works one node at a time.
begin
for statements in
(
select distinct address, hash_value
from gv$sql
where sql_id = '33t9pk44udr4x'
order by 1,2
) loop
sys.dbms_shared_pool.purge(statements.address||','||statements.hash_value, 'C');
end loop;
end;
/
If you have no control over the SQL, and need to fix the problem using a side-effect style solution, Jonathan Lewis and Randolf Geist have a solution using Virtual Private Database, that adds a unique predicate to each SQL statement on a specific table. You asked for something weird, here's a weird solution. Buckle up.
-- Create a random predicate for each query on a specific table.
create table hard_parse_test_rand as
select * from all_objects
where rownum <= 1000;
begin
dbms_stats.gather_table_stats(null, 'hard_parse_test_rand');
end;
/
create or replace package pkg_rls_force_hard_parse_rand is
function force_hard_parse (in_schema varchar2, in_object varchar2) return varchar2;
end pkg_rls_force_hard_parse_rand;
/
create or replace package body pkg_rls_force_hard_parse_rand is
function force_hard_parse (in_schema varchar2, in_object varchar2) return varchar2
is
s_predicate varchar2(100);
n_random pls_integer;
begin
n_random := round(dbms_random.value(1, 1000000));
-- s_predicate := '1 = 1';
s_predicate := to_char(n_random, 'TM') || ' = ' || to_char(n_random, 'TM');
-- s_predicate := 'object_type = ''TABLE''';
return s_predicate;
end force_hard_parse;
end pkg_rls_force_hard_parse_rand;
/
begin
DBMS_RLS.ADD_POLICY (USER, 'hard_parse_test_rand', 'hard_parse_policy', USER, 'pkg_rls_force_hard_parse_rand.force_hard_parse', 'select');
end;
/
alter system flush shared_pool;
You can see the hard-parsing in action by running the same query multiple times:
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
Now there are three entries in GV$SQL for each execution. There's some odd behavior in Virtual Private Database that parses the query multiple times, even though the final text looks the same.
select *
from gv$sql
where sql_text like '%hard_parse_test_rand%'
and sql_text not like '%quine%'
order by 1;
I think there is no hint indicating that Oracle shall find a new execution plan everytime it runs the query.
This is something we'd want for select * from mytable where is_active = :active, with is_active being 1 for very few rows and 0 for maybe billions of other rows. We'd want an index access for :active = 1 and a full table scan for :active = 0 then. Two different plans.
As far as I know, Oracle uses bind variable peeking in later versions, so with a look at the statistics it really comes up with different execution plans for different bind varibale content. But in older versions it did not, and thus we'd want some hint saying "make a new plan" there.
Oracle only re-used an execution plan for exactly the same query. It sufficed to add a mere blank to get a new plan. Hence a solution might be to generate the query everytime you want to run it with a random number included in a comment:
select /* 1234567 */ * from mytable where is_active = :active;
Or just don't use bind variables, if this is the problem you want to address:
select * from mytable where is_active = 0;
select * from mytable where is_active = 1;

INSERT AS SELECT in package where table has redaction

I've redacted columns on a table. When developer try to use a 'INSERT AS SELECT' command in a pkg they get :
ORA-28081: Insufficient privileges - the command references a redacted object.
Apart from granting exemption to the schema (defeating the use of redaction altogether) what can I do?
The developers use the package but shouldn't see the table contents
Here is the function :
FUNCTION Create****Transaction (******PackageUserId NUMBER)
return NUMBER
IS
/*****************************
Local Variable Definitions
******************************/
v_****tId NUMBER(15);
BEGIN
v_****Id := ***_PKG.GETNEXTAMSSEQNUM();
INSERT INTO ****_AGENCY_PACK_USER_TRANS ****
(
****_ID,
****_****_ID,
****_BUS_ID,
****_ROLE_ID,
****_DATE_FROM,
****_DATE_TO,
****_ABBR,
****_NOTE,
****_CREATED_BY,
****_DATE_CREATED,
****_AUDIT_ACTION,
****_AUDIT_DATE,
****_AUDIT_LOCATION,
****_AUDIT_USER,
****_VER_NUM,
****_AMSS_ID,
****_WEBSERVICE,
****_APR_ID,
****_ASSIGN_RULE_ALLOWED,
****_SUP_TAG,
****_CPR
)
(
select
v_****Id,
****_ID,
****_BUS_ID,
****_ROLE_ID,
****_DATE_FROM,
****_DATE_TO,
****_ABBR,
****_NOTE, (THIS IS REDACTED ON THE TABLE)
****_CREATED_BY,
****_DATE_CREATED,
****_AUDIT_ACTION,
****_AUDIT_DATE,
****_AUDIT_LOCATION,
****_AUDIT_USER,
****_VER_NUM,
****_AMSS_ID,
****_WEBSERVICE,
****_APR_ID,
****_ASSIGN_RULE_ALLOWED,
****_SUP_TAG,
****_CPR
FROM
***********_PACKAGE_USERS
WHERE ****_ID = *****PackageUserId
);
if SQL%ROWCOUNT = 0 THEN
dbms_output.put_line('ERROR - no rows inserted!!');
return 0;
else
return v_****Id;
end if;
END Create****ransaction;
Purpose of Redaction is to make sure no one with not having access to data should be able to view it.
Once you have applied redaction with policy 1=1 then it will be applicable for all users except SYS and it will not allow CTAS or insert as select. There is workaround or you can say a different way of providing access to rightful users.
Let me know if you need more details on this.

PL/SQL Update Trigger Updates All Rows

New to working with PL/SQL and trying to create a statement level trigger that will change the 'Reorder' value to 'Yes' when the product quantity (p_qoh) is either less than 10 or less than two times the product minimum (p_min). And if that's not the case, then to change the 'Reorder' value to 'No'. My problem is that when I perform an update for a specific product, it changes the reorder value of all rows instead of the one I'm specifying. Can't seem to figure out where I'm going wrong, think I've been staring at it too long, any help is greatly appreciated.
CREATE OR REPLACE TRIGGER TRG_AlterProd
AFTER INSERT OR UPDATE OF p_qoh, p_min ON product
DECLARE
v_p_min product.p_min%type;
v_p_qoh product.p_qoh%type;
CURSOR v_cursor IS SELECT p_min, p_qoh FROM product;
BEGIN
OPEN v_cursor;
LOOP
FETCH v_cursor INTO v_p_min, v_p_qoh;
EXIT WHEN v_cursor%NOTFOUND;
IF v_p_qoh < (v_p_min * 2) OR v_p_qoh < 10 THEN
UPDATE product SET p_reorder = 'Yes';
ELSE
UPDATE product SET p_reorder = 'No';
END IF;
END LOOP;
END;
/
The update command :
UPDATE product SET p_reorder = 'Yes';
updates all of your rows because you are not specifying a WHERE clause.
What you can do is to retrieve the product's id (product_id) using your cursor and save it so that you would use it this way:
UPDATE product SET p_reorder = 'Yes' WHERE id = product_id;
Whoaa, this is not how you do triggers.
1 - Read the Oracle Trigger Documentation
2 - (almost) Never do a commit in a trigger. That is the domain of the calling application.
3 - There is no need to select anything related to product. You already have the product record at hand with the :new and :old pseudo records. Just update the column value in :new as needed. Example below (not checked for syntax errors, etc.);
CREATE OR REPLACE TRIGGER TRG_AlterProd
BEFORE INSERT OR UPDATE OF p_qoh, p_min ON product
FOR EACH ROW
BEGIN
IF :new.p_qoh < (:new.p_min * 2) OR :new.p_qoh < 10 THEN
:new.p_reorder = 'Yes';
ELSE
:new p_reorder = 'No';
END IF;
END;
#StevieP, If you need to commit inside a trigger, you may want to consider doing it as Autonomous Transaction.
Also, sorry if my understanding of your problem statement is wrong, but your it sounded to me like a row level trigger - are you only updating the current row or are you scanning the entire table to change status on several rows? If it's on current row, #OldProgrammer's solution seems right.
And I am just curious, if you do an UPDATE statement inside the trigger on the same table, wouldn't it generate (recursive) trigger(s)? I haven't done statement triggers like this, so sorry if this is not the expected trigger behavior.
To me a statement trigger would make more sense, if the trigger was on say, sales table, when a product is sold (inserted into sales table), it will trigger the corresponding product id records to be updated (to REORDER) in Product table. That will prevent recursion danger also.

materialized view creation is fast but refresh takes hours

I am using a materialized view, and I cant set it to fast refresh because some of the tables are from remote database which does not have materialized view log.
When I create the materialized view, it took like 20 30 seconds. however when I was trying to refresh it.
It took more than 2 3 hours. and total number of records are only around 460,000.
Does anyone have any clue about how it would happen?
Thanks
Code looks like as following
create materialized view MY_MV1
refresh force on demand
start with to_date('20-02-2013 22:00:00', 'dd-mm-yyyy hh24:mi:ss') next trunc(sysdate)+1+22/24
as
( SELECT Nvl(Cr.Sol_Chng_Num, ' ') AS Change_Request_Nbr,
Nvl(Sr.Sr_Num, ' ') AS Service_Request_Nbr,
Nvl(Sr.w_Org_Id, 0) AS Org_Id,
Fcr.rowid,
Cr.rowid,
Bsr.rowid,
Sr.rowid,
SYSDATE
FROM Dwadmin.f_S_Change#DateWarehouse.World Fcr
INNER JOIN Dwadmin.d_S_Change#DateWarehouse.World Cr
ON Fcr.w_Sol_Chng_Id = Cr.w_Sol_Chng_Id
INNER JOIN Dwadmin.b_S_Change_Obl#DateWarehouse.World Bsr
ON Fcr.w_Sol_Chng_Id = Bsr.w_Sol_Chng_Id
INNER JOIN Dwadmin.d_S_Rec#DateWarehouse.World Sr
ON Sr.w_Srv_Rec_Id = Bsr.w_Srv_Rec_Id
WHERE Sr.Sr_Num <> 'NS'
);
I have tried to use dbms_mview.refresh('MY_MATVIEW', 'C', atomic_refresh=>false)
but it still took 141 mins to run... vs 159 mins without atomic_refresh=>false
I would personally NOT use the scheduler built into the mat view CREATE statement (start with ... next clause).
The main reason (for me) is that you cannot declare the refresh non-ATOMIC this way (at least I haven't found the syntax for this at CREATE time). Depending on your refresh requirements and size, this can save A LOT of time.
I would use dbms_mview.refresh('MY_MATVIEW', 'C', atomic_refresh=>false). This would:
Truncate MY_MATVIEW snapshot table
Insert append into MY_MATVIEW table
If you use the next clause in the create statement, it will setup an atomic refresh, meaning it will:
Delete * from MY_MATVIEW
Insert into MY_MATVIEW
Commit
This will be slower (sometimes much slower), but others can still query from MY_MATVIEW while the refresh is occurring. So, depends on your situation and needs.
You can test it. I run this manually and it works for me friend :)
BEGIN
DBMS_REFRESH.make(
name => 'DB_NAME.MINUTE_REFRESH',
list => '',
next_date => SYSDATE,
interval => '/*1:Mins*/ SYSDATE + 1/(60*24)',
implicit_destroy => FALSE,
lax => FALSE,
job => 0,
rollback_seg => NULL,
push_deferred_rpc => TRUE,
refresh_after_errors => TRUE,
purge_option => NULL,
parallelism => NULL,
heap_size => NULL);
END;
/
BEGIN
DBMS_REFRESH.add(
name => 'DB_NAME.MINUTE_REFRESH',
list => 'DB_NAME.MV_NAME',
lax => TRUE);
END;
/
And then u can destroy it with this.
BEGIN
DBMS_REFRESH.destroy(name => 'DB_NAME.MINUTE_REFRESH');
END;
/
You can create materialize view log.
CREATE MATERIALIZED VIEW LOG ON DB_NAME.TABLE_NAME
TABLESPACE users
WITH PRIMARY KEY
INCLUDING NEW VALUES;
I hope it can help you. :)
if it only takes 20-30 seconds to create why not just drop and recreate the materialized view instead of refreshing it?
I am guessing:
Create Table doesn't need to write into the transaction log, as it is a new table. atomic_refresh => false means there is a truncate on the delete side (bypassing logging), but you then still have the INSERT side to deal with, which likely means you get a lot of transaction logging.

Resources