通过某些组ID将Sum(Column)限制为1

时间:2019-03-25 20:18:27

标签: sql sql-server constraints

我有一张表,我要确保插入的总和等于1(是混合的)。
dbo.Test

我想约束它,所以整个FKID = 2失败,因为它加起来等于1.1。

当前我的约束是

FUNCTION[dbo].[CheckSumTarget](@ID bigint)
RETURNS bit
AS BEGIN 
    DECLARE @Res BIT
    SELECT @Res = Count(1)
    FROM dbo.Test AS t
    WHERE t.FKID = @ID 
    GROUP BY t.FKID
    HAVING Sum([t.Value])<>1    
    RETURN @Res
END
GO
ALTER TABLE dbo.Test  WITH CHECK ADD  CONSTRAINT [CK_Target_Sum] CHECK  (([dbo].[CheckSumTarget]([FKID])<>(1)))

,但是在第一次插入时失败,因为它尚未累加到1。我希望同时添加它们,但事实并非如此。

3 个答案:

答案 0 :(得分:2)

这种方法似乎充满了问题。

我建议另一种方法,从两个表开始:

  • aggregates,因此“ fkid”应为aggregate_id
  • components

然后,在aggregates中,使用触发器累积分量值的sum()。维护计算出的另一个标志:

 alter table aggregates add is_valid as ( sum_value = 1.0 )

然后,在两个表上创建视图以仅显示is_valid = 1处的记录。例如:

create view v_aggregates as
    select c.*
    from aggregates a join
         components c
         on a.aggregate_id = c.aggregate_id
    where a.is_value = 1;

答案 1 :(得分:1)

这是解决方案的有效版本

这是表DDL

create table dbo.test(
    id      int,
    fkid    bigint,
    value   decimal(4,2)
);

函数定义

CREATE FUNCTION[dbo].[CheckSumTarget](@ID bigint)
RETURNS bit
AS BEGIN 
    DECLARE @Res decimal(4,2)
    SELECT @Res = case when sum(value) > 1 then 1 else 0 end 
    FROM dbo.Test AS t
    WHERE t.FKID = @ID  
    RETURN @Res
END

和约束定义

ALTER TABLE dbo.Test  WITH CHECK ADD CONSTRAINT [CK_Target_Sum] CHECK ([dbo].[CheckSumTarget]([FKID]) <> 1)

在您的示例中

insert into dbo.test values (1, 2, 0.5);
insert into dbo.test values (1, 2, 0.4);
-- The following insert will fail, like you expect
insert into dbo.test values (1, 2, 0.2);

注意:此解决方案将被UPDATE语句(如“ Daniel Brughera”指出的)破坏,但这是已知的行为。一种更好的通用方法是使用触发器。您可能想探索一下。

答案 2 :(得分:1)

您的实际方法将以这种方式工作.....

  1. 您插入firts组件,该值必须为1
  2. 您尝试插入第二个组件,因为您的总和已经是1,它会被拒绝
  3. 您将现有组件更新为.85
  4. 您插入下一个组件,值必须为.15
  5. 您回到第2步,使用第三个组件

由于您的约束只处理FKID列,因此有可能,并且您可能认为这是可行的。...

但是....如果您在步骤3中离开了该过程,则您的总和不等于1,并且约束无法预见是否要插入下一个值,甚至更糟的是,您可以更新任何值大于1,它将被接受。

如果将value列添加到约束中,它将阻止这些更新,但是您将永远无法超越步骤1。

我个人不会这样做,但是在这里您可以找到一种方法

  1. 使用Gordon在父表上建议的计算列。使用计算列,您将始终获得实际值,因此,父项将一直有效,直到总和等于1。
  2. 使用此解决方案可防止该值大于1,因此,至少,您将确保任何无效的父级都是因为缺少组件,这可能对您的业务层有所帮助
  3. 正如我在一条评论中提到的,其余逻辑属于业务层和ui层

注意,因为您可以看到函数中未使用id和value参数,但是在创建约束时我需要它们调用它们,这样约束也将验证更新

 CREATE TABLE ttest (id int, fkid int, value float)
     go
     create FUNCTION [dbo].[CheckSumTarget](@id int, @fkid int, @value float)
     RETURNS FLOAT
     AS BEGIN 
         DECLARE @Res float
         SELECT @Res = sum(value)
         FROM dbo.ttest AS t
         WHERE t.FKID = @fkid 
         RETURN @Res
     END
     GO
     ALTER TABLE dbo.ttest  WITH CHECK ADD  CONSTRAINT [CK_Target_Sum] CHECK  (([dbo].[CheckSumTarget](id,[FKID],value)<=(1.0)))