WHERE clause - record with a NULL column is not di

2019-05-20 22:37发布

问题:

Conception

In the Org_Structure there are unique combinations of departments and offices. In the Emp_Positions table each employee is assigned to the certain combination of department/office or "Chain" in the organizational structure. But some employees should not belong to offices, so they are assigned to a chain where office_id column is NULL. On the second image such employee is colored in red.

The Bug

The provided stored procedure can find only employees of those chains where both dep_id and office_id are NOT NULL. If in some record office_id is NULL, or dep_id is NULL, this record will not be displayed. See the second picture, the record in red color is never displayed in the results. Once I replace NULL with a value, it is displayed. If I replace any column of any row with NULL, this whole row will not display.

I try to find the way to fix it, but I just can't figure it out... Guys here already helped me to make this procedure work and I'm very thankful for that, but WHERE clause is still buggy...

This is the stored procedure:

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE FilterEmpList
    @empName nvarchar(250) = null,
    @empDepID int = null,
    @empOfficeID int = null,
    @empPosID int = null    
AS 
BEGIN
SELECT 
    E.emp_id,
    E.emp_name,      
    P.pos_name,
    D.dep_name,
    O.office_name
FROM dbo.Org_Structure OS 
    JOIN dbo.Emp_Positions EP ON OS.chain_id=EP.chain_id  
    JOIN dbo.Employees E ON EP.emp_id=E.emp_id 
    JOIN dbo.Positions P ON P.pos_id=EP.pos_id
    JOIN dbo.Departments D ON D.dep_id=OS.dep_id
    LEFT JOIN dbo.Offices O ON O.office_id=OS.office_id  
WHERE (E.emp_name LIKE '%'+@empName+'%' OR @empName IS NULL)
    AND OS.dep_id = ISNULL(@empDepID, OS.dep_id)
    AND OS.office_id = ISNULL(@empOfficeID, OS.office_id)
    AND EP.pos_id = ISNULL(@empPosID, EP.pos_id)
END

*with LEFT JOIN added it still doesn't work

THE PROBLEM WAS RESOLVED

See comments

回答1:

The problem is fixed, but I've noticed there's no explanation as to why it's fixed. So here we go:

In SQL, any type of variable can receive the value NULL. This value is meant to represent the absence of a value or an unknown value. NULL is special in the sense that the result of any equality or inequality operator with NULL on one or both sides is not true or false, but NULL.

So:

'Hello' = 'Hello' -> true
'Hello' = 'World' -> false

But:

'Hello' = NULL -> NULL
NULL = NULL    -> NULL

This means SQL uses a boolean system with three possible values. Adding NULL to the usual "true" and "false" following these rules:

TRUE  and NULL = NULL
FALSE and NULL = FALSE
NULL  and NULL = NULL

TRUE  or NULL = TRUE
FALSE or NULL = NULL
NULL  or NULL = NULL

NOT NULL = NULL

In this case if "office_id" or "dep_id" is NULL then the result of the conditions

OS.dep_id = ISNULL(@empDepID, OS.dep_id)

or

OS.office_id = ISNULL(@empOfficeID, OS.office_id)

will be NULL. Since the result of a logical AND with a NULL value is always NULL or FALSE, the result of processing the whole clause clause must be NULL or FALSE, and since a row is only returned if the result of the clause is TRUE, then no line is returned.