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 INSERT
s1:
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.