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.
|