83

I have a (SQL Server) table that contains 3 types of results: FLOAT, NVARCHAR(30), or DATETIME (3 separate columns). I want to ensure that for any given row, only one column has a result and the other columns are NULL. What is the simplest check constraint to achieve this?

The context for this is trying to retrofit the ability to capture non-numeric results into an existing system. Adding two new columns to the table with a constraint to prevent more than one result per row was the most economical approach, not necessarily the correct one.

Update: Sorry, data type snafu. Sadly I wasn't intending the result types indicated to be interpreted as SQL Server datatypes, just generic terms, fixed now.

Paul White
  • 94,921
  • 30
  • 437
  • 687
David Clarke
  • 1,197
  • 2
  • 10
  • 17

4 Answers4

101

The following should do the trick:

CREATE TABLE MyTable (col1 FLOAT NULL, col2 NVARCHAR(30) NULL, col3 DATETIME NULL);
GO

ALTER TABLE MyTable
ADD CONSTRAINT CheckOnlyOneColumnIsNull
CHECK 
(
    ( CASE WHEN col1 IS NULL THEN 0 ELSE 1 END
    + CASE WHEN col2 IS NULL THEN 0 ELSE 1 END
    + CASE WHEN col3 IS NULL THEN 0 ELSE 1 END
    ) = 1
)
GO
Mr.Brownstone
  • 13,242
  • 4
  • 38
  • 55
Mark Storey-Smith
  • 31,860
  • 9
  • 90
  • 125
27

You'll probably need to do three tests within the constraint, one test for each pair that you want to be null and one for the column that should be not null:

ALTER TABLE table
ADD CONSTRAINT CK_one_is_null
CHECK (
     (col1 IS NOT NULL AND col2 IS NULL AND col3 IS NULL)
  OR (col2 IS NOT NULL AND col1 IS NULL AND col3 IS NULL) 
  OR (col3 IS NOT NULL AND col1 IS NULL AND col2 IS NULL)
);
Nick Chammas
  • 14,810
  • 17
  • 76
  • 124
mrdenny
  • 27,106
  • 2
  • 44
  • 81
6

Here's a PostgreSQL solution using the built-in array functions:

ALTER TABLE your_table
ADD chk_only_one_is_not_null CHECK (array_length(array_remove(ARRAY[col1::text, col2::text, col3::text], NULL), 1) = 1);
David Clarke
  • 1,197
  • 2
  • 10
  • 17
CrEOF
  • 61
  • 1
  • 1
3

FOR POSTGRESQL

CHECK( (col_1 IS NOT NULL)::integer + (col_2 IS NOT NULL)::integer + ... = 1 )

We convert column into boolean with IS NOT NULL (true or false), then cast into ::integer (0 or 1) We can then use arithmetic operators

= 1  //must one row is not null  
<= 1 //only one row can be not null
Mendes
  • 81
  • 7