SQL-Server: Define columns as mutually exclusive - sql-server

joking with a collegue, I came up with an interesting scenario: Is it possible in SQL Server to define a table so that through "standard means" (constraints, etc.) I can ensure that two or more columns are mutually exclusive?
By that I mean: Can I make sure that only one of the columns contains a value?

Yes you can, using a CHECK constraint:
ALTER TABLE YourTable
ADD CONSTRAINT ConstraintName CHECK (col1 is null or col2 is null)
Per your comment, if many columns are exclusive, you could check them like this:
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 +
case when col4 is null then 0 else 1 end
= 1
This says that one of the four columns must contain a value. If they can all be NULL, just check for <= 1.

Related

check if fields of a sqlserver table are empty/full in a computed field

i need to add a computed field in a sqlserver table that shows me if the other fields of the table are empty or full (all, some of them, none)
the field should contain:
0=all the fields are empty(null)
1=almost one field has been filled
2=all fields contains something
What is the most efficent way to achieve it?
Probably the easiest way to to do this is summing up all the null columns using case ...when statements and then comparing that to the total number of columns:
CREATE TABLE [Foo] (
[Col1] int NULL,
[Col2] int NULL,
[Col3] int NULL,
[ColumnDensity] AS CASE
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
WHEN 0 /* No columns are NOT NULL */ THEN 0
WHEN 3 /* All 3 columns are NOT NULL */ THEN 2
ELSE /* Some columns are NULL and some are NOT NULL*/ 1 END
)

Comparing integers in columns with nulls?

I have a table. The table has two integer columns. Nulls are allowed in each column. The following query works
select * from table where col1=1 and col2 = 2
but neither of the following two queries work
select * from table where col1=1 and col2 != 2 or
select * from table where col1=1 and col2 <> = 2
I am aware that the comparison operators are not supposed to work for columns that have null, but '=' is a comparison operator and the first query above works. I do not understand why the first query works and the second query does not? (If you see any typos just ignore them I tested this with real code any mistakes are just when I transcribed it to this question.)
Here are two sql statements that will allow you to create a table and insert data into it for testing the above queries.
CREATE TABLE Test (
ID int,
Col1 int,
Col2 int)
and the insert statements
INSERT INTO test
(id, col1 , col2)
VALUES
(1,1,NULL),
(2,NULL,2),
(3,1,2)
It may help you understand by examining the results of each query predicate individually using the sample data. Note that both conditions must evaluate to TRUE for a row to be returned due to the AND logical operator.
select * from Test where col1=1 and col2 = 2;
VALUES
(1,1,NULL), --col1=1 is TRUE and col2 = 2 is UNKNOWN
(2,NULL,2), --col1=1 is UNKNOWN and col2 = 2 is TRUE
(3,1,2) --col1=1 is TRUE and col2 = 2 is TRUE: row returned because both are TRUE
select * from table where col1=1 and col2 <> 2
VALUES
(1,1,NULL), --col1=1 is TRUE and col2 <> 2 is UNKNOWN
(2,NULL,2), --col1=1 is UNKNOWN and col2 <> 2 is FALSE
(3,1,2) --col1=1 is TRUE and col2 <> 2 is FALSE
If Col 2 is either 2 or null as in your comment then you need to use IS NULL
select * from table where col1=1 and col2 IS NULL
Or convert the null values to some other non-used value
select * from table where col1=1 and ISNULL(col2, 0) != 2
Sounds like those columns should really be bit fields though to me.

TSQL query optimizer view on non-nullable ISNULL()

As part of some dynamic SQL (ick), I've implemented the 'sort NULLs last' solution described here: Sorting null-data last in database query
ORDER BY CASE column WHEN NULL THEN 1 ELSE 0 END, column
My question is: On non-nullable columns that have ISNULL() applied to them, will the query optimizer strip this out when it realises that it will never apply?
It's not clear why your question mentions the ISNULL function when that isn't in your code.
ORDER BY CASE column WHEN NULL THEN 1 ELSE 0 END, column
First of all this code doesn't work, it is equivalent to CASE WHEN column = NULL which is not what you need.
It would need to be
ORDER BY CASE WHEN column IS NULL THEN 1 ELSE 0 END, column
The optimisation question is easy to test.
CREATE TABLE #T
(
X INT NOT NULL PRIMARY KEY
)
SELECT *
FROM #T
ORDER BY X
SELECT *
FROM #T
ORDER BY CASE WHEN X IS NULL THEN 1 ELSE 0 END, X
DROP TABLE #T
The plan shows a sort operation in the second plan indicating that this was not optimised out as you hoped and the pattern is less efficient than ORDER BY X.

Inserting value into target table dervived column from source table value with an IF ELSE statement

I'm trying to import from a staging table(source) to a destination table(dest) in SQL Server. Some of the destination table values are derived from the source table values based on a conditional statement. The logic is somewhat like this
IF source.col1=0
BEGIN
IF source.col2=0
BEGIN
dest.colA = 1
dest.colB = source.col3
END
ELSE
BEGIN
dest.colC= 1
dest.colD= source.col3
END
ELSE IF source.col4>0
BEGIN
dest.colD=1
dest.colE=source.col3
END
ELSE
BEGIN
dest.colF=1
dest.colG=source.col1
END
END
I am currently doing a merge from source table to destination table within a stored procedure and have other columns besides the above that map perfectly. How can i write this logic such that the destination table derived columns are set from source table columns based on the logic above? None of what I have tried so far works or makes sense to me. Thank you in advance for your help
Thank you M. Ali and phiosophicles. I am using this in a merge statement but now I am getting the error Invalid object name 'Source'.
I am not getting this error on any of my other merge statements. Source is below. I have anonymized to a certain extent so please let me know if it isn't clear
MERGE dbo.Destination WITH (HOLDLOCK) AS Target
USING
(
SELECT DISTINCT id, col3,
CAST(LEFT(DATEADD(m,months,CONVERT(date,CONCAT(origination,'/01'))),7) as varchar(20)) as CalendarMonth,
col1, col2,col4 FROM dbo.Staging
) AS Source
ON (Target.ID=Source.id
AND Target.Month=Source.col3)
WHEN MATCHED THEN
UPDATE SET
Target.CalendarMonth=Source.CalendarMonth,
Target.colF= (SELECT CASE WHEN col1>0 THEN 1 END AS colF FROM Source),
Target.colD=(SELECT CASE WHEN col4>0 THEN 1 END AS colD
FROM Source),
Target.colC=(SELECT CASE WHEN col1=0 AND col2=0
THEN 1 END AS colC FROM Source),
Target.colB(SELECT CASE WHEN col1>0 AND col2
THEN 1 END AS colB FROM Source),
Target.colG=(SELECT CASE WHEN col1>0 THEN col1 END AS colG FROM Source),
Target.colE=(SELECT CASE WHEN col4>0 THEN col3 END
AS colE FROM Source),
Target.PaidMonth=(SELECT CASE WHEN col1=0 AND col2=0
THEN col3 END AS PaidMonth
FROM Source),
WHEN NOT MATCHED BY TARGET THEN
INSERT
(Destination Table columns
)
VALUES
( Source values including derived values from above);
END
GO
UPDATE:
I replaced the select statement in the derived columns with CASE WHEN col4>0 THEN col3 END and so on which got rid of the error. Thanks everyone for your help!
T-SQL has two very different language constructs that both relate to "if one thing is true, do this, else that".
The IF/BEGIN/END construct demonstrated in the question is exclusively for flow control in T-SQL; that is, it should be used to wrap around whole blocks of code to control whether they are executed at all or not. You can put any SELECT, INSERT, UPDATE or DELETE query inside the BEGIN/END of an IF construct; you can also put data definition code (for example CREATE TABLE), variable assignment, and many other kinds of statement.
What you are trying to do, on the other hand, is control which column expression should be presented. This has to be done within a query (a SELECT, INSERT or UPDATE), and therefore uses a different T-SQL construct, CASE..END. M.Ali has provided a great example of how to use CASE..END in his answer, so I won't duplicate that. Just notice that each CASE..END block takes the place of a column in the SQL query.
INSERT INTO Destination (ColA, ColB, ColC, ColD, ColF, ColG)
Select CASE WHEN Col1 = 0 AND Col2 = 0 THEN 1 END AS ColA
,CASE WHEN Col1 = 0 AND Col2 = 0 THEN Col3 END AS ColB
,CASE WHEN Col1 = 0 AND Col2 <> 0 THEN 1 END AS ColC
,CASE WHEN Col1 = 0 AND Col2 <> 0 THEN Col3
WHEN Col1 <> 0 AND Col4 > 0 THEN 1 END AS ColD
,CASE WHEN Col1 <> 0 AND Col4 !> 0 THEN 1 END AS ColF
,CASE WHEN Col1 <> 0 AND Col4 !> 0 THEN Col1 END AS ColG
FROM SourceTable

Place a certain row first in ORDER BY

I want to change the order of the records resulted from a select.
I want a certain record to be the first in my list, and the other ones after.
ORDER BY CASE WHEN col = 'something' -- whatever identifies "a certain row"
THEN 1 ELSE 2 END,
col;
You can use CASE in ODRER BY
SELECT Col1, Col2
FROM Table
ORDER BY
CASE WHEN Col1 = #certainrecord THEN 0 ELSE 1 END
, Col1
, Col2

Resources