Can CHECK constraints act like if else? - sql

I have a table with 4 columns:
(ID (PK, int, NOT NULL), col1 (NULL), col2 (NULL), col3 (NULL))
I'd like to add a CHECK constraint (table-level I think?) so that:
if col1 OR col2 are NOT NULL then col3 must be NULL
and
if col3 is NOT NULL then col1 AND col2 must be NULL
i.e. col3 should be null if col1 and col2 are not null or vice-versa
I am very new to SQL and SQL server though and am not sure how to actually implement this or even if it can/should be implemented?
I think maybe:
CHECK ( (col1 NOT NULL OR col2 NOT NULL AND col3 NULL) OR
(col3 NOT NULL AND col1 NULL AND col2 NULL) )
But I am not sure if the brackets can be used to group the logic like this? If not, how can this best be implemented?

Absolutely, you can do this. See this sqlfiddle.
However, you need to make sure you bracket your logic properly. You should never mix ANDs and ORs in the same bracketing scope. So:
(col1 NOT NULL OR col2 NOT NULL AND col3 NULL)
Needs to become:
((col1 NOT NULL OR col2 NOT NULL) AND col3 NULL)
Or:
(col1 NOT NULL OR (col2 NOT NULL AND col3 NULL))
Depending on your intent.

Just be careful not to make mistake with brackets.
CREATE TABLE Test1 (col1 INT, col2 INT, col3 INT);
ALTER TABLE Test1
ADD CONSTRAINT CHK1
CHECK (((col1 IS NOT NULL OR col2 IS NOT NULL) AND col3 IS NULL) OR
((col1 IS NULL AND col2 IS NULL) AND col3 IS NOT NULL))
INSERT INTO Test1 VALUES (1,1,1); --fail
INSERT INTO Test1 VALUES (1,1,NULL); --good
INSERT INTO Test1 VALUES (1,NULL,NULL); --good
INSERT INTO Test1 VALUES (1,NULL,1); --fail
INSERT INTO Test1 VALUES (NULL,NULL,1); --good

I would say create a UDF like below
create FUNCTION dbo.fn_check_val
(#col1 int , #col2 int , #col3 int)
RETURNS bit
AS
BEGIN
declare #toRet bit
IF(#col1 is Not null OR #col2 is NOT NULL)
Begin
if(#col3 is null)
Begin
Set #toRet = 1
End
Else
Begin
Set #toRet = 0
End
End
Else
if(#col3 is not null)
Begin
Set #toRet = 1
End
Else
Begin
Set #toRet = 0
End
return #toRet
END
and then add following check statement in your table
([dbo].[fn_check_val]([col1],[col2],[col3])=(1))

Related

Copy rows that has atleast one null value in any column in SQL Server

I have table_1 that has certain columns. I want to copy that data to table_2 that has atleast one null value in any column of table_1. For e.g. table_1 has three columns. Now, I want to copy those rows to table_2 that has atleast one null value in any of the three columns of table_1.
I tried it using following query:
insert into table_2 (col1, col2, col3)
select col1, col2, col3
from table_1
where col1 is null or col2 is null or col3 is null
But, there is an issue that table_2 has a column 'error_value' which should contain data that indicates which column(s) has NULL value corresponding to that particular row like it should mention 'col2 is null' if col2 has missing value in that row. If more than one column has NULL values, then it should mention about all those columns in 'error_value' column like 'col1, col2, col3' is null if all columns have missing values.
Any suggestions or help how can I implement it.
Use CASE expressions to get the null columns, then concatenate. As of SQL Server 2017 this can best be achieved with CONCAT_WS (https://learn.microsoft.com/de-de/sql/t-sql/functions/concat-ws-transact-sql?view=sql-server-ver15).
insert into table_2 (col1, col2, col3, error_value)
select
col1, col2, col3,
concat_ws(', ',
case when col1 is null then 'col1' end,
case when col2 is null then 'col2' end,
case when col3 is null then 'col3' end
) + ' is null'
from table_1
where col1 is null or col2 is null or col3 is null;
Update
2017 version introduced concat_ws which can simplify the code significantly - check out Thorsten Kettner's answer for details.
I'm leaving this answer here in the hope it will help some other reader that uses an older version of SQL Server.
First version
One simple solution would be to use a combination of case, concat, stuff, and the string concatenation operator (+), taking advantage of the fact that concat will implicitly convert null to empty strings, while + will not.
First, create and populate sample table (Please save us this step in your future questions):
create table table_1 (col1 int, col2 int, col3 int);
create table table_2 (col1 int, col2 int, col3 int, error_value varchar(100));
insert into table_1(col1, col2, col3) VALUES
(null, null, null),
(null, null, 1),
(null, 1, null),
(null, 1, 1),
(1, null, null),
(1, null, 1),
(1, 1, null),
(1, 1, 1);
Then, the insert...select statement:
insert into table_2 (col1, col2, col3, error_value)
select
col1, col2, col3, stuff(
concat(
',' + case when col1 is null then 'col1' end, -- will be null if col1 contains a value
',' + case when col2 is null then 'col2' end, -- will be null if col2 contains a value
',' + case when col3 is null then 'col3' end, -- will be null if col3 contains a value
' is null'), 1, 1, '')
from table_1
where col1 is null or col2 is null or col3 is null
See a live demo on rextester
How about below query using CONCAT and IIF:
insert into
table_2 (col1, col2, col3, col4)
select
col1,
col2,
col3,
CONCAT(
IIF(col1 is null, 'col1 ', ''),
IIF(col2 is null, 'col2 ', ''),
IIF(col3 is null, 'col3 ', ''),
' is null'
)
from
table_1
where
col1 is null
or col2 is null
or col3 is null
Tried with simple case statement and concat
INSERT INTO #table_2 (col1, col2, col3,error_value)
SELECT col1, col2, col3
,SUBSTRING(CONCAT( CASE WHEN col1 IS NULL THEN ',col1' ELSE '' END
,CASE WHEN col2 IS NULL THEN ',col2' ELSE '' END
,CASE WHEN col3 IS NULL THEN ',col3' ELSE '' END
),2,20) + ' is null'
FROM #table_1
WHERE col1 IS NULL OR col2 IS NULL OR col3 IS NULL

Showing the result of COALESCE into separate columns based from where they where retrieved

I have a table with many NULL values. Therefore I use the COALESCE function to retrieve the NON NULL values. This works fine when the result of the COALESCE is to be placed in a single Column. However I need to place the values of the COALESCE into separate Columns depending from where they where picked.
E.g. I have the following table.
SELECT COALESCE(Col1, Col2, Col3, Col4) FROM Table 1
Will produce:-
Column1
1
1
3
4
However I do not want that result but I want this result:-
Col1 Col2 Col3 Col4
1 - - -
- 1 - -
- - 3 -
- 4 - -
As you can see I want only one field populated (that why I'm suing COALESCE but the result of COALESCE should be placed as illustrated, NOTICE ONE VALUE PER ROW.
Any ideas of how I can achieve this result please.
coalesce can be built with case statements. You need something like the below:
select col1
, case when col1 is not null then null else col2 end 'Col2'
, case when col1 is not null or col2 is not null then null else col3 end 'Col3'
, case when col1 is not null or col2 is not null or col3 is not null then null else col4 end 'Col4'
from table
You can achieve this with a combination of PIVOT, UNPIVOT and ROW_NUMBER.
declare #t table(rn int identity(1,1) primary key, col1 int, col2 int, col3 int, col4 int);
insert #t values (1,null,null,null), (null,1,0,null), (null,null,3,null), (null,4,null,2);
with a as (
select *, ranking = row_number() over (partition by rn order by col)
from #t a
unpivot ([val] for [col] in ([col1],[col2],[col3],[col4])) p
)
select *
from a
pivot (min(val) for [col] in ([col1],[col2],[col3],[col4])) p
where ranking = 1

SQL Check constraint on column referencing other columns

I want to limit a column that it can only have a value when another column has a value.
example: (this doesn't work)
create table testConstraint (
col1 int not null identity(1, 1) primary key,
col2 int,
col3 int check (col2 is not null),
col4 int)
This is not possible because he cannot reference another column.
Error:
Column CHECK constraint for column 'col3' references another column,
table 'testConstraint'.
Another try was: (also doesn't work)
create table testConstraint (
col1 int not null identity(1, 1) primary key,
col2 int,
col3 int,
col4 int)
GO
alter table testConstraint add constraint ck_columnNotNull check (case when col2 is null then col3 is null end)
GO
Anyone have an idea how this would be possible with a constraint?
You can write a trigger.
Also, you can try this
(1)
ALTER TABLE TestConstraint ADD CONSTRAINT
CK_TestConstraint CHECK (NOT ( (col3 is not null) and (col2 is null) ))
GO
or this
(2)
ALTER TABLE TestConstraint ADD CONSTRAINT
CK_TestConstraint CHECK
(
((col3 is not null) and (col2 is not null)) or
((col3 is null) and (col2 is null))
)
GO
depending on what exactly you need.
I just tested it and it works OK, I think.
insert into
TestConstraint
(col2, col3, col4)
values
(null, 1, 2)
-- ERROR
insert into
TestConstraint
(col2, col3, col4)
values
(1, 1, 2)
-- OK
ALTER TABLE testConstraint
ADD CONSTRAINT ck_columnNotNull
CHECK ( 1 = CASE
WHEN col2 IS NULL AND col3 IS NULL THEN 1
WHEN col2 IS NOT NULL AND col3 IS NOT NULL THEN 1
ELSE 0
END)
Only simple logic is required, plus it needs (as per your second attempt) to be a table check constraint, so you can't declare it inline with the declaration of col3:
create table testConstraint (
col1 int not null identity(1, 1) primary key,
col2 int,
col3 int,
col4 int)
GO
alter table testConstraint add constraint ck_columnNotNull check (
col3 is null
or col2 is not null
)
GO
If col3 is null, then we don't care what the value of col2 is. Conversely, if it's not NULL, then we do want to enforce the col2 isn't null. That's what the two sides of the or effectively give us.

SQL Server : stored procedure IFNULL check

Solution_id (Primary key, Int)
Col1 (varchar)
Col2 (varchar)
Col3 (varchar)
Col4 (varchar)
Col5 (varchar)
I am writing a stored procedure to update this table. There are 6 input parameters for the above 6 columns.
#Attached_File1 VARCHAR(MAX),
#Attached_File2 VARCHAR(MAX),
#Attached_File3 VARCHAR(MAX),
#Attached_File4 VARCHAR(MAX),
#Attached_File5 VARCHAR(MAX),
#Ticket_ID BIGINT
I want to write a SQL query which will update the table with the values specified in the input parameters. BUT I must not overwrite the attachment columns with null. I mean I need to use only those parameters which contains data.
For example, if the table has a row
[10, "aaa", "bbb", "efg", null, null]
and the input parameters are
(10, null, null, "mno", "ddd", null)
then after the update the row will become
[10, "aaa", "bbb", "mno", "ddd", null]
How to check for null/empty strings and generate the update query accordingly to achieve this?
Is this something like you're after?
UPDATE mytable
SET Col1 = ISNULL(#Attached_File1, Col1),
Col2 = ISNULL(#Attached_File2, Col2),
Col3 = ISNULL(#Attached_File3, Col3),
Col4 = ISNULL(#Attached_File4, Col4),
Col5 = ISNULL(#Attached_File5, Col5)
WHERE Solution_id = #Ticket_ID
ISNULL takes two values, if the first one is not null then it is used, otherwise the 2nd value is used.
See MSDN for more information on ISNULL
Update
I've just noticed your comment at the end, which talks about empty strings...
How to check for null/empty strings and generate the update query accordingly to achieve this?
In which case, you could do the following...
UPDATE mytable
SET Col1 = ISNULL(NULLIF(#Attached_File1,''), Col1),
Col2 = ISNULL(NULLIF(#Attached_File2,''), Col2),
Col3 = ISNULL(NULLIF(#Attached_File3,''), Col3),
Col4 = ISNULL(NULLIF(#Attached_File4,''), Col4),
Col5 = ISNULL(NULLIF(#Attached_File5,''), Col5)
WHERE Solution_id = #Ticket_ID
This uses the NULLIF statement which takes two values, if the first value is the same as the second value, then NULL is returned, otherwise it returns the first value.
See MSDN for more information on NULLIF
update YourTable
set col1 = isnull(#Attached_File1, col1)
, col2 = isnull(#Attached_File2, col2)
, col3 = isnull(#Attached_File3, col3)
, ...
where Solution_ID = #Ticket_ID
If the parameters can contain empty strings, consider #freefaller's answer. If it can contain whitepsace, try:
set col1 = case
when #Attached_File1 like '%[^ \t\r\n]%' then #Attached_File1
else col1
end
, col2 = ...
I would try this:
UPDATE Table
SET
Col1 = ISNULL(#Attached_File1, Col1),
Col2 = ISNULL(#Attached_File1, Col2),
Col3 = ISNULL(#Attached_File1, Col3),
Col4 = ISNULL(#Attached_File1, Col4),
Col5 = ISNULL(#Attached_File1, Col5),
WHERE
Solution_Id = #Ticket_ID
Checking only for Nulls, not for empty strings:
UPDATE
tableX
SET
Col1 = COALESCE(#Attached_File1, Col1),
...
Col5 = COALESCE(#Attached_File5, Col5)
WHERE
Solution_id = #Ticket_ID ;

SQL-Multiple Insert into identity table

I need to to do a insert from a table with the following structure:
Table A
Col1 Col2 Col3 Col4
intID1 intID2 intID3 intID4
I need to select the rows from the above table that are null
for col1,col2,col3 and insert those rows into a table that will generate an identity
row that I need to use to insert into another table.I am not sure of the
sql statement or the general method used to select those rows and insert them multiple times and retrieve the identity id one by one to insert into the next table.
Any help is greatly appreciated!
Sample process:
Table A
Col1 Col2 Col3 Col4
1 3 7 null
null null null 45
null null null 67
1)Retrieve rows 2 and 3
2)Insert 2 and 3 into another table to retrieve identity id for both rows
3)Insert identities from step 2 into another table
Venk covered step 1 and 2 I think. For 3 can use the OUPUT clause to retrieve the identity value from set operation.
Get Identity of multiple insertion in sql server 2008
INSERT INTO TABLEB(Col1,Col2,Col3,Col4)
SELECT * FROM TABLEA WHERE Col1 is NULL AND Col2 is NULL AND Col3 is NULL;
Sounds like you need the output operator:
declare #TableA table(Col1 int, Col2 int, Col3 int, Col4 int);
declare #TableB table(id int identity(1,1), Col1 int, Col2 int, Col3 int, Col4 int);
declare #Audit table(id int);
insert into #TableA
select 1,3,7,null union all
select null, null, null, 45 union all
select null, null, null, 67;
-- copy null columns from #TableA to #TableB
-- and output id's to #Audit
insert into #TableB
output inserted.id
into #Audit
select *
from #TableA
where Col1 is null
and Col2 is null
and Col3 is null;
-- Copied #TableB values and #Audit values
select * from #TableB;
select * from #Audit;