Zurück

Strategies for approaching NULL values with SQL Server


Overview

Dealing with null values is a fact of life for every database developer. Take advantage of these tips to properly deal with them in SQL Server for your next project.

Data integrity is a critical aspect of any database system. No matter how well a system is planned, the issue of null data values is always present. Let's examine the three aspects of dealing with these values in SQL Server: counting, using null table values, and dealing with foreign keys.

Handle null values with COUNT(*)

Most aggregate functions eliminate null values in calculations; one exception is the COUNT function. When using the COUNT function against a column containing null values, the null values will be eliminated from the calculation. However, if the COUNT function uses an asterisk, it will calculate all rows regardless of null values being present.

If you want the COUNT function to count all rows of a given column, including the null values, use the ISNULL function. The ISNULL function can replace the null value with a valid value.

In fact, the ISNULL function is very valuable for aggregate functions where null values affect the results in an erroneous fashion. Remember that when using an asterisk, the COUNT function will calculate all rows. The following sample code illustrates the impact of null values in the AVG and COUNT aggregate functions:

DROP  TABLE tabcount
GO

SET NOCOUNT ON
GO

CREATE TABLE tabcount (
  pkey int IDENTITY NOT NULL CONSTRAINT pk_tabcount PRIMARY KEY,
  col1 int NULL)
GO

INSERT tabcount (col1) VALUES (10)
GO
INSERT tabcount (col1) VALUES (15)
GO
INSERT tabcount (col1) VALUES (20)
GO
INSERT tabcount (col1) VALUES (NULL)
GO

SELECT AVG(col1) A1,                                 (1)
       AVG(ISNULL(col1,0)) A2,                       (2)
       COUNT(col1) C1,                               (3)
       COUNT(ISNULL(col1,0)) C2,                     (4)
       COUNT(*) C3                                   (5)
FROM tabcount
GO

A1          A2          C1          C2          C3
----------- ----------- ----------- ----------- ---
15          11          3           4           4

Warning: Null value is eliminated by an aggregate
         or other SET operation.

(1) - NULL values are eliminated.
(2) - With the IsNULL function, NULL is replaced with 0.
(3) - NULL values are eliminated.
(4) - With the IsNULL function, NULL is replaced with 0.
(5) - COUNT(*) calculates all rows, even those with NULLs.

SQL-Server shows a warning message, that NULLs are eliminated by AVG and COUNT.

«MAY HAVE» Referential Integrity

There's a situation in SQL Server where Declarative Referential Integrity (DRI) is not enforced because nulls are allowed in the table referencing the parent table. Even though the parent table doesn't contain null values, the child table may contain null values in the column that references the parent table’s primary or unique constraint.

Example

A Departement may have zero, one or more employees. An employee may belong to a departement, but this is not mandatory.

Often this situation may be appropriate when the value from the parent table is currently unknown. For example, the parent table may be an address table, and the child table may contain contact information. The contact address, which is to be passed to the parent table, may be temporarily unknown for any number of reasons. This is a time-dependent issue where null values may be appropriate.

For example, in the following code, the parent table is created and two values are inserted.

DROP TABLE child
GO

DROP  TABLE parent
GO

SET NOCOUNT ON
GO

CREATE TABLE parent (
  pkey int IDENTITY NOT NULL CONSTRAINT pk_parent PRIMARY KEY,
  col1 int NULL)
GO

INSERT parent (col1) VALUES (10)
GO
INSERT parent (col1) VALUES (15)
GO

In the code below, the child table is created, and a null value is inserted into the column referencing the parent table.

CREATE TABLE child (
  pnum int IDENTITY
    CONSTRAINT pk_child PRIMARY KEY,
  pkey int NULL
    CONSTRAINT fk_child_parent FOREIGN KEY
    REFERENCES parent(pkey),
  col1 int NULL)
GO

INSERT child (pkey, col1) VALUES (NULL,2)
GO

However, in the following code, the values are selected from both the parent and child tables. Even though the parent table doesn't contain null values, the child table will allow a null value for the column that references the parent table.

SELECT * FROM parent
GO

pkey        col1
----------- -----------
1           10
2           15

SELECT * FROM child
GO

pnum        pkey        col1
----------- ----------- -----------
1           NULL        2

Check for valid data in nullable foreign keys

When you have two columns that comprise the primary key, and a child table inherits the primary keys as nullable foreign keys, you may have bad data. You can insert a valid value into one of the foreign key columns and null into the other foreign key column. To avoid this situation you can add a table-check constraint that checks for valid data in the nullable foreign keys.

This anomaly may occur for any multicolumn foreign key. So you will need to add a check constraint to test for the anomaly. Initially, the check constraint will check for nullable values in all columns, which comprise the foreign key. The check constraint will also check for non-nullable values within these columns. If both checks pass, the anomaly should be circumvented.

The following is a sample script that illustrates such an anomaly and the check constraint that corrects it.

DROP TABLE child
GO

DROP  TABLE parent
GO

SET NOCOUNT ON
GO

CREATE TABLE parent (
  pkey1 int IDENTITY NOT NULL,
  pkey2 int NOT NULL,
  col1  int NULL,
CONSTRAINT pk_parent PRIMARY KEY NONCLUSTERED (pkey1, pkey2))
GO

INSERT parent (pkey2) VALUES (2)
INSERT parent (pkey2) VALUES (85)
INSERT parent (pkey2) VALUES (41)
INSERT parent (pkey2) VALUES (11)
GO

SELECT * FROM parent
GO

pkey1       pkey2       col1
----------- ----------- -----------
1           2           NULL
2           85          NULL
3           41          NULL
4           11          NULL

CREATE TABLE child (
  pnum int IDENTITY NOT NULL
    CONSTRAINT pk_child PRIMARY KEY NONCLUSTERED (pnum),
  pkey1 int NULL,
  pkey2 int NULL,
  col1 int NULL,
CONSTRAINT fk_parent_child FOREIGN KEY (pkey1, pkey2)
REFERENCES parent (pkey1, pkey2))
GO

INSERT child (pkey1, pkey2) VALUES (NULL,85)
GO

The INSERT is passed ... but is this really what we want ... in most cases this is a typical anomaly.

SELECT * FROM child
GO

pnum        pkey1       pkey2       col1
----------- ----------- ----------- -----------
1           NULL        85          NULL

Now, we remove the rows from the child table, then add a check constraint which avoids such situations.

DELETE FROM child
GO

ALTER TABLE child WITH NOCHECK
  ADD CONSTRAINT ck_fk_parent_child CHECK
  ((pkey1 IS NOT NULL AND pkey2 IS NOT NULL) OR
  (pkey1 IS NULL AND pkey2 IS NULL))
GO

INSERT child (pkey1, pkey2) VALUES (NULL, 11)
GO

Server: Msg 547, Level 16, State 1, Line 1
INSERT statement conflicted with TABLE CHECK constraint
'ck_fk_parent_child'. The conflict occurred in database
'Northwind', table 'child'.
The statement has been terminated.

The anomaly is now no more possible.

Conclusion

Null values are a fact of life for every database developer and administrator, so knowing how to deal with these values is imperative for a successful application. In this article, we have shared a few tips and techniques for dealing with nulls in your data.