在行更新之前触发器中的Oracle 10g和数据验证

时间:2013-12-03 05:51:19

标签: sql oracle triggers oracle10g

我正在使用Oracle 10g,我有下表:

create table DE_TRANSFORM_MAP
(
  DE_TRANSFORM_MAP_ID NUMBER(10) not null,
  CLIENT              NUMBER(5) not null,
  USE_CASE            NUMBER(38) not null,
  DE_TRANSFORM_NAME   VARCHAR2(100) not null,
  IS_ACTIVE           NUMBER(1) not null
)

映射到下表中的条目:

create table DE_TRANSFORM
(
  DE_TRANSFORM_ID NUMBER(10) not null,
  NAME            VARCHAR2(100) not null,
  IS_ACTIVE       NUMBER(1) not null
)

我想执行以下规则:

  • DE_TRANSFORM_MAP中只有一行具有相同的CLIENT和USE_CASE,可以随时将IS_ACTIVE设置为1
  • DE_TRANSFORM中只有一行,同时NAME和IS_ACTIVE设置为1
  • 如果DE_TRANSFORM_MAP中的任何行的DE_TRANSFORM_NAME等于NAME且IS_ACTIVE设置为1,则DE_TRANSFORM中的行不能将IS_ACTIVE从1更改为0

这有意义吗?

我试图写一个处理这个的存储过程:

create or replace trigger DETRANSFORMMAP_VALID_TRIG
after insert or update on SERAPH.DE_TRANSFORM_MAP
for each row
declare
   active_rows_count NUMBER;
begin
   select count(*) into active_rows_count from de_transform_map where client = :new.client and use_case = :new.use_case and is_active = 1;
   if :new.is_active = 1 and active_rows_count > 0 then
      RAISE_APPLICATION_ERROR(-20000, 'Only one row with the specified client, use_case, policy_id and policy_level may be active');
   end if;
end;

当我执行以下操作时,它按预期工作,我收到错误:

insert into de_transform_map (de_transform_map_id, client, use_case, de_transform_name, is_active) values (detransformmap_id_seq.nextval, 6, 0, 'TEST', 1);
insert into de_transform_map (de_transform_map_id, client, use_case, de_transform_name, is_active) values (detransformmap_id_seq.nextval, 6, 1, 'TEST', 1);

但如果我这样做:

update de_transform_map set use_case = 0 where use_case = 1

我得到以下内容:

ORA-04091: table DE_TRANSFORM_MAP is mutating, trigger/function may not see it

如何完成验证?

编辑:我认为Rene的答案是正确的,因为我认为最正确和优雅的方法是使用复合触发器,但我们的生产DB仍然只有10g,我们将在明年初更新到11g并且我将重写然后触发。在那之前,我有一个全局触发器,断言没有行重复,这里是:

create or replace trigger DETRANSFORMMAP_VALID_TRIG
after insert or update on DE_TRANSFORM_MAP
declare
   duplicate_rows_exist NUMBER;
begin
   select 1 into duplicate_rows_exist from dual where exists (
     select client, use_case, count(*) from de_transform_map where is_active = 1
      group by client, use_case
       having count(*) > 1
   );
   if duplicate_rows_exist = 1 then
      RAISE_APPLICATION_ERROR(-20000, 'Only one row with the specified client, use_case may be active');
   end if;
end;

3 个答案:

答案 0 :(得分:1)

您获得的错误意味着您无法在行级触发器本身内查询触发器所在的表。解决此问题的一种方法是使用3个触发器的组合。

  • a)前声明级别触发器
  • b)行级触发器
  • c)后句子级别触发器

触发器A初始化包中的集合

触发器B将每个更改的行添加到集合

触发器C对集合中的每个条目执行所需的操作。

此处有更多详情: http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936

Oracle 11G的一项改进是您可以在一个复合触发器中执行所有这些操作。更多信息: http://www.oracle-base.com/articles/11g/trigger-enhancements-11gr1.php

答案 1 :(得分:0)

你或许应该考虑做一件“插入之前”的事情!我现在只有一个MSSQL引擎可以使用,但希望下面的内容可能会帮助你...我不确定你的错误示例是什么意思,但是,因为它似乎与您发布的第一个用例相矛盾...无论哪种方式,触发器在并发写入期间都可能是一个真正的痛苦,因此您只需要小心从后端进行此类业务逻辑验证。 / p>

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'DE_TRANSFORM_MAP'
                    AND type = 'U' )
BEGIN
    --DROP TABLE DE_TRANSFORM_MAP;
    CREATE TABLE DE_TRANSFORM_MAP
    (
        DE_TRANSFORM_MAP_ID NUMERIC(10) NOT NULL, 
                                PRIMARY KEY ( DE_TRANSFORM_MAP_ID ),
        CLIENT              NUMERIC( 5 ) NOT NULL,
        USE_CASE            NUMERIC( 38 ) NOT NULL,
        DE_TRANSFORM_NAME   NVARCHAR( 100 ) NOT NULL,
        IS_ACTIVE           TINYINT NOT NULL
    );
END;

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'DE_TRANSFORM'
                    AND type = 'U' )
BEGIN
    --DROP TABLE DE_TRANSFORM;
    CREATE TABLE DE_TRANSFORM
    (
        DE_TRANSFORM_ID     NUMERIC( 10 ) NOT NULL, 
                            PRIMARY KEY ( DE_TRANSFORM_ID ),
        NAME                NVARCHAR( 100 ) NOT NULL,
        IS_ACTIVE           TINYINT NOT NULL                                        
    );
END;
GO

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'DETRANSFORMMAP_VALID_TRIG'
                    AND type = 'TR' )
BEGIN
    --DROP TRIGGER DETRANSFORMMAP_VALID_TRIG;
    EXEC( '
        CREATE TRIGGER DETRANSFORMMAP_VALID_TRIG 
            ON DE_TRANSFORM_MAP INSTEAD OF INSERT, UPDATE
        AS SET NOCOUNT OFF;' );
END;
GO

ALTER TRIGGER DETRANSFORMMAP_VALID_TRIG 
    ON DE_TRANSFORM_MAP INSTEAD OF INSERT, UPDATE
AS BEGIN
    SET NOCOUNT ON;

    IF ( (  SELECT  MAX( IS_ACTIVE )
            FROM (  SELECT  IS_ACTIVE = SUM( IS_ACTIVE )
                    FROM (  SELECT  CLIENT, USE_CASE, IS_ACTIVE
                            FROM    DE_TRANSFORM_MAP
                            EXCEPT
                            SELECT  CLIENT, USE_CASE, IS_ACTIVE
                            FROM    DELETED
                            UNION ALL
                            SELECT  CLIENT, USE_CASE, IS_ACTIVE
                            FROM    INSERTED ) f
                    GROUP BY CLIENT, USE_CASE ) mf ) > 1 )
    BEGIN
        RAISERROR( 'DE_TRANSFORM_MAP: CLIENT & USE_CASE cannot have multiple actives', 16, 1 );
    END ELSE BEGIN
        DELETE  DE_TRANSFORM_MAP
        WHERE   DE_TRANSFORM_MAP_ID IN (    SELECT  DE_TRANSFORM_MAP_ID
                                            FROM    DELETED );

        INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID, 
            CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
        SELECT  DE_TRANSFORM_MAP_ID, CLIENT, USE_CASE, 
                DE_TRANSFORM_NAME, IS_ACTIVE
        FROM    INSERTED;
    END;

    SET NOCOUNT OFF;
END;
GO

INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID, 
    CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
VALUES ( 1, 6, 0, 'TEST', 1 );

INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID, 
    CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
VALUES ( 2, 6, 1, 'TEST', 1 );
GO

SELECT  *
FROM    dbo.DE_TRANSFORM_MAP;
GO

TRUNCATE TABLE DE_TRANSFORM_MAP;
GO

INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID, 
    CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
            SELECT  1, 6, 0, 'TEST', 1
UNION ALL   SELECT  2, 6, 1, 'TEST', 1
UNION ALL   SELECT  3, 6, 1, 'TEST2', 1;
GO

SELECT  *
FROM    dbo.DE_TRANSFORM_MAP;
GO

TRUNCATE TABLE DE_TRANSFORM_MAP;
GO

INSERT INTO DE_TRANSFORM_MAP ( DE_TRANSFORM_MAP_ID, 
    CLIENT, USE_CASE, DE_TRANSFORM_NAME, IS_ACTIVE )
            SELECT  1, 6, 0, 'TEST', 1
UNION ALL   SELECT  2, 6, 1, 'TEST', 0
UNION ALL   SELECT  3, 6, 1, 'TEST2', 1;
GO

SELECT  *
FROM    dbo.DE_TRANSFORM_MAP;
GO

UPDATE  dbo.DE_TRANSFORM_MAP
    SET IS_ACTIVE = 1
WHERE   DE_TRANSFORM_MAP_ID = 2;
GO

IF NOT EXISTS ( SELECT  1
                FROM    sys.objects
                WHERE   name = 'DETRANSFORM_VALID_TRIG'
                    AND type = 'TR' )
BEGIN
    --DROP TRIGGER DETRANSFORM_VALID_TRIG;
    EXEC( '
        CREATE TRIGGER DETRANSFORM_VALID_TRIG 
            ON DE_TRANSFORM INSTEAD OF INSERT, UPDATE
        AS SET NOCOUNT OFF;' );
END;
GO

ALTER TRIGGER DETRANSFORM_VALID_TRIG 
    ON DE_TRANSFORM INSTEAD OF INSERT, UPDATE
AS BEGIN
    SET NOCOUNT ON;

    IF ( (  SELECT  MAX( IS_ACTIVE )
            FROM (  SELECT  IS_ACTIVE = SUM( IS_ACTIVE )
                    FROM (  SELECT  NAME, IS_ACTIVE
                            FROM    DE_TRANSFORM
                            EXCEPT
                            SELECT  NAME, IS_ACTIVE
                            FROM    DELETED
                            UNION ALL
                            SELECT  NAME, IS_ACTIVE
                            FROM    INSERTED ) f
                    GROUP BY NAME ) mf ) > 1 )
    BEGIN
        RAISERROR( 'DE_TRANSFORM: NAME cannot have multiple actives', 16, 1 );
    END ELSE IF EXISTS (SELECT  1
                        FROM    DE_TRANSFORM_MAP
                        WHERE   IS_ACTIVE = 1
                            AND DE_TRANSFORM_NAME IN (  SELECT  NAME
                                                        FROM    DELETED
                                                        UNION ALL
                                                        SELECT  NAME
                                                        FROM    INSERTED
                                                        WHERE   IS_ACTIVE = 0 ) )
    BEGIN
        RAISERROR( 'DE_TRANSFORM: NAME is active in DE_TRANSFORM_MAP', 16, 1 );
    END ELSE BEGIN

        DELETE  DE_TRANSFORM
        WHERE   DE_TRANSFORM_ID IN (SELECT  DE_TRANSFORM_ID
                                    FROM    DELETED );

        INSERT INTO DE_TRANSFORM ( DE_TRANSFORM_ID, NAME, IS_ACTIVE )
        SELECT  DE_TRANSFORM_ID, NAME, IS_ACTIVE
        FROM    INSERTED;
    END;

    SET NOCOUNT OFF;
END;
GO

INSERT INTO DE_TRANSFORM ( DE_TRANSFORM_ID, NAME, IS_ACTIVE )
VALUES( 1, 'TEST2', 0 );
GO

SELECT  *
FROM    DE_TRANSFORM;
GO

TRUNCATE TABLE DE_TRANSFORM;
GO

TRUNCATE TABLE DE_TRANSFORM_MAP;
GO

答案 2 :(得分:0)

如果在表中“始终”验证了触发条件,并且如果DE_TRANSFORM_MAP是一个小表,或者插入/更新语句影响DE_TRANSFORM_MAP中的许多行,那么您可以使用如下语句触发器:

CREATE OR REPLACE TRIGGER DETRANSFORMMAP_VALID_TRIG
AFTER INSERT OR UPDATE ON DE_TRANSFORM_MAP
DECLARE
    EXISTS_ROWS NUMBER;
BEGIN
    SELECT 1 INTO EXISTS_ROWS FROM DUAL WHERE EXISTS(
        SELECT CLIENT
        FROM DE_TRANSFORM_MAP
        WHERE IS_ACTIVE = 1
        GROUP BY CLIENT, USE_CASE
        HAVING COUNT(*) > 1
    );
    IF (EXISTS_ROW = 1) THEN
        RAISE_APPLICATION_ERROR(-20000, 'Only one row with the specified client, use_case, policy_id and policy_level may be active');
    END IF;
END;
/

如果在表中“并不总是”验证触发条件,并且DE_TRANSFORM_MAP是一个大表,或者插入/更新语句影响DE_TRANSFORM_MAP中的几行,则在Rene的答案后重新设计触发器。类似的东西:

CREATE GLOBAL TEMPORARY TABLE DE_TRANSFORM_MAP_AUX AS 
    SELECT CLIENT, USE_CASE FROM DE_TRANSFORM_MAP WHERE 1 = 0;
/

CREATE OR REPLACE TRIGGER DETRANSFORMMAP_VALID_TRIG1 
    BEFORE INSERT OR UPDATE ON SERAPH.DE_TRANSFORM_MAP
BEGIN
    DELETE FROM DE_TRANSFORM_MAP_AUX;
END;
/

CREATE OR REPLACE TRIGGER DETRANSFORMMAP_VALID_TRIG2 
    BEFORE INSERT OR UPDATE ON DE_TRANSFORM_MAP 
    FOR EACH ROW WHEN NEW.IS_ACTIVE = 1
BEGIN
    INSERT INTO DE_TRANSFORM_MAP_AUX VALUES(:NEW.CLIENT, :NEW.USE_CASE);
END;
/

CREATE OR REPLACE TRIGGER DETRANSFORMMAP_VALID_TRIG3 
    AFTER INSERT OR UPDATE ON DE_TRANSFORM_MAP
DECLARE
    EXISTS_ROW NUMBER;
BEGIN
    SELECT 1 INTO EXISTS_ROWS FROM DUAL WHERE EXISTS(
        SELECT CLIENT
        FROM DE_TRANSFORM_MAP
        WHERE IS_ACTIVE = 1 AND 
          (CLIENT, USE_CASE) IN (SELECT CLIENT, USE_CASE FROM DE_TRANSFORM_MAP_AUX)
        GROUP BY CLIENT, USE_CASE
        HAVING COUNT(*) > 1
    );
    DELETE FROM DE_TRANSFORM_MAP_AUX;
    IF (EXISTS_ROW = 1) THEN
        RAISE_APPLICATION_ERROR(-20000, 'Only one row with the specified client, use_case, policy_id and policy_level may be active');
    END IF; 
END;
/

如果DE_TRANSFORM_MAP不存在,您必须考虑在CLIENT和USE_CASE上创建索引。