By WebNest Studio Engineering Team
PostgreSQL Tutorial
PostgreSQL Data Types
PostgreSQL has one of the richest type systems of any relational database. Beyond the standard SQL numeric, text, and date types, it offers native UUID, JSON/JSONB, array, and range types, plus the ability to define your own custom types. Choosing the right type up front avoids painful migrations and subtle correctness bugs later, especially around money and time.
The two areas where beginners most often pick the wrong type are numeric precision (using floating-point types for currency) and timestamps (ignoring time zones). Both mistakes are easy to avoid once you understand what PostgreSQL actually offers.
Why it matters
PostgreSQL offers a richer set of types than most databases, and the right choice prevents whole categories of bugs: money that drifts by a paisa, timestamps that are wrong after a server moves time zone, identifiers that collide. Changing the type of a column on a large table later is slow and risky.
Text and Character Types
PostgreSQL provides CHAR(n) (fixed-length, blank-padded), VARCHAR(n) (variable-length with a limit), and TEXT (variable-length, unlimited). Unlike some databases, PostgreSQL has no meaningful performance difference between these — internally they use the same storage. Because of this, idiomatic PostgreSQL style favors TEXT for almost everything, adding a CHECK constraint if you truly need to cap a length, rather than reaching for VARCHAR(n) out of habit.
Numeric Types: NUMERIC vs. FLOAT
INTEGER and BIGINT handle whole numbers. For decimals, PostgreSQL gives you two fundamentally different families: REAL/DOUBLE PRECISION (binary floating-point, fast but inexact) and NUMERIC(precision, scale) (exact, arbitrary-precision decimal). This distinction matters enormously for money: floating-point types cannot represent values like 0.10 exactly in binary, so repeated arithmetic accumulates rounding error. NUMERIC stores digits exactly as specified, which is why every serious application uses NUMERIC(10,2) or similar for prices, balances, and any financial calculation — never FLOAT or REAL.
Date/Time, Boolean, and UUID
PostgreSQL distinguishes TIMESTAMP (no time zone, stores a naive wall-clock value) from TIMESTAMPTZ (stores the instant in UTC internally and converts to the session's time zone on display). For almost any application that could ever run across time zones, TIMESTAMPTZ is the correct default — it avoids an entire class of "which time zone was this in" bugs. BOOLEAN is a true first-class type (TRUE/FALSE/NULL), not an integer alias. UUID is a native 128-bit type, commonly used for primary keys in distributed systems or when IDs must not be sequentially guessable; generate values with gen_random_uuid() (from the built-in pgcrypto-free pgcrypto or, in modern PostgreSQL, the core uuid-ossp/pgcrypto functions).
Examples
A table demonstrating correct type choices, including NUMERIC for money
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer TEXT NOT NULL,
total_price NUMERIC(10,2) NOT NULL,
is_paid BOOLEAN NOT NULL DEFAULT FALSE,
placed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO orders (customer, total_price)
VALUES ('Ravi Shah', 149.99);
SELECT customer, total_price, is_paid, placed_at FROM orders;
INSERT 0 1
customer | total_price | is_paid | placed_at
--------------+-------------+---------+-------------------------------
Ravi Shah | 149.99 | f | 2026-09-21 10:32:07.481212+00
Why FLOAT is unsafe for money, compared with NUMERIC
SELECT
0.1::float8 + 0.2::float8 AS float_sum,
0.1::numeric(4,2) + 0.2::numeric(4,2) AS numeric_sum;
float_sum | numeric_sum
---------------------+-------------
0.30000000000000004 | 0.30
Common Mistakes
- Using FLOAT or REAL for prices or account balances, which introduces binary rounding error into financial calculations.
- Choosing TIMESTAMP instead of TIMESTAMPTZ, then getting inconsistent results once the application or its users span more than one time zone.
- Adding VARCHAR(n) length limits everywhere out of habit from other databases, when PostgreSQL treats TEXT and VARCHAR identically in storage and performance.
- Storing boolean-like flags as INTEGER (0/1) instead of using the native BOOLEAN type, losing clarity and constraint checking.
Exercise
This exercise uses PostgreSQL types that SQLite does not have, so it needs a PostgreSQL server. Create a payments table with: a UUID primary key that the database generates; an amount stored exactly, with two decimal places; a required text currency; a boolean refunded that defaults to false; and a paid_at timestamp with time zone that defaults to the current time. Then insert a payment giving only the amount and currency, and return the whole row.
Expected result: one row, with a generated id, refunded false and paid_at set to the time of the insert.
-- TODO: create the payments table
-- TODO: insert amount 499.99 in INR and return the inserted row
Show hints
- The types are
UUID,NUMERIC(12, 2),TEXT,BOOLEANandTIMESTAMPTZ. gen_random_uuid()generates a UUID,now()gives the current time, andRETURNING *returns the inserted row.
Show solution
CREATE TABLE payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
amount NUMERIC(12, 2) NOT NULL,
currency TEXT NOT NULL,
refunded BOOLEAN NOT NULL DEFAULT false,
paid_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO payments (amount, currency)
VALUES (499.99, 'INR')
RETURNING *;
Quick quiz
1. Which type should hold money in PostgreSQL?
- A. REAL
- B. DOUBLE PRECISION
- C. NUMERIC
- D. TEXT
Show answer
C. NUMERIC stores decimal values exactly. The floating-point types store approximations.
2. What is the practical difference between TEXT and VARCHAR(n) in PostgreSQL?
- A. VARCHAR(n) enforces a maximum length; there is no performance difference
- B. TEXT is much slower
- C. TEXT cannot be indexed
- D. VARCHAR(n) is stored as numbers
Show answer
A. Both are stored in the same way, so TEXT is the usual choice unless a length limit is a real rule.
3. What does TIMESTAMPTZ store?
- A. The time and the name of the time zone
- B. A time without a date
- C. A text string
- D. A single point in time, kept in UTC and shown in the session's time zone
Show answer
D. The original zone is not kept; the value is converted to UTC on the way in.
Interview questions
What is the difference between TIMESTAMP and TIMESTAMPTZ?
Show answer
TIMESTAMP, without time zone, stores a date and time exactly as given, with no notion of zone, so the same value means different moments to different readers. TIMESTAMPTZ converts the input to UTC for storage and converts it to the session's time zone for display, so it always identifies one exact moment. TIMESTAMPTZ is the right choice for recording when something happened.
Why should money not be stored in a floating-point column?
Show answer
Floating-point types store binary approximations, and most decimal fractions, such as 0.1, cannot be represented exactly. Sums and comparisons then come out slightly wrong: 0.1 plus 0.2 is not exactly 0.3. Over many transactions the errors accumulate and totals fail to reconcile. NUMERIC stores decimal digits exactly and gives correct arithmetic; the alternative is an integer count of the smallest unit.
Key Points to Remember
- TEXT, VARCHAR(n), and CHAR(n) perform identically in PostgreSQL; TEXT is the idiomatic default.
- NUMERIC(precision, scale) is exact and required for money; FLOAT/REAL are binary approximations and unsuitable for currency.
- TIMESTAMPTZ stores an absolute instant in UTC and should be the default over the time-zone-naive TIMESTAMP.
- PostgreSQL has native BOOLEAN and UUID types — prefer them over integer workarounds.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs your SQL on SQLite in your browser. Standard SQL works; a few features that exist only in PostgreSQL or MySQL do not. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
These examples need a project environment on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering