簡體   English   中英

行更新前觸發器中的Oracle 10g和數據驗證

[英]Oracle 10g and data validation in a trigger before row update

我正在使用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中只有相同名稱和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的答案標記為正確,因為我認為最正確,最優雅的方法是使用復合觸發器,但是我們的生產數據庫仍然只有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個觸發器的組合。

  • 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

您也許應該考慮做“插入之前”的事情! 我現在只有一個MSSQL引擎可以使用,但是希望下面的內容對您有所幫助...我不確定您對一個有效的錯誤示例的含義,但是看起來與您發布的第一個用例相矛盾...無論哪種方式,在並發寫入過程中觸發器都可能是一個真正的難題,因此您將需要謹慎進行僅從后端進行的這種業務邏輯驗證。

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

如果觸發器條件已在表中“始終”驗證,並且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上創建索引。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM