| Firebird Documentation Index → Firebird Null Guide → Altering populated tables |
![]() |
If your table already contains data, and you want to add a non-nullable column or change the nullability of an existing column, there are some consequences that you should know about. We'll discuss the various possibilities in the sections below.
Suppose you have this table:
Table 8. Adventures table
| Name | Bought | Price |
|---|---|---|
| Maniac Mansion | 12-Jun-1995 | $ 49,-- |
| Zak McKracken | 9-Oct-1995 | $ 54,95 |
You have already entered some adventure games in this table when you decide to add a non-nullable ID field. There are two ways to go about this, both with their own specific problems.
This is by far the preferred method in general, but it causes some special problems if used on a populated table, as you will see in a moment. First, add the field with this statement:
alter table Adventures add id int not null
After committing, the new ID fields that have been added
to the existing rows will all be NULL. In this
special case, Firebird allows invalid data to be present in a
NOT NULL column. It will also back them up
without complaining, but it will refuse to restore them, precisely
because of this violation of the NOT NULL
constraint.
Firebird 1.5 (but not 1.0 or 2.0) even allows you to make such a column the primary key!
To make matters worse, Firebird lies to you when you retrieve data from the table. With isql and many other clients, “SELECT * FROM ADVENTURES” will return this dataset:
Table 9. Result set after adding a NOT NULL column
| Name | Bought | Price | ID |
|---|---|---|---|
| Maniac Mansion | 12-Jun-1995 | $ 49,-- | 0 |
| Zak McKracken | 9-Oct-1995 | $ 54,95 | 0 |
Of course this will make most people think “OK, cool:
Firebird used a default value of 0 for the new fields – nothing to
worry about”. But you can verify that the ID fields are
really NULL with these queries:
SELECT * FROM ADVENTURES WHERE ID = 0 (returns empty set)
SELECT * FROM ADVENTURES WHERE ID IS NULL (returns set shown above, with false 0's)
SELECT * FROM ADVENTURES WHERE ID IS NOT NULL (returns empty set)
Another type of query hinting that something fishy is going on is the following:
SELECT NAME, ID, ID+3 FROM ADVENTURES
Such a query will return 0 in the “ID+3” column.
With a true 0 ID it should have been 3. The
correct result would be
NULL, of course!
With a (VAR)CHAR column, you would have
seen phoney emptystrings (''). With a DATE
column, phoney “zero dates” of 17 November 1858 (epoch
of the Modified Julian Day). In all cases, the true state of the
data is NULL.
What's going on here?
When a client application like
isql queries the server, the
conversation passes through several stages. During one of them –
the “describe” phase – the engine reports type and
nullability for each column that will appear in the result set. It
does this in a data structure which is later also used to retrieve
the actual row data. For columns flagged as NOT
NULL by the server, there is no way to return
NULLs to the client — unless the client flips
back the flag before entering the data retrieval stage. Most
client applications don't do this. After all, if the server
assures you that a column can't contain
NULLs, why would you think you know better,
override the server's decision and check for
NULLs anyway? And yet that's exactly what you
should do if you want to avoid the risk of reporting false values
to your users.
Firebird expert Ivan Prenosil has written a free
command-line client that works almost the same as
isql, but – among other enhancements –
reports NULLs correctly, even in
NOT NULL columns. It's called
FSQL and you can download it
here:
This is what you should do to make sure that your data are valid when adding a NOT NULL column to a populated table:
To prevent the nulls-in-not-null-columns problem from occurring at all, provide a default value when you add the new column:
alter table Adventures add id int default -1 not null
Default values are normally not applied when adding fields to existing rows, but with NOT NULL fields they are.
Else, explicitly set the new fields to the value(s) they should have, right after adding the column. Verify that they are all valid with a “SELECT ... WHERE ... IS NULL” query, which should return an empty set.
If the damage has already been done and you find
yourself with an unrestorable backup, use
gbak's -n
switch to ignore validity constraints when restoring. Then fix
the data and reinstate the constraints manually. Again, verify
with a “WHERE ... IS NULL”
query.
Firebird versions up to and including 1.5 have an additional
bug that causes gbak to restore
NOT NULL constraints even if you specify
-n. With those versions, if you have backed
up a database with NULL data in NOT
NULL fields, you are really up the creek. Solution:
install 1.5.1 or higher, restore with gbak
-n and fix your data.
Using a CHECK constraint is another way to
disallow NULL entries in a column:
alter table Adventures add id int check (id is not null)
If you do it this way, a subsequent SELECT will return:
Table 10. Result set after adding a CHECKed field
| Name | Bought | Price | ID |
|---|---|---|---|
| Maniac Mansion | 12-Jun-1995 | $ 49,-- | <null> |
| Zak McKracken | 9-Oct-1995 | $ 54,95 | <null> |
Well, at least now you can see that the
fields are NULL! Firebird does not enforce
CHECK constraints on existing rows when you add
new fields. The same is true if you add checks to existing fields with
ADD CONSTRAINT or ADD
CHECK.
This time, Firebird not only tolerates the presence and the
backing up of the NULL entries, but it will also
restore them. Firebird's gbak tool does
restore CHECK constraints, but doesn't apply them
to the existing data in the backup.
Even with the -n switch, gbak restores
CHECK constraints. But since they are not used
to validate backed-up data, this will never lead to a failed
restore.
This restorability of your NULL data
despite the presence of the CHECK constraint is
consistent with the fact that Firebird allows them to be present in
the first place, and to be backed up as well. But from a pragmatical
point of view, there's a downside: you can now go through cycle after
cycle of backup and restore, and your “illegal” data will
survive without you even receiving a warning. So again: make sure that
your existing rows obey the new rule immediately after adding the
constrained column. The “default” trick won't work here;
you'll just have to remember to set the right value(s) yourself. If
you forget it now, chances are that your outlawed
NULLs will survive for a long time, as there
won't be any wake-up calls later on.
Instead of specifying data types and constraints directly, you can also use domains, e.g. like this:
create domain icnn as int check (value is not null); alter table Adventures add id icnn;
For the presence of NULL fields, returning
of false 0's, effects of default values etc., it makes no
difference at all whether you take the domain route or the
direct approach. However, a NOT NULL constraint
that came with a domain can later be removed; a direct NOT
NULL on the column will stay forever.
You cannot add NOT NULL to an existing column, but there's a simple workaround. Suppose the current type is int, then this:
create domain intnn as int not null; alter table MyTable alter MyColumn type intnn;
will change the column type to “int not null”.
If the table already had records, any NULLs
in the column will remain NULL, and again
Firebird will report them as 0 to the user when queried. The situation
is almost exactly the same as when you add a NOT
NULL column (see Adding a
NOT NULL field). The only
difference is that if you give the domain (and therefore the column) a
default value, this time you can't be sure that it will be applied to
the existing NULL entries. Tests show that
sometimes the default is applied to all NULLs,
sometimes to none, and in a few cases to some of
the existing entries but not to others! Bottom line: if you change a
column's type and the new type includes a default, double-check the
existing entries – especially if they “seem to be” 0 or
zero-equivalents.
There are two ways to add a CHECK constraint to a column:
alter table Stk add check (Amt is not null)alter table Stk add constraint AmtNotNull check (Amt is not null)
The second form is preferred because it gives you an easy handle
to drop the check, but the constraints themselves function exactly the
same. As you might have expected, existing NULLs
in the column will remain, can be backed up and restored, etc. etc. –
see Adding a
CHECKed column.
If you used a CHECK constraint to make the column non-nullable, you can simply drop it again:
alter table Stk drop constraint AmtNotNull
If you haven't named the constraint yourself but added the CHECK directly to the column or table, you must first find out its name before you can drop it. This can be done with the isql “SHOW TABLE” command (in this case: SHOW TABLE STK).
In the case of a NOT NULL constraint, if you know its name you can just drop it:
alter table Stk drop constraint NN_Amt
If you don't know the name you can try isql's “SHOW TABLE” again, but this time it will only show the constraint name if it is user-defined. If the name was generated by the engine, you have to use this SQL to dig it up:
select rc.rdb$constraint_name
from rdb$relation_constraints rc
join rdb$check_constraints cc
on rc.rdb$constraint_name = cc.rdb$constraint_name
where rc.rdb$constraint_type = 'NOT NULL'
and rc.rdb$relation_name = '<TableName>'
and cc.rdb$trigger_name = '<FieldName>'
Don't break your head over some of the table and field names in this statement; they are illogical but correct. Make sure to uppercase the names of your table and field if they were defined case-insensitively. Otherwise, match the case exactly.
If the NOT NULL constraint came with a domain, you can also remove it by changing the column type to a nullable domain or built-in datatype:
alter table Stk alter Amt type int
Any concealed NULLs, if present, will now
become visible again.
No matter how you removed the NOT NULL
constraint, commit your work and close all connections to the
database. After that, you can reconnect and insert
NULLs in the column.
| Firebird Documentation Index → Firebird Null Guide → Altering populated tables |