Type Review Framework

Concepts covered: Data Types, Constraints

A column's type is the cheapest quality control a schema has, applied by the database to every write with no pipeline, test or reviewer, and the most expensive thing to change once the table is large. A type review before the first deploy asks every column the same questions.

A type shapes; a constraint rejects

The type decides what a stored value looks like, a weaker promise than it sounds. Inserting 19.995 into a PostgreSQL NUMERIC(10,2) column stores 20.00 with no error, because the engine rounds to the declared scale on write; MySQL in strict mode does the same and emits a note the driver discards. A negative amount fits DECIMAL(10,2), and so does a NULL. Rejection comes from constraints: NOT NULL for a fact that must exist, CHECK (amount >= 0) for a sign the business forbids, a foreign key for a code that must exist in a lookup table. A reviewed column names both its type and the constraints that make it a validator.

CREATE TABLE invoice_lines (
  invoice_line_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  invoice_id      BIGINT NOT NULL REFERENCES invoices(invoice_id),
  quantity        INTEGER NOT NULL CHECK (quantity > 0),
  unit_price      DECIMAL(12,4) NOT NULL CHECK (unit_price >= 0),
  line_total      DECIMAL(12,2) NOT NULL CHECK (line_total >= 0),
  currency_code   CHAR(3) NOT NULL REFERENCES currencies(code),
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

What a wrong type costs to fix

PlatformMetadata-only changeRewrite or rebuild
PostgreSQLWidening VARCHAR(n), VARCHAR to TEXTINT to BIGINT, FLOAT to NUMERIC: full rewrite under an exclusive lock, plus every index
MySQLAppending an ENUM value, widening VARCHAR within the same length-prefix sizeMost others copy the table; gh-ost or pt-online-schema-change avoid downtime
SnowflakeWidening VARCHAR, raising NUMBER precisionScale or type family: add a column, backfill, swap names
BigQueryINT64 to NUMERIC, NUMERIC to BIGNUMERIC or FLOAT64, lengthening STRING(L)Narrowing or cross-family: CREATE OR REPLACE TABLE AS SELECT

The rewrite column is where the review pays for itself. An id INT primary key on payment_events holds 2,147,483,647 values; at 1,000 inserts per second that is 24.9 days of continuous writes, which a growing platform reaches within a few years of normal traffic. The fix rewrites the table and then every table whose foreign key references it, in dependency order, each under its own lock. Declared BIGINT on the first day, the column costs 4 extra bytes per row and nothing else.

Do
  • Declare money as DECIMAL or BIGINT minor units, and write down the rounding rule beside it
  • Take string lengths from a standard or a known format, and check how the platform treats overflow
  • Store past events as TIMESTAMPTZ and future appointments as local time plus an IANA zone name
  • Model a growing value set as a lookup table with a foreign key
  • Pair every type with the NOT NULL, CHECK and FK constraints that reject bad rows
Avoid
  • Let an ORM default pick FLOAT for a column anyone will reconcile
  • Size a column from a sample and assume the longest value has been seen
  • Subtract 2 naive timestamps and call the result elapsed time
  • Add a third boolean flag to a row that already cannot keep 2 consistent
  • Choose INT for a key or CHAR(36) for a UUID on a table that will grow

Review this column from a proposed payments table that will serve customers in 40 countries: amount DECIMAL(10,2). Which finding is the one to raise?

About this lesson

This section is part of the Schema Types lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.