I don't get the simple boolean algebra on my sql-server. According to msdn, the following statement should return "1", but on my server it returns "0".
Can you help me?
SET ANSI_NULLS ON
SELECT CASE WHEN NOT(1=NULL) THEN 1 ELSE 0 END
Please have a look at msdn. There it clearly states: "Comparing NULL to a non-NULL value always results in FALSE." - no matter what the ANSI_NULLS-setting is. Thus "1=NULL" should be FALSE and NOT(FALSE) should thus be TRUE and the statement should return "1".
But on my machine, it returns "0"!
One explanation might be, that "1=NULL" evaluates to "UNKNOWN". NOT(UNKNOWN) is still UNKNOWN (msdn), which would force the CASE-Statement into the ELSE.
But then the official documentation of the equals-operator would be wrong. I cannot believe this!
Can anybody explain this behaviour?
Thank you very much for any help!
Edit (2012-03-15):
One thing I just found that might be of interest for some of you:
CREATE TABLE #FooTest (Value INT)
ALTER TABLE #FooTest WITH CHECK ADD CONSTRAINT ccFooTestValue CHECK (Value>1)
PRINT '(NULL>1) = ' + CASE WHEN NULL>1 THEN 'True' ELSE 'False' END
INSERT INTO #FooTest (Value) VALUES (NULL)
The print-Statement writes 'False', but the insertion runs without error.
SQL-Server seems to negate the check-constraint in order to search for rows that do not fulfill the constraint-check:
IF EXISTS (SELECT * FROM inserted WHERE NOT(Value>NULL)) <Generate error>
Since the check-constraint evaluates to UNKNOWN, the negation is also UNKNOWN and SqlServer does not find any row violating the check-constraint.
Yes that link is wrong. File a documentation bug on Microsoft Connect.
Sql uses three valued logic not boolean logic. true, false, and unknown
Most comparison operators (i.e. excluding IS [NOT] NULL) involving NULL result in unknown not True or False. Negating unknown yields unknown as per the truth tables shown here.
The MSDN page for Equals that you link to definitely appears incorrect.
Check the MSDN page for SET ANSI_NULLS.
When SET ANSI_NULLS is ON, all comparisons against a null value
evaluate to UNKNOWN.
To get that example SQL statement to work as expected, you should use compare using "IS NULL" or "IS NOT NULL" instead of using the equals operator (=). For example:
SELECT CASE WHEN NOT(1 IS NULL) THEN 1 ELSE 0 END
OR
SELECT CASE WHEN (1 IS NOT NULL) THEN 1 ELSE 0 END
You want to read the documentation on ANSI_NULLS. SQL actually implements a ternary logic, not boolean logic, where a comparison operation can result in true, false or undefined. Basically, this means that the explanation you proffered is correct.
This can be demonstrated with the following query:
SET ANSI_NULLS ON
SELECT CASE
WHEN (1=NULL) THEN 0
WHEN NOT(1=NULL) THEN 1
ELSE -1
END
Which results in -1 on my machine (SQL Server 2005 Enterprise). Changing the first line to SET ANSI_NULLS OFF produces 1 as expected.
So, is the official documentation wrong? I'd submit that is somewhat misleading. It says that it results in FALSE. Obviously this is wrong. What the documentation meant to say is that comparing a non-null to NULL always results in a mismatch whose value also depends on ANSI_NULLS.
Of course, on SQL Server 2012, the ANSI_NULLS setting has been removed, and therefore setting it any which way will not change the result.
It's not boolean logic, its trinary logic: {True, False, I Don't Know.} Break it down this way:
IF 1=NULL
print 'True'
else
print 'False'
Generates False because 1=NULL equals NULL, aka "not True"
IF not(1=NULL)
print 'True'
else
print 'False'
Also generates False because not(1=NULL) equals not(NULL) equals NULL, aka "not True". This gets you to
SELECT CASE WHEN NOT(1=NULL) THEN 1 ELSE 0 END
which as per above is the same as
SELECT CASE WHEN NULL THEN 1 ELSE 0 END
which, since NULL is not true, resovles to the ELSE clause.
In short, as far as I'm concerned the documentation is incorrect. Distressing, but not unique, and so not entirely surprising.
Try using EXISTS in a subquery, it uses 2 valued logic and will give you the true/false you are looking for.
From BOL (credit to Thomas):
SET ANSI_NULLS ON affects a comparison only if one of the operands of
the comparison is either a variable that is NULL or a literal NULL. If
both sides of the comparison are columns or compound expressions, the
setting does not affect the comparison.
So I guess the NOT operation is checking 1=NULL which is unknown and because this is not a variable or literal NULL gets the ELSE part of your comparison as you hypothesised.
1=NULL seems to return FALSE only when ANSI_NULLS is OFF. Otherwise it's indeterminate. The msdn page probably needs to be edited to clarify that point.
SET ANSI_NULLS OFF
SELECT CASE WHEN (1=NULL) THEN 'true' ELSE 'false or unknown' END --returns false or unknown
, CASE WHEN NOT(1=NULL) THEN 'true' ELSE 'false or unknown' END --returns true
go
SET ANSI_NULLS ON
SELECT CASE WHEN (1=NULL) THEN 'true' ELSE 'false or unknown' END --returns false or unknown
, CASE WHEN NOT(1=NULL) THEN 'true' ELSE 'false or unknown' END --returns false or unknown
go
Related
I have the following tasks in SSIS:
In Check Stock task, I execute a stored procedure which returns 0 or 1:
CREATE PROCEDURE [dbo].[SP_CheckStockAvailability]
AS
BEGIN
DECLARE #ItemGID nvarchar(250)=(SELECT TOP (1) ItemGID FROM XMLOrderlines WHERE Comparison='0')
SELECT CASE
WHEN #ItemGID IS NOT NULL
THEN CAST (0 AS bit)
ELSE CAST (1 AS bit)
END AS Comparison
FROM XMLOrderlines
END
GO
I would like to execute Reject Order (on the right) task if the result is 1 and, if not, to execute the one from the left. I set to export the result of the procedure in a variable, Boolean data type with a default value of "False".
If I edit the precedence constraint and I set Expression as evaluation operation and then choose the variable from the previous task, either way it does not go to the next task that is supposed to. What am I missing? I tried what I found on the web but nothing helped me. Thanks!
Solution
You have to set the following values:
Reject Order
Evaluation operation: Expression and Constraint
Value: Success
Expression: #[User::Result] = 1
OR
#[User::Result]
Accept Order
Evaluation operation: Expression and Constraint
Value: Success
Expression: #[User::Result] = 0
OR
!#[User::Result]
Screenshot
Suggestions
I think it is better to add a TRY...CATCH block to your procedure, so if it encounters an error the result will be 1 and the Order is rejected:
CREATE PROCEDURE [dbo].[SP_CheckStockAvailability]
AS
BEGIN
BEGIN TRY
DECLARE #ItemGID nvarchar(250)=(SELECT TOP (1) ItemGID FROM XMLOrderlines WHERE Comparison='0')
SELECT CASE
WHEN #ItemGID IS NOT NULL
THEN CAST (0 AS bit)
ELSE CAST (1 AS bit)
END AS Comparison
FROM XMLOrderlines
END TRY
BEGIN CATCH
SELECT 1 AS Comparison
END CATCH
END
GO
Try choosing Evaluation Operation to Expression and Constraint and the expression has to be smth like #[User::Result] = 1. The expression has to return True or False.
For more informations, follow this link:
Working with Precedence Constraints in SQL Server Integration Services
The expression for 1/True should be #[User::Result], as you have, and the expression for 0/False should be !#[User::Result] (notice the exclamation mark).
You may also want to look into using "Expression and Constraint" as an "Evaluation operation", as without that, the flow will continue regardless of whether "Check stock" succeeds or fails (unless that is the desired behaviour). The "Constraint" part of this is what the "Value" field represents - in your image it is set to Failure, but greyed out (due to having only "Expression" selected) and therefore inactive.
As you can see i have this condition in my query
if lower('HP||2008|201408')=lower(#serial1) or lower('HP||2008|201408')=lower(#serial2)
select 1
else select 0
But the sql skips my condition and return 0 ,but the condition is true .Why ?
All variables are nvarchar(max) data type .
See, you're comparing 'HP||2008|201408' (one pipe) with serial2 that is equal to 'HP||2008||201408' (two pipes)
(don't know the value of serial1)
The condition is NOT true, that is clear even in the screenshot.
#serial2 does NOT have the same value as what you are checking for - look at it again.
I have a Stored Procedure for searching that takes several optional parameters. One of those is #keywords defined as
#keywords nvarchar(1000) = null,
If #keywords is null or empty string, I want to short circuit, otherwise I need to search a full text index. My logic used to look like this:
WHERE
(#keywords IS NULL OR CONTAINS( (Title, CrossRef, company_name), #keywords))
AND
-- other search terms
However, I just discovered that OR is not guaranteed to short circuit, so sometimes this returns an error for "empty full-text predicate." Apparently CASE is supposed to be guaranteed to short circuit, but some versions of SQL server have a bug where this isn't the case.
Here's what I'm trying:
WHERE
(1 = CASE
WHEN #keywords IS NULL THEN 1
WHEN #keywords = '""' THEN 1
ELSE (CASE WHEN CONTAINS( (Title, CrossRef, company_name), #keywords) THEN 1 ELSE 0 END)
)
AND
-- other search terms
This still gives the "empty full-text predicate" error. I'd be happy to just replace #keywords with something that will always match, but I'm not sure how to do that either.
Since you're already using a SP, why not just throw this into IF THEN ELSE blocks to implement your short-circuit logic. Not only will it be easier to read, but the optimizer will like it better as well.
Here's what I ended up doing. At the top of the SP:
DECLARE #fix_keywords nvarchar(1000)
SET #fix_keywords = #keywords
IF #keywords IS NULL OR #keywords = '' SET #fix_keywords = 'zqxvMatchNothing321'
and then
WHERE
(#keywords IS NULL OR #keywords = '' OR CONTAINS( (Title, CrossRef, company_name), #fix_keywords))
AND
-- other search terms
I realize this is a bit hacky, but I think it's the best solution in my case, since I don't have to duplicate the entire query in an IF/ELSE.
Another possible solution would be to use dynamic SQL.
I'm having some difficulty in understanding the following WHERE clause in a T-SQL (SQL Server 2000/2005) query:
update #tempTable
SET
Total_Avg=isnull(TerminationReason,'terminated'),
Individual_Subscriptions=null,
Business_Subscriptions=null,
Other_subscriptions=null,
-- snip. 10 more fields set to NULL.
PMIE_BI=null,
Digital_Editions_BI=null
where
(
AbcTerminationDate<=dbo.fnGetPeriodFinalDate(#periodid)
and (AbcTerminationDate!=19000101 or AbcTerminationDate is null)
and (Total_Avg is not NULL or PrevTotalAvg is not NULL)
)
Specifically, the second clause doesn't make sense to me - it's 2 sub-clauses separated by the OR operator seem contradictory.
The AbcTerminationDate field is declared as INT NULL in a table called Members. I believe a date of 19000101 in the system means NULL or a default value or no value, i.e. that a member is NOT terminated. So the query appears to blank out a whole lot of fields/figures if a member is marked as terminated, which would be when the AbcTerminationDate is NULL or has the default value.
Without knowing any more information, what do you make of it?
It does look like those are contradictory. Perhaps they meant
and !(AbcTerminationDate==19000101 or AbcTerminationDate is null)
?
Whatever 19000101 is "supposed" to mean, it is not the same as NULL in the eyes of the database. NULL is NULL. If you try to evaluate any other value to NULL then it can become problematic, because NULL means "unknown". For example, does 1=NULL? Maybe it does, maybe it doesn't. In fact, you can't even say that NULL=NULL, because each NULL is unknown so might or might not be equal to the other. It's safest to explicitly check for NULL conditions.
EDIT:
As I point out in my comment, if NULLs are to be included then the first part of the query precludes that. Here is how it should be written if NULLs should be included:
(
(
(
AbcTerminationDate <= dbo.fnGetPeriodFinalDate(#periodid) AND
AbcTerminationDate != 19000101
) OR
AbcTerminationDate is NULL
) AND
(Total_Avg is not NULL or PrevTotalAvg is not NULL)
)
If the "or" arg were outside the () grouping it would negate:
AbcTerminationDate<=dbo.fnGetPeriodFinalDate(#periodid)
[edit]
Basically it's saying to take whatever results are true from that 1st clause, and perform an additional filter to make sure it's not 19000101 or it's null, are most likely exceptional values for the fnGetPeriodFinalDate function to properly evaluate.
In postgres you can do a comparison against multiple items like so:
SELECT 'test' IN ('not','in','here');
Which is the same as doing:
SELECT ('test' = 'not' OR 'test' = 'in' OR 'test' = 'here');
Is there a functional equivalent for SQL Server ?
It is supported, but you will need to put the expression somewhere that accepts a boolean expression. For example, in a case statement:
select case when 'test' in ('not','in','here') then 1 else 0 end
-----------
0
(1 row(s) affected)
Or a where clause:
select * from T where C in (1,3,5,7,9)
this should give similar results in all recent versions of MSSQL. You could also write a scalar function to shorten things up a bit.
select case
when 'test' IN ('not', 'in', 'here') then 1
else 0
end;
This will give 1 if 'test' is in the comparison set 1 or more times, or 0 if it isn't.
SELECT CAST(COUNT(*) AS BIT) as IsItHere WHERE 'test' IN('not','in','here')
Note that the cast isn't strictly necessary, but may be useful if this is called from another language... It should then resolve to a boolean.
EDIT: According to MSSQL Query planner, this is executed ~4 times faster then the CASE method. YMMV.