Synopsis
I've got a query with a WHERE clause that contains a condition that checks a NULLable column against a parameter that may be NULL, like so:
SELECT ...
FROM Table
WHERE NullableColumn = @NullableParameter
From my understanding, SQL Server is supposed to evaluate NULL = NULL non-NULL = NULL as false always, meaning if @NullableParameter is NULL then the above query would return zero records.
That's the behavior I see when running the query in a query window, but when I use the same query in a stored procedure and when @NullableParameter is NULL it is returning all records where NullableColumn is NULL. In short, it seems like the sproc is saying NULL = NULL is true.
That's the problem/question is a nutshell. The actual WHERE clause is a bit more involved and is presented with more detail in the section below.
The Gritty Details
I have a database with a table called StudyResults. Each record is uniquely identified by a StudyResultId primary key field. There is also a ParticipantId field, that indicates the subject involved in the study, and GroupId, which identifies what group the subject belonged to, if any. If the study was a single-person study, GroupId is NULL. ParticipantId cannot be NULL.
I have a stored procedure that needs to update some records in the StudyResults table for a particular study, but here's the rub - if the study is single-person study then I need to just update that one row; if it was a group study I want to update all rows in StudyResults for that group, for that subject.
This isn't too hard to accomplish. The stored procedure is passed a StudyResultId and then runs the following T-SQL to determine the GroupId and ParticipantId values for that row:
DECLARE @GroupId INT, @ParticipantId INT
SELECT @GroupId = GroupId,
@ParticipantId = ParticipantId
FROM StudyResults
WHERE StudyResult = @StudyResultId
Next, I create a CURSOR to enumerate the StudyResults records of interest. Note the WHERE clause, which says, "Give me the records where StudyResultId equals the @StudyResultId passed into the sproc or the GroupId and ParticipantId values line up with the GroupId and ParticipantId values for the StudyResults record of interest.
DECLARE resultsToEnumerate CURSOR LOCAL FAST_FORWARD FOR
SELECT DISTINCT StudyResultId
FROM StudyResults
WHERE StudyResult = @StudyResultId OR (GroupId = @GroupId AND ParticipantId= @ParticipantId)
If @GroupId is NULL, then comparing GroupId = @GroupId should always be false, right? Because for SQL Server, NULL = NULL is false, and non-NULL = NULL is false.
But here's what it gets weird - if I run the above statements from a query window and use a @StudyResultId for a single-person study the CURSOR contains what I expect - namely, a single record. However, if I put the exact same code in a stored procedure and run it the CURSOR contains all of the single-person studies for that participant! It's as if it is saying @GroupId is NULL, so I'll return all records where GroupId is NULL, but why? NULL = NULL should never return a record, right?
In fact, if I go into the sproc and change the WHERE clause and replace GroupID = @GroupID with NULL = NULL I see the same results - all single-person studies for the participant in question. So it clearly is evaluating NULL = NULL to true in the sproc (or ignoring it).
The Workaround
I can "fix" this in the stored procedure by adding an additional check in the parentheses to ensure GroupId IS NOT NULL like so:
WHERE ActivityID = @ActivityID OR (GroupID IS NOT NULL AND GroupID = @GroupID AND PatientID = @PatientID)
This is what I have done, but I am perplexed as to why the WHERE clause is being evaluated differently in my stored procedure.