Overview

3 Improper data type usage

This chapter surveys common data type pitfalls in PostgreSQL and how they translate into incorrect results, unexpected behavior, and maintenance headaches. The overarching guidance is to pick types that encode the true semantics of your data and enable correct computations, rather than relying on defaults or SQL-Standard curiosities that look convenient but hide sharp edges. The discussion spans time-related types, character types, monetary values, surrogate identifiers, and semi-structured data, offering practical replacements and patterns that are safer, clearer, and no slower in practice.

For temporal data, the chapter shows how “naive” timestamps (TIMESTAMP without time zone) yield wrong durations across time zones and daylight saving changes, and how even standardizing on a single zone doesn’t fix DST edge cases. The remedy is to store moments in time with TIMESTAMPTZ, which uses the same storage size, supports accurate arithmetic, and can be displayed in any zone when needed; you can always extract just the time for presentation. TIME WITH TIME ZONE (TIMETZ) and CURRENT_TIME are discouraged because “time of day” without a date lacks the context required for math and consistency; prefer timestamps and derive time views as needed.

Beyond time, fixed-length CHAR(n) introduces trailing-space semantics, silent truncation on casts, wasted storage, and awkward pattern matching; VARCHAR(n) adds brittle limits without performance or storage gains and can also truncate silently on casts. Prefer TEXT and, if necessary, enforce length with CHECK constraints or a DOMAIN. MONEY is locale-dependent, rounds away sub-cent units, and accepts dubious inputs; use NUMERIC/DECIMAL and store currency in a separate column, avoiding floating-point types. SERIAL/BIGSERIAL create permission and cloning traps around sequences; use SQL-standard identity columns instead, and generate gapless sequences in the application if legally required. Finally, XML is verbose, fragile, hard to compare or index, and fussy about encodings; choose JSON/JSONB for compactness, performance, and indexability. The consistent theme: pick types that preserve meaning, support correct operations, and simplify your system over time.

String padding with CHAR(10)
CH03 F01 Angelakos

Summary

  • There is no benefit to using TIMESTAMP (WITHOUT TIME ZONE), as it can lead to time calculation errors due to lack of time zone and DST context. TIMESTAMP WITH TIME ZONE is the proper data type for recording timestamps as specific moments in time.
  • TIMETZ and CURRENT_TIME have questionable usefulness because time zones have no meaning without the context of dates. Again, it is preferable to use TIMESTAMPTZ even if we don’t need to display the date part of the timestamp.
  • MONEY doesn’t store which currency, it suffers from a limited and flawed implementation, and should be avoided in favor of using NUMERIC or other number formats that can accurately store exact values, potentially in conjunction with storing the currency as a separate column.
  • The two serial types SERIAL and BIGSERIAL have been effectively superseded by identity columns, which have more predictable behavior when it comes to role ownership and use of sequences, and clarity regarding which table the sequence belongs to.
  • You don’t save storage space by using the limited character types CHAR(n) and VARCHAR(n), and the whitespace stored with CHAR(n) can be detrimental to performance. Additionally, you run the risk of running into SQL quirks and painting yourself in a corner with maximum lengths — TEXT is the better choice.
  • XML is a terrible choice for document storage, unless you’re just copying immutable XML data inside the database — if you intend to query/manipulate the data, you should use JSON(B).

FAQ

Why is TIMESTAMP WITHOUT TIME ZONE a bad choice for recording events?Because it’s a naive timestamp that stores no time zone context. Arithmetic across values entered in different time zones or around DST transitions yields wrong durations. Prefer TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ), which stores an absolute moment in time and makes time math correct.
Can I just store all timestamps in a single time zone (e.g., Europe/London) to avoid problems?No. DST transitions in that single zone can still skew calculations (e.g., the “fall back” hour). Using TIMESTAMPTZ lets Postgres handle offsets and DST correctly for arithmetic and conversions.
Is TIME WITH TIME ZONE (TIMETZ) useful for time-of-day values?Not really. Offsets vary with DST and PostgreSQL doesn’t support time arithmetic on TIMETZ. Time zones without dates lack needed context. If you need a specific moment, store TIMESTAMPTZ; if you truly need a time-of-day, store TIME and pair it with a date or timestamp for calculations.
What’s wrong with CURRENT_TIME and what should I use instead?CURRENT_TIME returns TIMETZ, inheriting its pitfalls. Use CURRENT_TIMESTAMP (or now()) to get a TIMESTAMPTZ; if you only need the time-of-day for display, extract it (e.g., EXTRACT or date_part) or handle formatting in the application.
Why should I avoid CHAR(n) for text?It is blank-padded: trailing spaces are ignored in equality but not in LIKE/regex, causing surprises. Casting longer strings truncates silently. It wastes storage and adds overhead stripping spaces, and indexes can be awkward with driver-passed TEXT parameters. Prefer TEXT; if you must enforce exact length, use a CHECK constraint.
Is VARCHAR(n) better than TEXT?No. VARCHAR(n) offers no storage or performance advantage over TEXT, can silently truncate on casts, and creates maintenance pain when the limit is too small. Prefer TEXT and enforce any length rules with a flexible CHECK constraint or a DOMAIN.
What’s the issue with the MONEY type and what should I use?MONEY depends on server locale (LC_MONETARY), doesn’t store currency, rounds to whole cents (losing sub-cent precision), and accepts malformed inputs. For monetary values, use NUMERIC/DECIMAL with appropriate precision/scale and store the currency (e.g., ISO code) in a separate column.
Should I still use SERIAL/BIGSERIAL for auto-increment IDs?Prefer SQL-standard identity columns: GENERATED ALWAYS/BY DEFAULT AS IDENTITY. SERIAL creates a separate sequence with extra permission management and can be accidentally shared via CREATE TABLE … LIKE. Identity columns avoid these pitfalls and are easier to manage. Note that any sequence can have gaps; if you need gapless numbers, generate them in the application.
Is XML a good choice for semi-structured data in PostgreSQL?No. XML is verbose, fragile (namespaces, encodings, schema issues), lacks comparison operators, isn’t indexable in Postgres, and is inefficient to store/parse. Use JSONB instead; it’s compact, well-supported, and indexable with GIN/GiST.
Does TIMESTAMPTZ cost more than TIMESTAMP and can it help with keys/partitioning?They both occupy 8 bytes. TIMESTAMPTZ adds correctness (time zone-aware conversions and arithmetic) at no extra storage cost, and it works well as a natural key for time-series data and as a partition key for efficient querying.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • PostgreSQL Mistakes and How to Avoid Them ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • PostgreSQL Mistakes and How to Avoid Them ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • PostgreSQL Mistakes and How to Avoid Them ebook for free