触发器变异表问题,oracle--ORA:04091:触发器/函数不能读它

本文详细介绍了在Oracle数据库中触发器的使用方法,包括解决同一事务内触发器与DML语句冲突的问题,通过使用Pragma autonomous_transaction实现独立事务处理。此外,还提供了创建表、触发器、定时器的示例代码,以及如何通过定时器定期执行存储过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题分析

Oracle中执行DML语句的时候是需要显示进行提交操作的。当我们进行插入的时候,会触发触发器执行对触发器作用表和扩展表的种种操作,但是这个时候触发器和插入语句是在同一个事务管理中的,因此在插入语句没有被提交的情况下,我们无法对触发器作用表进行其他额外的操作。如果执行其他额外的操作则会抛出如上异常信息。

解决方案

1,我们知道,出错的原因是因为触发器和DML语句在同一事务管理中,所以方案一便是将触发器和DML语句分成两个单独的事务处理。这里可以使用Pragma autonomous_transaction; 告诉Oracle触发器是自定义事务处理。

SQL语句如下:

create trigger iu_table
  after insert or update on tr_table
  for each row
  declare  --这里是关键的地方,在变量申明的地方,指定自定义事务处理。
  pragma autonomous_transaction; 
  begin
   insert into ts_table select * from tr_table t where t.tab_id = :new.tab_id;
  --这里需要显示提交事务
   commit;
  end iu_table;

2,在Oracle Trigger中有:new,:old两个特殊变量,当触发器为行级触发器的时候,触发器就会提供new和old两个保存临时行数据的特殊变量,我们可以从俩个特殊的变量中取出数据执行扩张表的DML操作。

SQL语句如下:

 create trigger iu_table
  after insert on tr_table
  for each row
  begin
   insert into ts_table(tab_id,tab_name) values(:new.tab_id,:new.tab_name);
   --这里需要注意,要知道不同的触发类型其特殊变量:new和:old保存的值的区别。
   --commit; 注意使用方案二,这里不能显示的进行提交操作操作,trigger中在没有声明自定义事务管理的时候,不能执行显示提交。
  end iu_table;

附工大科雅中间库过程:

/*
 * CREATED BY LS 2018.07.30
 * 河北廊坊广达-工大科雅中间库
*/
--一、建表
--清除已存在内容
DROP TABLE USER_INFO;
DROP TABLE USER_SF_INFO;
DROP TABLE RB_INFO;
DROP FUNCTION GET_ORGNAME_F;
DROP FUNCTION GET_ITEMVALUE_F;
DROP TRIGGER TR_USER_INFO_IU;
DROP TRIGGER TR_USER_INFO_DELETE;
DROP TRIGGER TR_AREA_INFO;
DROP TRIGGER TR_USER_SF_INFO;
DROP SEQUENCE EBZ_METER_READ_S;
DROP PROCEDURE RB_INFO_PRO;
DROP JOB xxx;



--用户信息表
CREATE TABLE USER_INFO
(
  YHBH      NUMBER, 
  YHBM      VARCHAR2(20),
  FGS       VARCHAR2(20),
  RLZ       VARCHAR2(20),
  XQ        VARCHAR2(50),
  LH        VARCHAR2(20),
  DYH       VARCHAR2(20),
  CS        VARCHAR2(20),
  SH        VARCHAR2(20),
  GNZT      VARCHAR2(20),
  YHMC      VARCHAR2(20),
  LXDH      VARCHAR2(20),
  CNMJ      NUMBER,
  SYNCSTATE NUMBER 
);
-- ADD COMMENTS TO THE COLUMNS 
COMMENT ON COLUMN USER_INFO.YHBH
  IS '用户编号(收费系统中的内部编号,唯一)';
COMMENT ON COLUMN USER_INFO.YHBM
  IS '用户编码(热力公司给用户做出的编码)';
COMMENT ON COLUMN USER_INFO.FGS
  IS '分公司';
COMMENT ON COLUMN USER_INFO.RLZ
  IS '热力站';
COMMENT ON COLUMN USER_INFO.XQ
  IS '小区';
COMMENT ON COLUMN USER_INFO.LH
  IS '楼号';
COMMENT ON COLUMN USER_INFO.DYH
  IS '单元号';
COMMENT ON COLUMN USER_INFO.CS
  IS '层数';
COMMENT ON COLUMN USER_INFO.SH
  IS '室号';
COMMENT ON COLUMN USER_INFO.GNZT
  IS '供暖状态';
COMMENT ON COLUMN USER_INFO.YHMC
  IS '用户名称';
COMMENT ON COLUMN USER_INFO.LXDH
  IS '联系电话';
COMMENT ON COLUMN USER_INFO.CNMJ
  IS '采暖面积';
COMMENT ON COLUMN USER_INFO.SYNCSTATE
  IS '热计量系统进行数据同步:状态0,未同步;1,已同步';  
  
--缴费信息表
CREATE TABLE USER_SF_INFO
(
  YHBH      NUMBER,
  YSJE      NUMBER,
  JFJE      NUMBER, 
  FMZT      VARCHAR2(10), 
  JFRQ      DATE,
  SYNCSTATE NUMBER
);
-- ADD COMMENTS TO THE COLUMNS 
COMMENT ON COLUMN USER_SF_INFO.YHBH
  IS '用户编号(收费系统中的内部编号,唯一)';
COMMENT ON COLUMN USER_SF_INFO.YSJE
  IS '应收金额';
COMMENT ON COLUMN USER_SF_INFO.JFJE
  IS '缴费金额';
COMMENT ON COLUMN USER_SF_INFO.FMZT
  IS '阀门状态 0,关阀,1开阀';
COMMENT ON COLUMN USER_SF_INFO.JFRQ
  IS '缴费日期';
COMMENT ON COLUMN USER_SF_INFO.SYNCSTATE
  IS '热计量系统进行数据同步: 状态0,未同步;1,已同步';
  

 -- 表计量数据表
CREATE TABLE RB_INFO
(
  YHBH      NUMBER,
  CNQ       VARCHAR2(20),
  RBBH      NUMBER,
  QCBL      NUMBER,
  JZBL      NUMBER,
  BJYL      NUMBER,
  SJYL      NUMBER,
  ROOMTEMP  NUMBER,
  SETTEMP   NUMBER,
  INTEMP    NUMBER,
  OUTTEMP   NUMBER,
  BOLTSTATUS NUMBER,
  SETBOLTSTATUS NUMBER,
  CBSJ      DATE,
  SYNCSTATE NUMBER
);
-- ADD COMMENTS TO THE COLUMNS 
COMMENT ON COLUMN RB_INFO.YHBH
  IS '用户编号';
COMMENT ON COLUMN RB_INFO.CNQ
  IS '采暖期';
COMMENT ON COLUMN RB_INFO.RBBH
  IS '热表编号';
COMMENT ON COLUMN RB_INFO.QCBL
  IS '期初表量(单位KWH)';
COMMENT ON COLUMN RB_INFO.JZBL
  IS '截止表量(单位KWH)';
COMMENT ON COLUMN RB_INFO.BJYL
  IS '表计用量(单位KWH)';
COMMENT ON COLUMN RB_INFO.SJYL
  IS '实际用量(单位KWH)';
COMMENT ON COLUMN RB_INFO.ROOMTEMP
  IS '室温';
COMMENT ON COLUMN RB_INFO.SETTEMP
  IS '设定室温';
COMMENT ON COLUMN RB_INFO.INTEMP
  IS '供水温度';
COMMENT ON COLUMN RB_INFO.OUTTEMP
  IS '回水温度';
COMMENT ON COLUMN RB_INFO.BOLTSTATUS
  IS '阀门状态';
COMMENT ON COLUMN RB_INFO.SETBOLTSTATUS
  IS '供暖状态';
COMMENT ON COLUMN RB_INFO.CBSJ
  IS '抄表时间';
COMMENT ON COLUMN RB_INFO.SYNCSTATE
  IS '热计量系统进行数据同步: 状态0,未同步;1,已同步';

--二、触发器
--根据CODE获取机构信息名称函数
CREATE OR REPLACE FUNCTION GET_ORGNAME_F(
V_FGSCODE IN VARCHAR2,
V_RLZCODE IN VARCHAR2,
V_XQCODE IN VARCHAR2,
V_LHCODE IN VARCHAR2,
V_TYPE   IN VARCHAR2
) RETURN VARCHAR2 IS
  RESULT VARCHAR2(200);
BEGIN
   --获取分公司名称
   IF V_TYPE='1' THEN
     SELECT FGS.NAME INTO RESULT FROM JC_CUSTOMER_ORG FGS
     WHERE FGS.TYPE='1'
     AND FGS.IS_DELETED=0
     AND FGS.CODE=V_FGSCODE;
   --获取热力站名称
   ELSIF V_TYPE='2' THEN
     SELECT RLZ.NAME INTO RESULT FROM JC_CUSTOMER_ORG FGS
     LEFT JOIN JC_CUSTOMER_ORG RLZ
     ON FGS.ID=RLZ.PARENT_ID
     AND RLZ.TYPE='2'
     AND RLZ.IS_DELETED=0
     AND RLZ.CODE=V_RLZCODE
     WHERE FGS.IS_DELETED=0
     AND FGS.CODE=V_FGSCODE;
   --获取小区名称
   ELSIF V_TYPE='3' THEN
     SELECT XQ.NAME INTO RESULT FROM JC_CUSTOMER_ORG FGS
     LEFT JOIN JC_CUSTOMER_ORG RLZ
     ON FGS.ID=RLZ.PARENT_ID
     AND RLZ.IS_DELETED=0
     AND RLZ.CODE=V_RLZCODE
     LEFT JOIN JC_CUSTOMER_ORG XQ
     ON XQ.PARENT_ID=RLZ.ID
     AND XQ.TYPE='3'
     AND XQ.IS_DELETED=0
     AND XQ.CODE=V_XQCODE
     WHERE FGS.IS_DELETED=0
     AND FGS.CODE=V_FGSCODE;

   --获取大楼名称
   ELSIF V_TYPE='4' THEN
     SELECT LH.NAME INTO RESULT FROM JC_CUSTOMER_ORG FGS
     LEFT JOIN JC_CUSTOMER_ORG RLZ
     ON FGS.ID=RLZ.PARENT_ID
     AND RLZ.IS_DELETED=0
     AND RLZ.CODE=V_RLZCODE
     LEFT JOIN JC_CUSTOMER_ORG XQ
     ON XQ.PARENT_ID=RLZ.ID
     AND XQ.CODE=V_XQCODE
     AND XQ.IS_DELETED=0
     LEFT JOIN JC_CUSTOMER_ORG LH
     ON LH.PARENT_ID=XQ.ID
     AND LH.TYPE='4'
     AND LH.IS_DELETED=0
     AND LH.CODE=V_LHCODE
     WHERE FGS.IS_DELETED=0
     AND FGS.CODE=V_FGSCODE;
   END IF;
  RETURN(RESULT);
END GET_ORGNAME_F;

--获取自定义项值函数
CREATE OR REPLACE FUNCTION GET_ITEMVALUE_F(
V_CUSTOMERID IN VARCHAR2,
V_TYPE   IN VARCHAR2
) RETURN VARCHAR2 IS
  RESULT VARCHAR2(200);
BEGIN
  --获取用户供暖状态,暂 排除部分停供及拆网
  IF V_TYPE='GNZT' THEN
    SELECT DECODE(JC.HEATING_STATUS,'是','停供','正常') INTO RESULT FROM JC_CUSTOMER JC
    WHERE JC.ID=V_CUSTOMERID
    AND JC.IS_DELETED=0
	AND JC.LOGOUT_FLAG=0;
  --获取用户收费面积
  ELSIF V_TYPE='SFMJ' THEN
    SELECT TO_CHAR(SUM(MJ.CHARGE_AREA_SIZE)) INTO RESULT FROM JC_AREA MJ
    WHERE MJ.JCCUSTOMERID=V_CUSTOMERID
    AND MJ.IS_DELETED=0;
  END IF;
  RETURN(RESULT);
END GET_ITEMVALUE_F;
--非自治事务
--1 用户信息触发器
--01 用户表JC_CUSTOMER触发
--001 插入、更新触发
CREATE OR REPLACE TRIGGER TR_USER_INFO_IU 
AFTER INSERT OR UPDATE ON JC_CUSTOMER
FOR EACH ROW 
DECLARE 
 RSSUM NUMBER;
BEGIN
 SELECT COUNT(*) INTO RSSUM FROM USER_INFO WHERE YHBH=:OLD.ID;
  IF RSSUM=0 THEN
    INSERT INTO USER_INFO
    (YHBH,
     YHBM,
     FGS,
     RLZ,
     XQ,
     LH,
     DYH,
     CS,
     SH,
     GNZT,
     YHMC,
     LXDH,
     CNMJ,
     SYNCSTATE)
  VALUES
    (:NEW.ID,
     :NEW.CODE,
     GET_ORGNAME_F(:NEW.COMPANY_CODE, '', '', '', '1'),
     GET_ORGNAME_F(:NEW.COMPANY_CODE, :NEW.STATION_CODE, '', '', '2'),
     GET_ORGNAME_F(:NEW.COMPANY_CODE, :NEW.STATION_CODE, :NEW.COMMUNITY_CODE, '', '3'),
     GET_ORGNAME_F(:NEW.COMPANY_CODE, :NEW.STATION_CODE, :NEW.COMMUNITY_CODE, :NEW.BUILDING_CODE, '4'),
     :NEW.UNIT_NO,
     :NEW.FLOOR_NO,
     :NEW.ROOM_NO,
     '正常',  	--新增用户供暖状态:正常
     :NEW.NAME,
     :NEW.CELLPHONE,
     0, 		--新增用户采暖面积:0
     0);
  ELSIF RSSUM =1 THEN
   UPDATE USER_INFO
	 SET YHBH = :NEW.ID,
		 YHMC = :NEW.NAME,
		 LXDH = :NEW.CELLPHONE
   WHERE YHBH = :OLD.ID;
  END IF;
END TR_USER_INFO_IU;

--002 删除触发,暂用自治事务
CREATE OR REPLACE TRIGGER TR_USER_INFO_DELETE
AFTER DELETE ON JC_CUSTOMER
FOR EACH ROW 
DECLARE
--使用自治事务,一个事务里的子事务
PRAGMA AUTONOMOUS_TRANSACTION; 
BEGIN
  DELETE FROM USER_INFO WHERE YHBH = :OLD.ID;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
END TR_USER_INFO_DELETE;

--02 面积表JC_AREA触发
--增加package变量,在Oracle Trigger中有:new,:old两个特殊变量,当触发器为行级触发器的时候,
--触发器就会提供new和old两个保存临时行数据的特殊变量,我们可以从俩个特殊的变量中取出数据执行扩张表的DML操作。
--如果既想更新变异表,同时又需要查询变异表,那么如何处理呢?
--将行级触发器与语句级触发器结合起来,在行级触发器中获取要修改的记录的信息,存放到一个软件包的全局变量中,
--然后在语句级后触发器中利用软件包中全局变量信息对变异表的查询,并根据查询的结果进行业务处理
--面积程序包
CREATE OR REPLACE PACKAGE JCAREA_PKG 
AS
  V_NEWCUSTOMERID NUMBER(19); 
  V_OLDCUSTOMERID NUMBER(19); 
END;
--001面积表,行级触发
CREATE OR REPLACE TRIGGER TR_AREA_ROW
AFTER INSERT OR UPDATE ON JC_AREA
FOR EACH ROW
BEGIN
  JCAREA_PKG.V_NEWCUSTOMERID := :NEW.JCCUSTOMERID;
END TR_AREA_ROW;

--002面积表,表级触发 
CREATE OR REPLACE TRIGGER TR_AREA_INFO
AFTER INSERT OR UPDATE OR DELETE ON JC_AREA
BEGIN
  UPDATE USER_INFO
    SET CNMJ = GET_ITEMVALUE_F(JCAREA_PKG.V_NEWCUSTOMERID,'SFMJ'),
	    GNZT = GET_ITEMVALUE_F(JCAREA_PKG.V_NEWCUSTOMERID,'GNZT')
  WHERE YHBH = JCAREA_PKG.V_NEWCUSTOMERID;
END TR_AREA_INFO;


--2 收费信息触发器
--费用信息程序包
CREATE OR REPLACE PACKAGE RFGL_TRANSACTION_PKG 
AS
  V_NEWCUSTOMERID NUMBER(19); 
  V_ID NUMBER(19);
  V_INCOME  NUMBER;
  V_TRANSACTION_TIME TIMESTAMP(6);
END;
--001交易表,行级触发
CREATE OR REPLACE TRIGGER TR_TRANSACTION_ROW
AFTER INSERT OR UPDATE ON RFGL_TRANSACTION
FOR EACH ROW
BEGIN
  RFGL_TRANSACTION_PKG.V_NEWCUSTOMERID := :NEW.CUSTOMER_ID;
  RFGL_TRANSACTION_PKG.V_ID := :NEW.ID;
  RFGL_TRANSACTION_PKG.V_INCOME := :NEW.INCOME;
  RFGL_TRANSACTION_PKG.V_TRANSACTION_TIME := :NEW.TRANSACTION_TIME;
END TR_AREA_ROW;
--002交易表,表级触发
CREATE OR REPLACE TRIGGER TR_TRANSACTION
AFTER INSERT OR UPDATE OR DELETE ON RFGL_TRANSACTION
DECLARE
 V_YSJE NUMBER;
BEGIN
	--获取该户账单应收
    SELECT RECEIVABLE INTO V_YSJE FROM 
    (
       SELECT * FROM RFGL_BILL RB 
        WHERE RB.CUSTOMER_ID=RFGL_TRANSACTION_PKG.V_NEWCUSTOMERID
       ORDER BY RB.CREATED_TIME DESC
    ) 
    WHERE ROWNUM=1;
	--默认关阀
	INSERT INTO USER_SF_INFO
	  (YHBH, YSJE, JFJE, FMZT, JFRQ, SYNCSTATE)
	VALUES
	  (RFGL_TRANSACTION_PKG.V_ID, V_YSJE, RFGL_TRANSACTION_PKG.V_INCOME, 0, RFGL_TRANSACTION_PKG.V_TRANSACTION_TIME, 0);
END TR_TRANSACTION;

--三、抄表、定时器
--抄表序列
CREATE SEQUENCE EBZ_METER_READ_S
MINVALUE -999999999999999999999999999
MAXVALUE -1
START WITH -1
INCREMENT BY -1
CACHE 20;

--读取热计量系统计量信息到收费系统
CREATE OR REPLACE PROCEDURE RB_INFO_PRO IS 
/*V_CUSTOMERID      JL_METER_BIND.CUSTOMER_ID%TYPE; --用户ID
  V_CYCLE           EBZ_METER_READ.CYCLE%TYPE;      --采暖期
  V_METER_CODE      JL_METER.METER_CODE%TYPE;   --仪表ID
  V_LAST_VALUE      EBZ_METER_READ.LAST_VALUE%TYPE; --上次表数
  V_THIS_VALUE      EBZ_METER_READ.THIS_VALUE%TYPE; --本次表数
  V_USE_VALUE       EBZ_METER_READ.USE_VALUE%TYPE;  --表计用量
  V_REAL_VALUE      EBZ_METER_READ.REAL_VALUE%TYPE; --实际用量
  V_READ_DATE       EBZ_METER_READ.READ_DATE%TYPE;  --抄表日期
  V_METER_ID        JL_METER_BIND.YID%TYPE;         --仪表ID
  */
BEGIN
INSERT INTO EBZ_METER_READ
  (ID,
   REAL_VALUE,
   THIS_VALUE,
   LAST_VALUE,
   METER_ID,
   REMARK,
   READER,
   READ_DATE,
   USE_VALUE,
   CYCLE)
  SELECT 
        EBZ_METER_READ_S.NEXTVAL,
        R.SJYL,
        R.JZBL,
        R.QCBL,
        M.YID,
        '工大科雅中间库导入',
        '中间库导入',
        R.CBSJ,
        R.BJYL,
        R.CNQ
    FROM RB_INFO R, JL_METER_BIND M
   WHERE R.YHBH = M.CUSTOMER_ID
     AND R.RBBH = M.METERCODE
     AND SYNCSTATE = 0;
  --更改同步状态
  UPDATE RB_INFO
     SET SYNCSTATE = 1
  WHERE SYNCSTATE = 0;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
END; 

--定时器,每分钟执行一次
DECLARE 
  JOB NUMBER;  
BEGIN 
  DBMS_JOB.SUBMIT(JOB, 'RB_INFO_PRO;', SYSDATE, 'TRUNC(SYSDATE,''MI'') + 1 / (24*60)');  
COMMIT;
END; 

-------------------------------------------------------------------------------

--以下是使用PRAGMA AUTONOMOUS_TRANSACTION

--触发器
--暂时用自治事务,有单独的commit,rollback,不使用自治事务会和变异表处在一个事务中
--1 用户信息触发器
--01 用户表JC_CUSTOMER触发
-- 001 插入、更新触发
CREATE OR REPLACE TRIGGER TR_USER_INFO_IU 
AFTER INSERT OR UPDATE ON JC_CUSTOMER
FOR EACH ROW 
DECLARE
--使用自治事务,一个事务里的子事务
PRAGMA AUTONOMOUS_TRANSACTION; 
 RSSUM NUMBER;
BEGIN
 SELECT COUNT(*) INTO RSSUM FROM USER_INFO WHERE YHBH=:OLD.ID;
  IF RSSUM=0 THEN
    INSERT INTO USER_INFO
    (YHBH,
     YHBM,
     FGS,
     RLZ,
     XQ,
     LH,
     DYH,
     CS,
     SH,
     GNZT,
     YHMC,
     LXDH,
     CNMJ,
     SYNCSTATE)
  VALUES
    (:NEW.ID,
     :NEW.CODE,
     GET_ORGNAME_F(:NEW.COMPANY_CODE, '', '', '', '1'),
     GET_ORGNAME_F(:NEW.COMPANY_CODE, :NEW.STATION_CODE, '', '', '2'),
     GET_ORGNAME_F(:NEW.COMPANY_CODE, :NEW.STATION_CODE, :NEW.COMMUNITY_CODE, '', '3'),
     GET_ORGNAME_F(:NEW.COMPANY_CODE, :NEW.STATION_CODE, :NEW.COMMUNITY_CODE, :NEW.BUILDING_CODE, '4'),
     :NEW.UNIT_NO,
     :NEW.FLOOR_NO,
     :NEW.ROOM_NO,
     '正常',  --新增用户:正常
     :NEW.NAME,
     :NEW.CELLPHONE,
     TO_NUMBER(GET_ITEMVALUE_F(:NEW.ID,'SFMJ')), --新增用户表:0
     0);
  ELSIF RSSUM =1 THEN
   UPDATE USER_INFO
	 SET YHBH = :NEW.ID,
		 YHMC = :NEW.NAME,
		 LXDH = :NEW.CELLPHONE
	--	 GNZT = GET_ITEMVALUE_F(:NEW.ID,'GNZT'),
	--   CNMJ = GET_ITEMVALUE_F(:NEW.ID,'SFMJ')
   WHERE YHBH = :OLD.ID;
  END IF;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
END TR_USER_INFO_IU;

--002 删除触发
CREATE OR REPLACE TRIGGER TR_USER_INFO_DELETE
AFTER DELETE ON JC_CUSTOMER
FOR EACH ROW 
DECLARE
--使用自治事务,一个事务里的子事务
PRAGMA AUTONOMOUS_TRANSACTION; 
BEGIN
  DELETE FROM USER_INFO WHERE YHBH = :OLD.ID;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
END TR_USER_INFO_DELETE;

--02 面积表JC_AREA触发
--001插入、更新 触发 
CREATE OR REPLACE TRIGGER TR_AREA_INFO
AFTER INSERT OR UPDATE OR DELETE ON JC_AREA
FOR EACH ROW
DECLARE
--使用自定义事务
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  UPDATE USER_INFO
    SET CNMJ = GET_ITEMVALUE_F(:NEW.JCCUSTOMERID,'SFMJ'),
	    GNZT = GET_ITEMVALUE_F(:NEW.ID,'GNZT')
  WHERE YHBH = :OLD.JCCUSTOMERID;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
END TR_AREA_INFO;


--2 收费信息触发器
CREATE OR REPLACE TRIGGER TR_USER_SF_INFO
AFTER INSERT OR UPDATE OR DELETE ON RFGL_TRANSACTION
FOR EACH ROW
DECLARE
--使用自定义事务
PRAGMA AUTONOMOUS_TRANSACTION;
 V_YSJE NUMBER;
BEGIN
	--获取该户账单应收
    SELECT RECEIVABLE INTO V_YSJE FROM 
    (
       SELECT * FROM RFGL_BILL RB 
        WHERE RB.CUSTOMER_ID=:NEW.CUSTOMER_ID
       ORDER BY RB.CREATED_TIME DESC
    ) 
    WHERE ROWNUM=1;
	--默认关阀
	INSERT INTO USER_SF_INFO
	  (YHBH, YSJE, JFJE, FMZT, JFRQ, SYNCSTATE)
	VALUES
	  (:NEW.ID, V_YSJE, :NEW.INCOME, 0, :NEW.TRANSACTION_TIME, 0);
	COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
END;

补充Oracle定时器内容:

declare

jobno number;

begin dbms_job.submit(

  jobno,--定时器ID,系统自动获得

  'PRC_INSERT;', --what执行的过程名

   sysdate,--next_date,定时器开始执行的时间,这样写表示立即执行

  'sysdate + 15/1440'--interval,设置定时器执行的频率,这样写每隔15分钟执行一次

);

commit; 

end;



这里第一个参数是任务编号,系统自动赋值。也可以采用isubmit来手动指定

第二个参数是需要执行的任务过程,代码长的话,可以将它写到一个存储过程里,再放到里面调用,比如'pro_test;' (pro_test假定为一个存储过程名)

第三个参数是,自动任务第一次执行的时间,如果需要它立即执行,则使用sysdate

最后一个参数,系统根据该参数的值指定下一次的执行时间。





declare

jobno

number;

begin

dbms_job.remove(45);

commit;

end;



exec dbms_job.remove(83);--删除一个定时器
exec dbms_job.run(84);--运行一个定时器
exec DBMS_JOB.BROKEN(83,SYS.DIUTIL.INT_TO_BOOL(1));--停止一个定时器
exec DBMS_JOB.INTERVAL(84, 'sysdate + 60/1440');--改变一个定时器的执行频率成每隔一小时执行一次


select * from user_jobs;——查看调度任务

select * from dba_jobs_running;——查看正在执行的调度任务

select * from dba_jobs;——查看执行完的调度任务





定时器的参数说明:

myjob参数是由Submit()过程返回的binary_ineger。这个值用来唯一标识一个工作;

what参数是将被执行的PL/SQL代码块,这里指的是一个存储过程,注意名字后面的分号;

next_date参数指识何时将运行这个工作。写Job的时候可以不指定该值;

interval参数何时这个工作将被重执行。

关于interval的设置,参考以下几个例子:

1、 每分钟执行

Interval => TRUNC(sysdate,’mi’) + 1 / (24*60)

2、 每天定时执行

例如:每天的凌晨2点执行

Interval => TRUNC(sysdate) + 1 +2 / (24)

3、 每周定时执行

例如:每周一凌晨2点执行

Interval => TRUNC(next_day(sysdate,2))+2/24 --星期一,一周的第二天

4、 每月定时执行

例如:每月1日凌晨2点执行

Interval =>TRUNC(LAST_DAY(SYSDATE))+1+2/24

5、 每季度定时执行

例如每季度的第一天凌晨2点执行

Interval => TRUNC(ADD_MONTHS(SYSDATE,3),'Q') + 2/24

6、 每半年定时执行

例如:每年7月1日和1月1日凌晨2点

Interval => ADD_MONTHS(trunc(sysdate,'yyyy'),6)+2/24

7、 每年定时执行

例如:每年1月1日凌晨2点执行

Interval =>ADD_MONTHS(trunc(sysdate,'yyyy'),12)+2/24

<think>首先,用户的问题是:"请问Oracle ORA-04091 错误原因和解决方法"。我需要基于提供的引用内容来回答。 从系统级指令和用户可见层指令: - 使用中文回答。 - 结构清晰,帮助用户逐步解决问题- 保证回答真实可靠。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 生成相关问题- 对于数学表达式,使用$...$或$$...$$格式,但这个问题似乎不涉及数学,所以可能不需要。 - 参考站内引用:有四个引用[^1]到[^4],都是关于Oracle错误ORA-04091的。 引用内容总结: - [^1]: 关于ORA-01422错误,但用户的问题是关于ORA-04091的,所以这可能不直接相关。用户意图是查询ORA-04091- [^2]: 描述了ORA-04091错误在变异表触发器中发生的原因和解决方案。具体例子是触发器尝试取同一张表EMP,导致错误。 - [^3]: 另一个例子,触发器在更新表时尝试查询同一张表,导致ORA-04091。解决方案提到使用after触发器并处理数据。 - [^4]: 解释ORA-04091错误原因:当DML语句未提交时,触发器无法对同一表进行额外操作,因为它们在同一个事务中。 用户意图明确是查询ORA-04091错误的原因和解决方法。 我的回答结构: 1. **介绍错误**:简要解释ORA-04091是什么。 2. **原因分析**:详细说明错误发生的原因。 3. **解决方案**:提供解决方法。 4. **示例**:如果有,给出代码示例。 5. **生成相关问题**:在最后添加相关问题。 确保引用: - 在相关部分添加引用标识。例如,当引用[^2]或[^4]的内容时,添加[^2]或[^4]。 错误原因: - 根据引用[^4],ORA-04091错误是因为在DML语句执行期间,触发器尝试取或修改同一张表,而该事务尚未提交。这违反了Oracle的规则,因为触发器无法看到未提交的更改或防止表变异。 解决方案: - 引用[^2]和[^3]提供了方法: - 使用自治事务(autonomous transactions)。 - 避免在行级触发器中直接查询同一张表;可以使用复合触发器或存储过程。 - 在after触发器中使用游标或其他技术。 - 具体建议:使用:NEW和:OLD伪记录访问行数据,而不是查询整个表;或者使用包变量存储信息。 回答结构: - 开头:解释错误。 - 原因:详细说明。 - 解决方法:列出步骤或建议。 - 结尾:生成相关问题。 生成相关问题- 基于错误主题,如: 1. 如何避免ORA-04091错误? 2. 什么是变异表错误? 3. Oracle触发器的类型和使用场景? 4. 如何调试ORA-04091错误? 使用中文回答。</think>### Oracle ORA-04091 错误原因和解决方法 Oracle 数据库中的 ORA-04091 错误是一个常见的“变异表错误”(mutating table error),它通常发生在触发器(trigger)执行过程中。当触发器尝试取或修改触发事件所作用的同一张表时,Oracle 会抛出此错误,因为事务尚未提交,导致数据状态不一致。下面我将逐步解释原因并提供可靠的解决方案,确保回答基于实际数据库原理和实践。 #### 错误原因 ORA-04091 错误的根本原因是**事务隔离性问题**。在 Oracle 中,当您执行一个 DML 语句(如 INSERT、UPDATE 或 DELETE)时,该操作会触发一个触发器触发器和触发事件属于同一个事务。在事务提交前,表数据处于“未提交”状态。如果触发器内部尝试查询或修改同一张表(称为“变异表”),Oracle 无法保证数据一致性,从而抛出错误。具体原因包括: - **行级触发器冲突**:在 `FOR EACH ROW` 触发器中,如果使用 `SELECT` 语句查询同一张表(例如,统计行数或聚合数据),Oracle 会检测到表正在被修改,导致错误[^2][^4]。 - **事务未提交**:DML 操作未显式提交前,触发器无法安全访问同一表的数据,因为 Oracle一致性机制会阻止对未提交更改的取[^4]。 - **常见场景**:例如,在 `AFTER UPDATE` 触发器中更新表后,立即查询该表以计算总和或计数,就会触发 ORA-04091[^3]。 引用说明:ORA-04091 错误提示类似于“表 SCOTT.EMP 发生了变化, 触发器/函数不能它”,这源于触发器在事务中试图取未提交的数据[^2][^4]。 #### 解决方法 解决 ORA-04091 错误的关键是避免在触发器中直接操作同一张表。以下是已验证的可靠方法(基于引用内容): 1. **使用自治事务(Autonomous Transactions)** 将触发器中需要查询表的代码部分封装为一个自治事务,使其独立于主事务。这样,查询操作不会受未提交更改的影响。 **示例代码**: ```sql CREATE OR REPLACE TRIGGER safe_trigger AFTER UPDATE ON your_table FOR EACH ROW DECLARE PRAGMA AUTONOMOUS_TRANSACTION; -- 声明自治事务 v_count NUMBER; BEGIN SELECT COUNT(*) INTO v_count FROM your_table WHERE condition = :NEW.column; -- 其他操作 COMMIT; -- 自治事务内显式提交 END; ``` **注意**:自治事务需谨慎使用,因为它会增加事务复杂性;确保只在必要时启用[^3][^4]。 2. **避免查询同一表,改用伪记录(:NEW 和 :OLD)** 在行级触发器中,优先使用 `:NEW` 和 `:OLD` 伪记录访问当前行数据,而不是查询整个表。例如,计算总和时,可以存储值到变量中。 **示例代码**: ```sql CREATE OR REPLACE TRIGGER update_trigger AFTER UPDATE ON table_a FOR EACH ROW DECLARE yxtcd NUMBER; BEGIN -- 使用 :NEW 和 :OLD 直接获取值,避免 SELECT 查询 IF :OLD.bid IS NOT NULL THEN yxtcd := :OLD.acd + :NEW.acd; -- 简化计算,避免查询表 UPDATE table_b SET bcd = yxtcd WHERE xtbh = :OLD.bid; END IF; END; ``` 此方法减少表访问,降低错误风险[^3]。 3. **改用复合触发器或存储过程** 如果逻辑复杂,使用 Oracle 11g 及以上版本的复合触发器(compound trigger),或在触发器中调用存储过程。存储过程可以将查询操作移到事务外。 **示例步骤**: - 创建存储过程处理查询逻辑。 -触发器中调用该过程。 这能隔离表操作,防止变异问题[^2][^4]。 4. **优化触发器类型和逻辑** - 使用 `AFTER STATEMENT` 触发器替代 `FOR EACH ROW`:如果不需要逐行处理,`AFTER STATEMENT` 触发器在事务提交后执行,避免冲突。 - 添加提交控制:在 DML 语句后显式添加 `COMMIT`,但需注意事务完整性。 - 调试建议:使用 `DBMS_OUTPUT` 输出变量值,检查触发器逻辑是否在查询前依赖未提交数据[^3][^4]。 **总结**:ORA-04091 错误通常由触发器设计不当引起。优先采用伪记录或自治事务方案,成功率较高。如果问题持续,检查表索引和事务隔离级别。 #### 相关问题 1. 如何避免在 Oracle 触发器中触发 ORA-04091 错误? 2. 什么是 Oracle 变异表错误?它与其他错误如 ORA-01422 有何区别? 3. Oracle 触发器的类型(如行级和语句级)如何选择? 4. 如何调试和优化 Oracle 数据库中的触发器性能? [^1]: oracle ora-01422,返回多行时出现ORA-01422错误的解决方法。 [^2]: oracle变异表触发器ORA-04091错误原因及解决方案。 [^3]: oracle 触发器ORA-04091原因及解决。 [^4]: ORA-04091错误原因与解决方法。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值