Ограничить сумму (столбец) до 1 некоторым идентификатором группы - PullRequest
1 голос
/ 25 марта 2019

У меня есть таблица, в которой я пытаюсь убедиться, что совокупная сумма вставок составляет 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 ]

2 голосов
/ 25 марта 2019

Такой подход кажется чреватым проблемами.

Я бы предложил другой подход, начиная с двух таблиц:

  • 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 голос
/ 26 марта 2019

Ваш фактический подход будет работать таким образом .....

  1. Вы вставляете первый компонент, значение должно быть 1
  2. Вы пытаетесь вставить второй компонент, он будет отклонен, поскольку ваша сумма уже равна 1
  3. Вы обновляете существующий компонент до .85
  4. Вы вставляете следующий компонент, значение должно быть .15
  5. Вы вернетесь к шагу 2. с третьим компонентом

Поскольку ваше ограничение заботится только о столбце FKID, это будет возможно, и вы можете подумать, что это работает ....

Но .... если вы вышли из процесса на шаге 3. ваша сумма не равна 1 и ограничение не может быть предвидено, если вы введете следующее значение или нет, даже в худшем случае, вы можете обновить любое значение быть больше 1, и оно будет принято.

Если вы добавите столбец значения к своему ограничению, это предотвратит эти обновления, но вы никогда не сможете выйти за пределы шага 1.

Лично я бы этого не делал, но здесь вы можете получить подход

  1. Используйте вычисленный столбец, предложенный Гордоном, в вашей родительской таблице. С вычисленными столбцами вы всегда получите фактическое значение, поэтому родительский элемент не будет действительным до тех пор, пока сумма не станет равной одному
  2. Используйте это решение, чтобы значение не превышало 1, поэтому, по крайней мере, вы будете уверены, что любой недопустимый родитель является причиной отсутствия компонента, что может быть полезно для вашего бизнес-уровня
  3. Как я уже упоминал в одном комментарии, остальная часть логики принадлежит бизнесу и пользовательскому интерфейсу

Примечание , поскольку вы можете видеть, что параметры id и значения не используются в функции, но мне нужно, чтобы они вызывались при создании ограничения, таким образом, ограничение также будет проверять обновления

 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)))
1 голос
/ 26 марта 2019

Вот рабочая версия решения

Вот таблица 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'), однако это известное поведение.Лучшим и распространенным подходом является использование триггера.Вы можете изучить это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...