When migrating from Oracle to PostgreSQL, one of the most important differences to consider is the treatment of null and empty string values. In Oracle, null and empty string are treated as the same entity, but in PostgreSQL, they are distinct. Failing to account for this distinction during migration can lead to logical errors and unexpected behavior in the database. This post will explore key nuances and demonstrate how to handle these differences effectively.
Comparing null and empty string
In Oracle, an empty string is treated as null when compared. Consider the following query:
CASE WHEN ''='' THEN 'is equal' ELSE 'is not equal' END AS example_2
FROM dual;
The result is following:
EXAMPLE_1 | EXAMPLE_2 |
is null | is not equal |
Here, Oracle treats ” (empty string) as null. However, in PostgreSQL, the same logic doesn’t apply:
CASE WHEN ''='' THEN 'is equal' ELSE 'is not equal' END AS example_2,
CASE WHEN NULL IS NOT DISTINCT FROM NULL THEN 'is equal' ELSE 'is not equal' END AS example_3;
The result is following:
EXAMPLE_1 | EXAMPLE_2 | EXAMPLE_3 |
is not null | is equal | is equal |
In PostgreSQL, an empty string is distinct from null, which means that the first comparison returns is not null. Also, in Oracle, an empty string (”) is treated as null, so the comparison using “=” works differently. Note that PostgreSQL provides a useful feature: the “is not distinct from” operator, which allows nulls to be compared as if they were regular values. This simplifies expressions like:
by using:
Function DECODE in Oracle vs PostgreSQL
The Oracle DECODE function allows comparisons that treat nulls and empty strings as equivalent, which isn’t directly supported in PostgreSQL. For example, the following Oracle query works as expected:
decode(NULL, '', 'is empty string', '1', '1', 'default') AS example_2
FROM dual;
EXAMPLE_1 | EXAMPLE_2 |
is null | is empty string |
When using SCT to convert this to PostgreSQL, the results are incorrect because PostgreSQL lacks native support for DECODE. Here’s an example of an AWS SCT-generated query:
CASE NULL WHEN '' THEN 'is empty string' ELSE 'default' END AS example_2;
EXAMPLE_1 | EXAMPLE_2 |
default | default |
This must be rewritten in PostgreSQL to handle null and empty string distinctions correctly:
CASE WHEN NULL='' THEN 'is empty string' ELSE 'default' END AS example_2;
EXAMPLE_1 | EXAMPLE_2 |
is null | default |
Handling numeric, date, and interval types
The situation becomes even more complex with numeric, date, or interval types in Oracle. When comparing values, Oracle’s DECODE allows empty strings to be treated like nulls. Here’s a sample query in Oracle:
FROM dual;
EXAMPLE_1 |
is null |
In PostgreSQL, after AWS SCT automatic conversion, errors can occur, especially when empty strings are involved:
This query throws error and needs to be corrected by explicitly handling empty strings as null:
EXAMPLE_1 |
is null |
Concatenation differences
In Oracle, concatenation with || behaves in a way that ignores null values unless all concatenated strings are null. However, in PostgreSQL, concatenation with || will return null if any of the values being concatenated are null. SCT often converts concatenation using the concat_ws function in PostgreSQL:
This function behaves similarly to Oracle’s ||. But it returns an empty string if all inputs are null, while Oracle operator || returns null in this case. For cases where you expect null behavior, you’ll need to adjust your conditions:
Or, a more precise approach:
Empty string and NOT NULL constraint
In Oracle, inserting an empty string into a NOT NULL column will trigger a rejection because it’s treated as null. In PostgreSQL, however, the insert will succeed because empty strings are distinct from null. To ensure the same behavior, you can add a check constraint:
field_with_not_null_constraint text NOT NULL,
CHECK (field_with_not_null_constraint <> ''::text)
);
Conclusion
When migrating from Oracle to PostgreSQL, understanding the differences between null and empty string handling is crucial. Oracle treats null and empty string as the same entity, while PostgreSQL distinguishes between them, which can lead to unexpected outcomes if not handled properly. By making use of PostgreSQL’s is not distinct from operator, adjusting comparisons, and carefully handling concatenation and constraints, you can ensure a smoother migration process and avoid common pitfalls.
If you’re planning a migration or dealing with issues related to null and empty string behavior, ensuring that your code is correctly adapted for PostgreSQL is key to maintaining data integrity and performance.