Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this query return different results when in a stored procedure?

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.

like image 336
Scott Mitchell Avatar asked Dec 21 '10 18:12

Scott Mitchell


People also ask

Can stored procedure return multiple result sets?

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.

What does stored procedure return?

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.

How can a stored procedure return more than one value?

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.

Can a stored procedure return a result set?

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.


1 Answers

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.

like image 63
Jeffrey L Whitledge Avatar answered Nov 15 '22 06:11

Jeffrey L Whitledge