PL/SQL存储过程调用dba_snapshot_refresh_times报错,求简洁解决方法
解决Oracle存储过程中MVIEW刷新校验的权限与实现问题
嘿,我来帮你搞定这个问题~你遇到的“Table or View does not exists”报错,其实核心是Oracle存储过程的权限机制在搞鬼,而且还有更简洁的实现方式可以替代原来的逻辑,咱们一步步说:
为啥单独跑脚本没问题,放存储过程就报错?
当你直接在SQL窗口跑那段脚本时,用的是你当前登录用户的权限,这个用户应该已经有访问dba_snapshot_refresh_times的权限了。但存储过程默认是定义者权限模式——也就是说,执行存储过程时用的是创建这个存储过程的用户的权限,而不是调用它的用户权限。如果创建存储过程的用户没有被直接授予SELECT ON dba_snapshot_refresh_times的权限(划重点:通过角色给的权限在存储过程里不生效,必须是直接给用户授权),就会触发这个找不到视图的错误。
更简洁的替代方案:用user_mviews代替dba视图
其实完全不用依赖dba_snapshot_refresh_times,咱们可以直接查user_mviews视图——这个视图只返回当前用户拥有的所有MVIEW,权限要求更低,而且里面的LAST_REFRESH_DATE字段直接就是MVIEW最后刷新的时间,用它来计算间隔更方便。
给你写好优化后的存储过程代码,直接用就行:
CREATE OR REPLACE PROCEDURE CHECK_REFRESH_MATV_ADDRESS IS v_refresh_interval_min NUMBER; BEGIN -- 计算当前时间和最后刷新时间的间隔(单位:分钟) SELECT ROUND(ABS((SYSDATE - last_refresh_date) * 24 * 60), 0) INTO v_refresh_interval_min FROM user_mviews WHERE mview_name = 'MATV_ADDRESS'; -- 因为是当前用户的MVIEW,不用指定owner -- 判断是否需要刷新 IF v_refresh_interval_min > 20 THEN DBMS_OUTPUT.PUT_LINE('MVIEW超过20分钟没刷新了,开始刷新...'); -- 执行MVIEW刷新操作 DBMS_MVIEW.REFRESH('MATV_ADDRESS'); ELSE DBMS_OUTPUT.PUT_LINE('MVIEW在20分钟内刚刷新过,不用动~'); END IF; -- 处理找不到指定MVIEW的情况 EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('哎,没找到名为MATV_ADDRESS的MVIEW哦'); END; /
要是你非得用dba_snapshot_refresh_times怎么办?
如果坚持要用原来的视图,有两个办法解决权限问题:
- 直接给存储过程所有者授权:
给创建这个存储过程的用户直接授予访问视图的权限(别用角色授权),执行这条SQL:GRANT SELECT ON dba_snapshot_refresh_times TO 你的存储过程所有者用户名; - 改用调用者权限模式:
创建存储过程时加上AUTHID CURRENT_USER,这样执行存储过程时就会用调用者的权限(前提是调用者有访问该视图的权限),代码改成这样:CREATE OR REPLACE PROCEDURE CHECK_REFRESH_MATV_ADDRESS AUTHID CURRENT_USER -- 开启调用者权限模式 IS v_last_refresh_min FLOAT; v_need_refresh VARCHAR2(10) := 'NO'; BEGIN SELECT ROUND(ABS((LAST_REFRESH - SYSDATE)*24*60),0) INTO v_last_refresh_min FROM dba_snapshot_refresh_times WHERE owner = 'ME' AND NAME = 'MATV_ADDRESS'; IF v_last_refresh_min > 20 THEN v_need_refresh := 'YES'; DBMS_MVIEW.REFRESH('ME.MATV_ADDRESS'); -- 执行刷新 END IF; DBMS_OUTPUT.PUT_LINE('是否需要刷新:' || v_need_refresh); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('没找到这个MVIEW的刷新记录'); END; /
顺便说下EXECUTE IMMEDIATE的正确赋值方式
之前你用EXECUTE IMMEDIATE没法赋值,是因为没加INTO子句,正确的写法应该是这样(不过其实没必要用动态SQL,静态SQL更简洁):
EXECUTE IMMEDIATE 'SELECT ROUND(ABS((LAST_REFRESH - SYSDATE)*24*60),0) FROM dba_snapshot_refresh_times WHERE owner = ''ME'' AND NAME = ''MATV_ADDRESS''' INTO v_last_refresh_min;
注意字符串里的单引号要写成两个才能转义哦~
内容的提问来源于stack exchange,提问作者denisb
相关产品推荐
相关产品推荐

