我正在使用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
)
我想执行以下规则:
这有意义吗?
我试图写一个处理这个的存储过程:
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;
答案 0 :(得分:1)
您获得的错误意味着您无法在行级触发器本身内查询触发器所在的表。解决此问题的一种方法是使用3个触发器的组合。
触发器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上创建索引。