Constraints based on other columns
First of all, Welcome to dba.stackexchange.com and thanks for your post !!
Is it possible to limit what values are allowed in a column based off of other values in the row.
Yes using CHECK CONSTRAINTS as described here
Example :
create table myTable (ID int identity(1,1)
, Test_mode int
, Active int
)
go
-- Active can only be NULL, 1, 0, AND only 1 with Test_mode as 0.
ALTER TABLE myTable WITH CHECK ADD
CONSTRAINT ck_active CHECK (active IS NULL OR active IN (1, 0))
go
-- some test data
insert into myTable (test_mode, Active) values (1, null)
insert into myTable (test_mode, Active) values (0, null)
insert into myTable (test_mode, Active) values (1, 0)
insert into myTable (test_mode, Active) values (0, 1)
insert into myTable (test_mode, Active) values (1, 1)
select * from myTable
-- Is there a way to either change the value of Test_mode to 0 if a 1 is inserted into Active
update myTable
set Test_mode = case when Active = 1 then 0
else Test_mode
end
where Active = 1
If Test_mode
is 1 not allow insertion/update of Active --OR--
Throw some kind of error if Test_mode is 1 and an insertion/update of Active is attempted.
Use TRY/CATCH as described here
The first line of defence to guard against invalid data getting into your tables is the data types of the columns.
If a process tries to insert or update a column to a value that's out of the data type's range (or NULL
if the column does not allow NULL
s), the operation will fail immediately without you needing to do any extra work.
Data type selection is one of the most important aspects of table design.
So, since you didn't post a schema, I'm going to construct one based on the information you've provided:
CREATE TABLE [dbo].[Tests]
(
ID int IDENTITY(1, 1) PRIMARY KEY,
Test_mode bit NOT NULL, /* Based on only seeing 0/1. Maybe tinyint? */
Active bit NULL
);
Based on this design, the available combinations are already limited to the following:
Test_mode Active 0 NULL 0 0 0 1 1 NULL 1 0 1 1
Anything other than that will cause an error to be raised. (Which is a good thing.)
Is there a way to either change the value of Test_mode to 0 if a 1 is inserted into Active
OR
If Test_mode is 1 not allow insertion/update of Active
OR
Throw some kind of error if Test_mode is 1 and an insertion/update of Active is attempted.
Active can only be NULL, 1, 0, AND only 1 with Test_mode as 0.
You've given 4 different ways to arrive at the allowed combination of values (well, sort of). These are very different strategies, with very different implementation behaviours.
I prefer to use what are called declarative constraints. In other words, the table schema and its associated objects limit the allowed values by explicitly declaring what is allowed (or sometimes, what is not allowed). In fact, the column data types themselves are a type of declarative constraint. The closer to the table data the values can be restricted, the easier and more reliably they can be restricted. (In contrast, a non-declarative or active constraint would be implemented by writing a piece of T-SQL, usually either a table trigger, or part of a stored procedure.)
The first 3 of the options can only be implemented by non-declarative means. The last one, however, is declarative, so let's focus on that:
Active
can only be NULL, 1, 0, AND only 1 withTest_mode
as 0.
This defines what you actually want, which is the allowed combinations of values in the table. Note that the valid combinations are only dependent on column values within the same row. This is important, because it determines which mechanism(s) can be used to implement the constraint.
In this case, we can use a CHECK
constraint, which is a true/false test that determines whether a row is valid or invalid based on a row's column values1. If the test fails, the operation that attempted to change the row will fail with an error.
ALTER TABLE [dbo].[Tests] WITH CHECK
ADD CONSTRAINT CC_Tests_TestMode_Active
CHECK ((Test_mode != 0) OR ((Active IS NOT NULL) AND (Active = 1)));
You'll note that I've constructed the predicate such that it will continue to work even if Test_mode
is actually a (non-nullable) integer type. The IS NOT NULL
part is required because CHECK
constraints permit rows where the predicate evaluates to undefined
.
1 They can be used to do checking outside of the current row, but this is a bad practice, and I'm not going to get into that here. Use a trigger instead.
CREATE UNIQUE INDEX [UNQ_IndexName]
ON [dbo].[Table]([Column])
WHERE ([Status] in( 'A','D' ) );