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.
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).
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.
Most stored procedures return multiple result sets. Such a stored procedure usually includes one or more select statements. The consumer needs to consider this inclusion to handle all the result sets.
The RETURN statement is used to unconditionally and immediately terminate an SQL procedure by returning the flow of control to the caller of the stored procedure. It is mandatory that when the RETURN statement is executed that it return an integer value. If the return value is not provided, the default is 0.
In order to fetch the multiple returned values from the Stored Procedure, you need to make use of a variable with data type and size same as the Output parameter and pass it as Output parameter using OUTPUT keyword. You can also make use of the Split function to split the comma separated (delimited) values into rows.
In addition to returning output parameters, a stored procedure can return a result set (that is, a result table associated with a cursor opened in the stored procedure) to the application that issues the CALL statement.
Try altering the stored procedure in a session after setting ANSI_NULLS to ON.
SET ANSI_NULLS ON
GO
alter procedure procedureName
...
From the docs:
For stored procedures, SQL Server uses the SET ANSI_NULLS setting value from the initial creation time of the stored procedure. Whenever the stored procedure is subsequently executed, the setting of SET ANSI_NULLS is restored to its originally used value and takes effect. When invoked inside a stored procedure, the setting of SET ANSI_NULLS is not changed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With