SQL Server:两列中的值必须是单调的

时间:2014-03-28 13:15:59

标签: sql sql-server constraints

我需要一个表,其中CHECK将检查值是否以相同的方式排序(是单调的)。例如:

CREATE TABLE test
(
    ID int identity primary key,
    ID_foreignkey int references something,
    order int,
    value int
)

我需要的效果是,对于相同的ID_foreign键,订单会增长并且值也会增长,例如:

(1,1,4), (1,2,5), (1,3,9), (1,4,12), (1,5,13), (1,6,18)

是否可以使用约束轻松完成,或者必须使用某个程序?也许检查插入的值是否大于测试中的select max(value)?

3 个答案:

答案 0 :(得分:1)

你可以创建一个这样的函数来返回预期的下一个值

create FUNCTION [dbo].[test](@id integer, @typeKey integer)
RETURNS int
AS
BEGIN
   DECLARE @retval int
   if @typeKey=1
   begin
       SELECT @retval = max([order]) + 1 FROM table where ID_foreignkey = @id
   end
   else
   begin
       /*next value i don't understand the logic*/
       SELECT @retval = max([value]) + 1 FROM table where ID_foreignkey = @id
   end
   RETURN @retval
END

然后在检查约束

dbo.test(ID_foreignkey, 1) = order

dbo.test(ID_foreignkey, 2) = value

答案 1 :(得分:1)

我认为您应该编写一个FOR INSERT, UPDATE触发器,其验证方式如下:

if exists (
    select 1
    from (
        select ID_foreignkey
            , row_number() over (partition by ID_foreignkey order by [order]) as num_order
            , row_number() over (partition by ID_foreignkey order by value) as num_value
        from test
        where ID_foreignkey in (
            select ID_foreignkey
            from inserted
        )
    ) t
    where num_order <> num_value
)
    raiserror('Error', 16, 1);

答案 2 :(得分:1)

如果我们可以强制order从1开始并且没有间隙,那么我们可以声明性地实现大多数的要求:

CREATE TABLE test
(
    ID int not null identity,
    ID_foreignkey int not null,
    [order] int not null,
    value int not null,
    prev_order as CASE WHEN [order] > 1 THEN [order]-1 END persisted,
    prev_value int null,
    constraint PK_test PRIMARY KEY (ID),
    constraint UQ_test_backref UNIQUE (ID_foreignkey,[order]),
    constraint CK_test_orders CHECK ([order] >= 1),
    constraint CK_test_prevpopulated CHECK ([order]=1 or prev_value is not null),
    constraint FK_test_backref FOREIGN KEY (ID_foreignkey,prev_order)
          references test (ID_foreignkey,[order]),
    --Finally, we can actually write the constraint you wanted
    constraint CK_test_increase_only CHECK (value > prev_value)
)

不幸的是,我们确实需要添加一个触发器,以便在prev_value s 1 期间正确设置INSERT

create trigger T_test on test
instead of insert
as
    set nocount on;
    insert into test (ID_foreignkey,[order],value,prev_value)
    select i.ID_foreignkey,i.[order],i.value,COALESCE(p.value,i2.value)
    from inserted i
        left join
        test p
            on
                i.ID_foreignkey = p.ID_foreignkey and
                i.[order] = p.[order]+1
        left join
        inserted i2
            on
                i.ID_foreignkey = i2.ID_foreignkey and
                i.[order] = i2.[order]+1

现在我们可以做一些示例插入:

insert into test (ID_foreignkey,[order],value) values
(1,1,4), (1,2,5), (1,3,9)
go
insert into test (ID_foreignkey,[order],value) values
(1,4,12), (1,5,13)

现在失败了:

insert into test (ID_foreignkey,[order],value) values
(1,6,12)
  

Msg 547,Level 16,State 0,Procedure T_test,Line 4

     

INSERT语句与CHECK约束“CK_test_increase_only”冲突。冲突发生在数据库“X”,表“dbo.test”。

     

声明已经终止。

只有值得一提的其他事情 - 如果你想稍后调整value,你不需要做很多事情就可以做到这一点,并且仍然要强制执行约束 - 你只需要添加ON UPDATE CASCADEFK_text_backref


1 但请注意,声明性约束会强制 时应填充数据并且必须包含正确的数据。因此,没有把触发器弄错的危险,比如说,并且在中以错误的数据结束。