October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk4 min

How One Nullable Column Created Five Years of Branches

A blank CSV cell revealed a deeper schema problem: one nullable locale field had come to mean unknown, not applicable, and empty. Here’s how to choose a clear data contract and migrate it safely.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A CSV export exposed the problem: unfinished backfill work had left blank cells in accounts.locale. In the system described by the post, the deeper issue was that NULL had come to mean three different things—unknown, not applicable, and empty—and each reader had to guess which one it was.

Why one nullable field spread through every client

The post describes a nullable accounts.locale field whose optionality propagated into client code: Python used Optional[str], Go used *string, and TypeScript used string | null | undefined. Consumers also accumulated fallback logic. These are examples from the author’s system, not independent measurements, but they illustrate the interface cost: every reader needs a rule for absence.

As an Amazon Associate I earn from qualifying purchases.

The CSV export used SELECT *, so blank locale cells made the incomplete backfill visible. The export was a symptom; the harder problem was that no single, stable meaning of absence existed for consumers to follow.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What does NULL mean in this column?

The post identifies three meanings that had been folded into one value:

  • Unknown: the user had not been asked for a locale.
  • Not applicable: the account was API-only.
  • Empty: the user had cleared a preference.

These states can imply different application behavior. A default locale might be appropriate when a value is unknown, but not when the user deliberately cleared a preference. An API-only account may need no locale at all. Replacing every NULL with 'en-US' through COALESCE(locale, 'en-US') hides those distinctions rather than resolving them.

A nullable column is a sound choice when absence has one well-defined meaning and writers and readers apply it consistently. If absence represents multiple product states, record those states explicitly or model the optional fact separately.

How NULL changes SQL results

Comparisons and NOT IN

PostgreSQL uses three-valued logic: an expression can evaluate to true, false, or unknown. Its PostgreSQL 17 documentation says, “SQL uses a three-valued logic system with true, false, and null, which represents ‘unknown’.” (PostgreSQL 17: Logical Operators)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

That matters because a comparison with NULL generally evaluates to unknown, not true or false. A WHERE clause keeps rows only when its condition is true, so equality or inequality filters do not retain rows whose comparison is unknown. Test nullness explicitly with IS NULL or IS NOT NULL.

The same logic explains a common NOT IN surprise. If the list on the right contains NULL, a non-matching value may produce unknown instead of true; the row is then filtered out. If nulls are possible in either the column or the list, define the intended behavior explicitly rather than assuming NOT IN means “not equal to any listed value.” (PostgreSQL 17: Comparison Functions and Operators)

Counts and aggregates

PostgreSQL distinguishes row counts from non-null value counts: count(*) counts input rows, while count(locale) counts rows where locale is not null. Most built-in aggregates ignore null inputs, but aggregate behavior should be checked for the specific function. A report must say whether it means all accounts or only accounts with a recorded locale. (PostgreSQL 17: Aggregate Functions)

Uniqueness

A regular PostgreSQL unique constraint treats nulls as distinct by default, so multiple rows can have NULL in a unique column. If nulls should count as equal for this rule, PostgreSQL 15 and later provide NULLS NOT DISTINCT; verify the target server version and intended semantics before relying on it. (PostgreSQL 17: Constraints)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose a data shape that matches the meaning

Shape Use it when What to account for
Required value with NOT NULL Every row should have a meaningful value. Choose a truthful default or a defensible backfill mapping; a convenient placeholder can change the meaning.
Nullable column Absence is one well-defined state. Document that meaning and make readers handle it consistently, using explicit null predicates where needed.
Non-null state field Several absent states matter, such as unknown versus not applicable. For example, locale_state text NOT NULL DEFAULT 'unknown' can represent the state separately. Add constraints so state and value combinations remain valid.
Child table The optional fact is better represented as a separate relationship. For example, zero account_locale rows can mean absent; one row holds a non-null value. Consider the query and update patterns that follow from the relationship.

Compare candidate designs on semantic clarity, integrity constraints, query shape, consumer complexity, migration risk, and operational cost. The right choice depends on how many meanings absence has and how the data is read and updated.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Migrate to NOT NULL without guessing at the data

For PostgreSQL, treat this as a semantic and operational migration, not just a column alteration. First decide what each existing null means and how future writes should behave. Then make the data satisfy that rule before enforcing it.

  1. Audit the contract. Find writers, readers, exports, filters, aggregates, and constraints involving the column. Count existing nulls and determine whether they correspond to distinct states.
  2. Choose the target meaning. Decide whether every row needs a value, whether one consistent nullable state is valid, or whether a state field or child table is required.
  3. Deploy compatible writes and readers. Make new writes follow the chosen semantics. Update consumers so they no longer infer different meanings from the same null.
  4. Backfill deliberately. Map existing rows based on evidence about their meaning. Do not assign a default merely to make nulls disappear. Run the backfill in a way suited to table size, write volume, and workload.
  5. Add and validate a check constraint when staging helps. PostgreSQL supports ADD CONSTRAINT ... CHECK (locale IS NOT NULL) NOT VALID, followed by VALIDATE CONSTRAINT. Adding a NOT VALID constraint avoids scanning existing rows at that command; validation checks them later and allows concurrent updates, but it still scans the table and acquires a lock. Assess the exact server version and workload. (PostgreSQL 17: ALTER TABLE)
  6. Enforce the column rule. After the data is clean, apply SET NOT NULL if the chosen design requires a value. Its operational behavior depends on PostgreSQL version and whether a valid check constraint proves the condition; consult the documentation for the target version rather than assuming the change is lock-free.
  7. Remove obsolete branches after deployment. Once the constraint is enforced and all relevant consumers have adopted the contract, delete fallback paths that only existed to handle the old ambiguity.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.