determine if any values are null, if true then false, else true - sql-server

I currently have a select statement that checks several columns to see if they have data. if any of them are null then i want a bit set to false. if none of them are null then i want a bit set to true. here's what i currently have:
select
cast(
case when ChangeOrderNumber is null then 0 else 1 end *
case when ClientName is null then 0 else 1 end *
case when QuoteNumber is null then 0 else 1 end *
case when ClientNumber is null then 0 else 1 end *
case when ServiceLine is null then 0 else 1 end *
case when ServiceLineCode is null then 0 else 1 end *
case when GroupLeader is null then 0 else 1 end *
case when CreatedBy is null then 0 else 1 end *
case when PTWCompletionDate is null then 0 else 1 end *
case when BudgetedHours is null then 0 else 1 end *
case when BudgetDollars is null then 0 else 1 end *
case when InternalDeadlineDate is null then 0 else 1 end *
case when ProjectDescription is null then 0 else 1 end *
case when Sales is null then 0 else 1 end *
case when Coop is null then 0 else 1 end *
case when PassThrough is null then 0 else 1 end *
case when POStatus is null then 0 else 1 end *
case when PONumber is null then 0 else 1 end as bit
)
as Flag
from t
now, that code works, but it's a bit lengthy, i was wondering if anyone knew of a better way to do this. please note that there are several data types being checked.
further details:
this code is in a view that is being looked at in an application for processing change orders. before a change order can be processed it must meet some data quality checks. this view shows if any of the required data is null.

Just add them up since NULL + "something" is always NULL ...
CREATE TABLE #test(column1 int,column2 varchar(4),column3 float)
INSERT #test VALUES(2,'2',2)
INSERT #test VALUES(0,'1',0)
INSERT #test VALUES(null,'1',0)
INSERT #test VALUES(1,null,0)
INSERT #test VALUES(0,'1',null)
INSERT #test VALUES(null,null,null)
SELECT CASE
WHEN column1 + column2 + column3 is NULL THEN 0 ELSE 1 END, *
FROM #test
from a post I created over 3 years ago ...
Keep in mind that if you have characters that are not numbers that you have to convert to varchar ...
INSERT #test VALUES(0,'abc',null)
Here is the conversion, no need to convert the varchar columns
SELECT CASE WHEN CONVERT(VARCHAR(100),column1)
+ column2
+CONVERT(VARCHAR(100),column3) is NULL THEN 0 ELSE 1 END,*
FROM #test

I think I might go with this solution unless someone comes up with a better one, inspired by #Alireza:
cast(
case when (ChangeOrderNumber is null or
a.ClientName is null or
a.QuoteNumber is null or
ClientNumber is null or
ServiceLine is null or
ServiceLineCode is null or
GroupLeader is null or
CreatedBy is null or
PTWCompletionDate is null or
BudgetedHours is null or
BudgetDollars is null or
InternalDeadlineDate is null or
ProjectDescription is null or
Sales is null or
Coop is null or
PassThrough is null or
POStatus is null or
PONumber is null) then 'false' else 'true'
end as bit) as Flag

Please use IIF() (need to be sql server 2012 or later) I really recommend:
IIF(column1 is null, '0', '1')

What about this one?
select not(a is null or b is null or ...)

You could invert the logic.
SELECT
CASE WHEN ChangeOrderNumber IS NOT NULL
AND ClientName IS NOT NULL
AND QuoteNumber IS NOT NULL
....
THEN 1
ELSE 0
END [Flag]
FROM t

Create a HasValue function that takes in a sql_variant and returns a bit. Then use bitwise AND in your SELECT clause.
CREATE FUNCTION dbo.HasValue(#value sql_variant) RETURNS bit
AS
BEGIN
RETURN (SELECT COUNT(#value))
END
GO
SELECT dbo.HasValue(ChangeOrderNumber)
& dbo.HasValue(ClientName)
& dbo.HasValue(QuoteNumber)
...
as [Flag]
FROM t

Or this:
declare #test1 char(1)
declare #test2 char(1)
declare #outbit bit
set #test1 = NULL
set #test2 = 'some value'
set #outbit = 'True'
select #test1
select #test2
If #test1 + #test2 IS NULL set #outbit = 'False'
Select #outbit

Much simpler -- just use the COALESCE function, which returns the value in the first non-null column.
SELECT Flag = CASE
WHEN COALESCE (column1, column2, column3, ...) IS NULL THEN 0
ELSE 1
END
FROM MyTable

Related

How to use a query-results in the same Select statement?

In my select statement I calculate some values and then need to calculate a new value based on the results of the already calculated values. How can this be archived without using a temp table?
SELECT
CASE WHEN [Customer] = '173220000' THEN '1' ELSE NULL END AS Ver_a
, CASE WHEN [Service] = '173220000' THEN '1' ELSE NULL END AS Ver_b
, CASE WHEN [Productavailability] = '173220000' THEN '1' ELSE NULL END AS Ver_c
, (SUM(Ver_a, Ver_b, Ver_c)/3) AS Identity_Ver
FROM x
Just use a sub-query/derived table:
SELECT
Ver_a
, Ver_b
, Ver_c
, (coalesce(Ver_a,0) + coalesce(Ver_b,0) + coalesce(Ver_c,0))/3 AS Identity_Ver
FROM (
SELECT
CASE WHEN [Customer] = '173220000' THEN '1' ELSE NULL END AS Ver_a
, CASE WHEN [Service] = '173220000' THEN '1' ELSE NULL END AS Ver_b
, CASE WHEN [Productavailability] = '173220000' THEN '1' ELSE NULL END AS Ver_c
FROM x
) x
Note 1: as you are not grouping you don't need the sum function, just the sum operator (+) - and the sum function doesn't take a comma separated list of values either.
Note 2: your sum won't work without using the coalesce function as you are returning null from the case expressions.
Firstly, you cannot do sum(var1, var2, var3).
The answer to your question, as stated, seems to be the code at the end.
BUT, I have to ask: what do you want to accomplish? Your approach does make sense to me :-(
SELECT
Sum(
CASE WHEN [customer] = '173220000' THEN 1 ELSE 0 END +
CASE WHEN [service] = '173220000' THEN 1 ELSE 0 END +
CASE WHEN [productavailability] = '173220000' THEN 1 ELSE 0 END
) / 3 AS Identity_Ver
FROM x

How to convert NVARCHAR to BIT in mssql

I am new to mssql , Here I receive a parameter as "NVARCHAR" but the column datatype is BIT so I need to convert the Nvarchar data as Bit data.
Here the Nvarchar data is always either "True" or "False".
INSERT INTO EC_CUSTOMER_PROFILE(
CP_SEND_NEWS_LETTER,
CP_SEND_PARTNER_SPECIAL_OFFER,
CP_CREATION_DATE,
CP_CREATED_BY)
VALUES(
#mNewsLetter, //Nvarchar(50)
#mSpecialOffer, //Nvarchar(50)
GETDATE(),
#mUserId)
Can anyone help me to fix it .
Just use a CASE expression, e.g.
CASE #mSpecialOffer WHEN 'TRUE' THEN 1 ELSE 0 END
so...
INSERT INTO EC_CUSTOMER_PROFILE(
CP_SEND_NEWS_LETTER,
CP_SEND_PARTNER_SPECIAL_OFFER,
CP_CREATION_DATE,
CP_CREATED_BY)
VALUES(
CASE #mNewsLetter WHEN 'TRUE' THEN 1 ELSE 0 END,
CASE #mSpecialOffer WHEN 'TRUE' THEN 1 ELSE 0 END,
GETDATE(),
#mUserId)
Just cast it as bit.
The strings True and False are interpreted as you would expect.
INSERT INTO EC_CUSTOMER_PROFILE
(CP_SEND_NEWS_LETTER,
CP_SEND_PARTNER_SPECIAL_OFFER,
CP_CREATION_DATE,
CP_CREATED_BY)
VALUES ( CAST(#mNewsLetter AS BIT),CAST(#mSpecialOffer AS BIT),GETDATE(),#mUserId)
INSERT INTO EC_CUSTOMER_PROFILE(
CP_SEND_NEWS_LETTER,
CP_SEND_PARTNER_SPECIAL_OFFER,
CP_CREATION_DATE,
CP_CREATED_BY)
VALUES(
CASE WHEN #mNewsLetter = 'True' THEN 1
WHEN #mNewsLetter = 'False' THEN 0
END,
CASE WHEN #mSpecialOffer = 'True' THEN 1
WHEN #mSpecialOffer = 'False' THEN 0
END,
GETDATE(),
#mUserId)

SQL Server - WHERE clause with CASE

ALTER PROCEDURE GetVendor_RMA_CreditMemo
(#HasCreditMemoNo INT)
BEGIN
SELECT
*
FROM
(SELECT
CreditMemoNumber,
CASE WHEN CreditMemoNumber != ''
THEN 1
ELSE 0
END AS HasCreditMemoNo
FROM
XYZ) as C
WHERE
(C.HasCreditMemoNo = #HasCreditMemoNo OR #HasCreditMemoNo = -1)
END
CreditMemoNumber is a varchar column
I want to achieve this:
CASE
WHEN #HasCreditMemoNo = 0
THEN -- select all rows with no value in CreditMemoNumber Column,
WHEN #HasCreditMemoNo = 1
THEN -- all rows that has some data,
WHEN #HasCreditMemoNo = -1
THEN -- everything regardless..
You can't do this kind of thing with a CASE.
The correct way to do it is with OR:
WHERE (#HasCreditMemoNo = 0 AND {no value in CreditMemoNumber Column})
OR
(#HasCreditMemoNo = 1 AND {all rows that has some data})
OR
(#HasCreditMemoNo = -1)
Would this work for you? I'm not sure if it would improve your performance. You may be better off writing an if else if else statement and three separate select statements with an index on the CreditMemoNumber column.
ALTER PROCEDURE GetVendor_RMA_CreditMemo(#HasCreditMemoNo int)
BEGIN
select
CreditMemoNumber,
case when CreditMemoNumber != '' then 1 else 0 end as HasCreditMemoNo
from XYZ
where
(#HasCreditMemoNo = 0 and (CreditMemoNumber is null or CreditMemoNumber = ''))
or (#HasCreditMemoNo = 1 and CreditMemoNumber != '')
or (#HasCreditMemoNo = -1)
END

How can I correctly compare two nullable BIT values in SQL Server?

A nullable BIT value in SQL Server has 3 possible values: null, 0, and 1. I have two columns that are both nullable BITs, and I want to know when they are "equal" in the sense that a NULL "equals" a NULL and a 1 equals a 1 and a 0 does not "equal" a NULL. But, all of my CASE statements fail to give me the answer I want. What is the best way to do this comparison accurately?
DECLARE #BitComparison TABLE
(
OldValue BIT,
NewValue BIT,
ActuallyEqual VARCHAR(10)
)
INSERT INTO #BitComparison (OldValue, NewValue, ActuallyEqual)
VALUES
(null,null,'equal'),
(null,0,'not equal'),
(null,1,'not equal'),
(0,null,'not equal'),
(0,0,'equal'),
(0,1,'not equal'),
(1,null,'not equal'),
(1,0,'not equal'),
(1,1,'equal')
SELECT *
, CASE WHEN OldValue <> NewValue then 'not equal' else 'equal' end as 'ComparisonTestA'
, CASE WHEN ISNULL(OldValue, 0) <> ISNULL(NewValue, 0) then 'not equal' else 'equal' end as 'ComparisonTestB'
, CASE WHEN ISNULL(OldValue, -1) <> ISNULL(NewValue, -1) then 'not equal' else 'equal' end as 'ComparisonTestC'
FROM #BitComparison
Given the script above, none of the values in the hard-coded ActuallyEqual column match the values in the ComparisonTest columns. What can I add to my SELECT to match the ActuallyEqual column dynamically?
Use IS NULL for NULL check
CASE
WHEN OldValue = NewValue
OR ( OldValue IS NULL AND NewValue IS NULL ) THEN 'equal'
ELSE 'not equal'
END AS 'ComparisonTestA'
Here is a fun alternative using CONCAT().
SELECT OldValue,NewValue,ActuallyEqual
, Test = IIF(concat(OldValue,'-',NewValue) = concat(NewValue,'-',OldValue),'equal','not equal')
FROM #BitComparison
Returns
OldValue NewValue ActuallyEqual Test
NULL NULL equal equal
NULL 0 not equal not equal
NULL 1 not equal not equal
0 NULL not equal not equal
0 0 equal equal
0 1 not equal not equal
1 NULL not equal not equal
1 0 not equal not equal
1 1 equal equal
The solution that has already been posted is the simplest I can think of. Here is a slight variation:
CASE WHEN OldValue = NewValue
OR ISNULL(NewValue, OldValue) IS NULL THEN 'Equal' ELSE 'Not Equal' END
But I wanted to add an explanation as to why each of your expressions doesn't work
CASE WHEN OldValue <> NewValue THEN 'not equal' ELSE 'equal' END
Will fail because NULL <> Anything, is NULL therefore when either value is NULL it will fall into the ELSE statement and return equal.
CASE WHEN ISNULL(OldValue, 0) <> ISNULL(NewValue, 0) THEN 'not equal' ELSE 'equal' END
When either value is NULL it is replaced with 0, therefore when one is NULL and the other is 0 the two will be identified as matching when they don't.
CASE WHEN ISNULL(OldValue, -1) <> ISNULL(NewValue, -1) THEN 'not equal' ELSE 'equal' END
This looks like the one that should work, but because ISNULL will return the data type of the first argument, -1 is converted to 1 (as can be seen with SELECT CONVERT(BIT, -1)), therefore when one value is 1 and the other is null this will incorrectly identify a match.
With this in mind, you could also swap ISNULL for COALESCE:
CASE WHEN COALESCE(OldValue, -1) = COALESCE(NewValue, -1) THEN 'equal' ELSE ' not equal' END
Since COALESCE will return the datatype with the highest precedence (INT > BIT), rather than the datatype of the first argument.

Check if exactly one variable is not null

In a trigger in my sql-server 2008 database I need to check if exactly one variable is not null. This code does what I need, but can it be done in fewer lines and more readable?
DECLARE #string varchar
DECLARE #float float
DECLARE #bit bit
DECLARE #int int
Set #string=NULL -- Exactly one of these variables needs to be set
Set #float=NULL --
Set #bit=NULL --
Set #int=NULL --
IF( (#string is not null AND COALESCE(#float, #bit, #int) IS NULL)
OR (#float is not null AND COALESCE(#string, #bit, #int) IS NULL)
OR (#bit is not null AND COALESCE(#string, #float, #int) IS NULL)
OR (#int is not null AND COALESCE(#string, #float, #bit) IS NULL)
)
print ' ok'
ELSE
print ' not ok'
SELECT CASE WHEN COUNT(c) =1 THEN 'Y' ELSE 'N' END
FROM
(VALUES (CAST(#string AS SQL_VARIANT)),(#float),(#bit),(#int)) T (c)
I'm not necessarily sure it's any more readable (although I guess if you abstracted it to a function it might be) but
if((case when #string is null then 0 else 1 end +
case when #float is null then 0 else 1 end +
case when #bit is null then 0 else 1 end +
case when #int is null then 0 else 1 end) = 1)
....
is a little more flexible?
I thought I was being clever, but this will only work if none of the 4 varibles can have the exact same value, so it's not useful in many cases. I'll post it anyway:
IF(
(COALESCE(#string,#float, #bit, #int) IS NULL)
OR (COALESCE(#string, #float, #bit, #int) != COALESCE(#int, #bit, #float, #string))
)
print 'not ok'
ELSE print 'ok'
COALESCE evals from left to right until it hits a NOT NULL value, so if you invert the order you'll get a different result if you have more than one var. set (unless the varibles can have the same value at the same time)
I think I know the type of setup you're creating. In those circumstances, I usually define me data structures as:
CREATE TABLE DataItems (
DataItemID int IDENTITY(1,1) not null,
Name varchar(10) not null,
TypeRequired varchar(6) not null,
constraint PK_DataItems PRIMARY KEY (DataItemID),
constraint CK_TypeRequired CHECK (TypeRequired in ('STRING','FLOAT','BIT','INT'),
constraint UQ_DataItems_TypeCheck UNIQUE (DataItemID,TypeRequired)
)
note that I've made DataItemID,TypeRequired a superkey, so I can reference it in a foreign key constraint.
Now, in the table that's collecting the data:
CREATE TABLE Answers (
AnswerID int IDENTITY(1,1) not null,
/* Other columns to FK to e.g. Client, Users, Session, whatever */
DataItemID int not null,
Type varchar(6) not null,
StringValue varchar(max) null,
FloatValue float null,
BitValue bit null,
IntValue int null,
constraint PK_Answers PRIMARY KEY (AnswerID),
constraint FK_Answers_DataItems FOREIGN KEY (DataItemID) references DataItems (DataItemID),
constraint FK_Answers_DataItems_TypeCheck FOREIGN KEY (DataItemID,Type) references DataItems (DataItemID,TypeRequired),
constraint CK_Answers_TypeCheck CHECK (
(FloatValue is null or TypeRequired = 'FLOAT') and
(StringValue is null or TypeRequired = 'STRING') and
(BitValue is null or TypeRequired = 'BIT') and
(IntValue is null or TypeRequired = 'INT')),
constraint CK_Answers_NotNUll CHECK (
FloatValue is not null or StringValue is not null or BitValue is not null or IntValue is not null)
)
The second foreign key constraint ensures the type column matches the defined type for the data item, and the pair of check constraints ensure that exactly one column (and the right column) is not null.
If you need to hide the Type column from the users, then I'd suggest renaming the above table (e.g. _Answers) creating a view with an insert trigger:
CREATE VIEW Answers
WITH SCHEMABINDING
AS
SELECT
AnswerID,
DataItemID,
StringValue,
FloatValue,
BitValue,
IntValue
FROM
dbo._Answers
CREATE TRIGGER T_Answers_I
ON Answers
INSTEAD OF INSERT
AS
INSERT INTO _Answers (DataItemID,Type,StringValue,FloatValue,BitValue,IntValue)
SELECT i.DataItemID,di.Type,i.StringValue,i.FloatValue,i.BitValue,i.IntValue
FROM inserted i inner join DataItems di on i.DataItemID = di.DataItemID
I found an alternative solution but it isn't any fewer lines. It uses the bitwise XOR operator. I'm not sure if I like it or not - but it does mean that each variable only gets checked once rather than on each line so potentially it satisfies your readability requirement:
DECLARE #string varchar
DECLARE #float float
DECLARE #bit bit
DECLARE #int int
SET #string=NULL -- Exactly one of these variables needs to be set
SET #float=NULL --
SET #bit=NULL --
SET #int=NULL --
if ((case when #string is null then 1 else 0 end)
^ (case when #float is null then 1 else 0 end)
^ (case when #bit is null then 1 else 0 end)
^ (case when #int is null then 1 else 0 end)) = 1
print 'ok'
else
print 'not ok'
Comments? Criticisms? Also not sure how efficient the CASE statements are.
Can't we check by this:
IF ISNULL(#string, '') <> ''
OR ISNULL(#float, 0) <> 0
OR #bit IS NOT NULL
OR ISNULL(#int, 0) <> 0
PRINT 'There is atleast one value'
ELSE
PRINT 'ALL ARE NULL'

Resources