触发插入以检查总和

时间:2015-01-11 18:55:43

标签: sql sql-server triggers sum

我试图创建一个触发器来检查三种不同木材类型的插入总和在一个特定的工作区域中应该总和为10(10代表100%)'在一个名为SpaceMixes的表中。 '百分比'是木材类型的百分比存储在特定工作空间中的列(木材类型命名为' H',' P'或' F')。

这些工作空间由它们的组合主键(AreaNr,SpaceNr)定义,即使SpaceMixes的整个PK是(AreaNr,SpaceNr,WoodType)。下面的触发器检查总和在一个工作空间中只能是10,但问题是该代码的第一个值也必须是10。但我希望能够插入类似" 2 + 5 + 3"对于工作空间,它不应该像2 + 5 + 5那样允许,因为这将超过该工作空间的总和10(参见期望的结果)。

我使用的当前触发器:

ALTER TRIGGER trg_Sum ON SpaceMixes
FOR INSERT AS

DECLARE @sum INT

SELECT @sum = sum(SpaceMixes.Percentage)
FROM inserted, SpaceMixes
WHERE inserted.AreaNr = SpaceMixes.AreaNr AND inserted.SpaceNr = SpaceMixes.SpaceNr
GROUP BY inserted.AreaNr

IF NOT (@sum = 10)

BEGIN
RAISERROR ('The sum of the percentages must be 10 for each work space!',16, 1)
ROLLBACK TRANSACTION
END

期望的结果: 下面的前两个插入应该可以正常工作,但我希望触发器在第三个上激活,因为百分比' 5'将超过工作空间(2,3)的10(5 + 3 + 5 = 13)之和。换句话说,如果给定的百分比是2而不是5,则只允许第三个插入。

INSERT INTO SpaceMixes VALUES (2, 3, 'P', 5)
INSERT INTO SpaceMixes VALUES (2, 3, 'F', 3)
INSERT INTO SpaceMixes VALUES (2, 3, 'H', 5)

有人知道如何修复此触发器以使此总和有效吗?

2 个答案:

答案 0 :(得分:2)

首先,您应该在CHECK表中包含SpaceMixes constaint,以确保没有单个记录超过最大百分比值:

ALTER TABLE SpaceMixes ADD CONSTRAINT CHK_Percentage CHECK  ( [Percentage] <=10 )

此触发器:

CREATE TRIGGER [dbo].[trg_Sums] ON [dbo].[SpaceMixes]
   AFTER UPDATE, INSERT
AS  

-- First check Work spaces having only two records (not yet complete).
-- These should have a percentage not greater than 10, allowing for the insertion of
-- the last work space member.
IF EXISTS (
    SELECT TOP 1 NULL
    FROM (
       SELECT SUM(Percentage) AS TotalPercentage
       FROM SpaceMixes
       WHERE AreaNr = (SELECT AreaNr FROM inserted) AND 
             SpaceNr = (SELECT SpaceNr FROM inserted) 
       GROUP BY AreaNr, SpaceNr
       HAVING COUNT(*) = 2) t
   WHERE t.TotalPercentage > 10 
)
BEGIN
    RAISERROR ('The sum of the percentages cannot be greater than 10 for any sub-workspace!',16, 1)
    ROLLBACK TRANSACTION
END

-- Now check Work spaces having exactly three records (i.e. complete workspace).  
-- These should have a total percentage equal to 10.
IF EXISTS (
   SELECT TOP 1 NULL
   FROM (SELECT SUM(Percentage) AS TotalPercentage
         FROM SpaceMixes
         WHERE AreaNr = (SELECT AreaNr FROM inserted) AND 
               SpaceNr = (SELECT SpaceNr FROM inserted) 
         GROUP BY AreaNr, SpaceNr
         HAVING COUNT(*) = 3) t
   WHERE t.TotalPercentage <> 10    
)
BEGIN
    RAISERROR ('The sum of the percentages must be 10 for each work space!',16, 1)
    ROLLBACK TRANSACTION
END

应该做的工作。请注意,除了INSERT之外,您还应为UPDATE定义触发器。

插入这两条记录后:

INSERT INTO SpaceMixes VALUES (2, 3, 'P', 5)
INSERT INTO SpaceMixes VALUES (2, 3, 'F', 3)

以下INSERTUPDATE个查询都会引发错误:

INSERT INTO SpaceMixes VALUES (2, 3, 'H', 5) -- SUM = 13
INSERT INTO SpaceMixes VALUES (2, 3, 'H', 1) -- SUM = 9

-- UPDATE SpaceMixes after inserting a percentage of 2 for WOOD TYPE = 'H'
UPDATE SpaceMixes
SET Percentage = 6
WHERE AreaNr = 2 AND SpaceNr =3 And WoodType = 'F'

答案 1 :(得分:0)

通常,如果可能的话,它是一个很好的数据建模规则,可以避免在同一个表的不同行之间传播数据完整性要求。如您所见,它会产生强制执行约束的问题。

这是您可能需要考虑的权衡。您可以在同一行上拥有所有三个值:

create table SpaceMixes(
  AreaNr    int not null,
  SpaceNr   int not null,
  P_Wood    int,
  F_Wood    int,
  H_Wood    int,
  ...
  constraint PK_SpaceMixes primary key( AreaNr, SpaceNr ),
  constraint CK_Exactly10Required check( IsNull( P_Wood + F_Wood + H_Wood, 10 ) = 10 ),
  constraint CK_Total10Exceeded check( IsNull( P_Wood, 0 ) + IsNull( F_Wood, 0 ) + IsNull( H_Wood, 0 ) <= 10 ),
);

优点是你想要的大部分内容都可以通过正常的检查约束来实现。 CK_Exactly10Required约束将强制执行以下规则:如果所有三个字段都有值,则总数必须等于10. CK_Total10Exceeded约束将强制执行以下规则:无论定义了多少个值,总数不能超过10.您可以添加其他约束(例如,没有值可以是否定的,以根据需要改进检查。无论如何都不需要触发器 - 无论如何。

权衡是您必须添加新字段而不是新行才能处理第四种类型的木材。这有多重要取决于发生这种情况的可能性。

编辑:即使您不想或不能反规范化,我想我也可以提交自己的触发器供您考虑。您并不需要单独的检查约束来验证每个值是否为10或更小,因为触发器也将捕获该条件。我建议使用检查约束来捕获负值。这也可以放在触发器中,但检查应该尽可能低。

开启触发器。这适用于插入更新。不需要删除触发器,因为没有删除操作最终会导致无效金额。正如您在查询中看到的那样,它只是验证三个条目的总数必须是10,否则它必须是10或更少。只需检查是否有任何偏差,如果找到则会引发错误。

create trigger SpaceMixes_IU
on SpaceMixes
after insert, update as
declare
    @Result int;

with
KeyList as(
    select  distinct AreaNr, SpaceNr
    from    inserted
)
select  @Result = case when Count( * ) = 3
            then case Sum( sm.Percentage ) when 10 then 0 else 1 end
        else case when Sum( sm.Percentage ) <= 10 then 0 else 1 end
        end
from    SpaceMixes  sm
join    KeyList     kl
    on  kl.AreaNr   = sm.AreaNr
    and kl.SpaceNr  = sm.SpaceNr
group by sm.AreaNr, sm.SpaceNr;

if @Result > 0 begin
    Rollback;
    RaisError( 'Workspace percentages not valid!', 16, 0 );
end;