Question

I need a table where CHECK will check if values are ordered in the same way (are monotonic). For example:

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

I need effect that for the same ID_foreign key, order grows and also value grows, for example:

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

Is it possible to do it easy using constraint, or a procedure must be used? Maybe check if inserted value is bigger than select max(value) from test?

Was it helpful?

Solution

If we can force order to start at 1 and to have no gaps, then we can achieve most of the requirements declaratively:

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)
)

Unfortunately, we do need to add a trigger so that prev_value is correctly set during INSERTs1:

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

And now we can do some sample inserts:

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)

And now one that fails:

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

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

The INSERT statement conflicted with the CHECK constraint "CK_test_increase_only". The conflict occurred in database "X", table "dbo.test".

The statement has been terminated.

Only other thing worth mentioning - if you want to allow value to be adjusted later, you don't need to do a lot to allow that and for the constraint to still be enforced - you just have to add ON UPDATE CASCADE to FK_text_backref.


1But note that the declarative constraints do force when the data should be populated and that it must contain the correct data. So there's no danger of getting the trigger wrong, say, and ending up with the wrong data in the table.

OTHER TIPS

I think you should write a FOR INSERT, UPDATE trigger that has a validation like this:

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);

you can create a function like this that return the next value expected

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

then in check constraint

dbo.test(ID_foreignkey, 1) = order

and

dbo.test(ID_foreignkey, 2) = value
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top