PostgreSQL
PostgreSQL Data Types
Every column in a PostgreSQL table must have a data type. The data type tells PostgreSQL what kind of value that column will hold — a number, a piece of text, a date, a true/false flag. Choosing the right data type is one of the most important decisions you make when designing a table. The right choice saves storage, prevents bad data from getting in, and makes your queries faster. This lesson covers every major data type in PostgreSQL with clear examples so you know exactly when to use each one.
Numeric Types
PostgreSQL has several numeric types depending on whether you need whole numbers or decimals, and how large those numbers can get. Choosing the smallest type that fits your data keeps storage lean and arithmetic fast.
-- Integer types — for whole numbers with no decimal part
CREATE TABLE numeric_demo (
small_num SMALLINT, -- -32,768 to 32,767
normal_num INTEGER, -- -2.1 billion to 2.1 billion (most common)
big_num BIGINT, -- very large whole numbers
auto_id SERIAL, -- auto-incrementing integer (great for primary keys)
big_auto_id BIGSERIAL -- auto-incrementing bigint
);
-- Decimal types — for numbers with a fractional part
CREATE TABLE decimal_demo (
price NUMERIC(10, 2), -- exact: up to 10 digits, 2 after decimal — use for money
rating DECIMAL(3, 1), -- exact: e.g. 4.5 — DECIMAL and NUMERIC are identical
temperature REAL, -- approximate 6 decimal digits — use for scientific data
measurement DOUBLE PRECISION -- approximate 15 decimal digits — more precise than REAL
);
CREATE TABLE
Use INTEGER for IDs, counts, and quantities. Use NUMERIC(p,s) for money and anything where exact decimal precision matters — never use REAL or DOUBLE PRECISION for financial values because they are approximate and can introduce rounding errors. Use SERIAL or BIGSERIAL for auto-incrementing primary key columns.
Text Types
PostgreSQL gives you three main ways to store text. Unlike some databases that charge a performance penalty for using TEXT, in PostgreSQL all three types are stored the same way internally — so the choice is mostly about enforcing length limits.
CREATE TABLE text_demo (
product_code CHAR(8), -- fixed length — always exactly 8 characters, padded with spaces
first_name VARCHAR(50), -- variable length — up to 50 characters, no padding
bio TEXT, -- unlimited length — no maximum, stores any size text
status VARCHAR(20) -- common for short controlled values like 'active', 'pending'
);
-- Inserting text values
INSERT INTO text_demo (product_code, first_name, bio, status)
VALUES ('PROD0001', 'Alice', 'Alice is a senior engineer at Dataplexa.', 'active');
SELECT * FROM text_demo;
--------------+------------+------------------------------------------+--------
PROD0001 | Alice | Alice is a senior engineer at Dataplexa. | active
In practice most developers use TEXT for anything open-ended like descriptions, notes, and content. Use VARCHAR(n) when you want to enforce a maximum length — for example, a status field or a product code. Use CHAR(n) only when every value is guaranteed to be exactly the same length — like a fixed country code or a standardised identifier.
Boolean Type
A boolean column stores only two values — true or false. PostgreSQL is flexible about how you write these values when inserting data. All of the following are valid.
CREATE TABLE boolean_demo (
product_name TEXT,
is_active BOOLEAN,
is_featured BOOLEAN
);
-- PostgreSQL accepts many formats for true and false
INSERT INTO boolean_demo VALUES ('Laptop', TRUE, TRUE);
INSERT INTO boolean_demo VALUES ('Monitor', FALSE, TRUE);
INSERT INTO boolean_demo VALUES ('Webcam', 't', 'f'); -- t and f are also valid
INSERT INTO boolean_demo VALUES ('Keyboard','yes', 'no'); -- yes and no work too
SELECT product_name, is_active, is_featured
FROM boolean_demo
WHERE is_active = TRUE;
--------------+-----------+-------------
Laptop | t | t
Webcam | t | f
Date and Time Types
Handling dates and times correctly is critical in any real application. PostgreSQL has a rich set of date and time types, and critically it supports timezones — something that trips up many developers using simpler databases.
CREATE TABLE datetime_demo (
event_name TEXT,
event_date DATE, -- date only: 2024-03-15
event_time TIME, -- time only: 14:30:00
created_at TIMESTAMP, -- date + time, no timezone
updated_at TIMESTAMPTZ, -- date + time WITH timezone (recommended)
duration INTERVAL -- a span of time: '2 hours', '30 days'
);
INSERT INTO datetime_demo VALUES (
'Product Launch',
'2024-06-01',
'09:00:00',
'2024-06-01 09:00:00',
'2024-06-01 09:00:00-05', -- -05 = US Central time
'2 hours 30 minutes'
);
-- Get the current date and time
SELECT CURRENT_DATE, CURRENT_TIME, NOW();
--------------+--------------+-------------------------------
2024-06-01 | 14:22:05 | 2024-06-01 14:22:05.123456+00
Always use TIMESTAMPTZ (timestamp with timezone) over plain TIMESTAMP for any column that records when something happened — it stores the moment in UTC and converts to any timezone on retrieval. Use DATE when the time of day is irrelevant. Use INTERVAL for storing durations like subscription lengths or shipping times.
UUID Type
A UUID (Universally Unique Identifier) is a 128-bit identifier that is practically guaranteed to be unique across all systems in the world — no coordination needed. It looks like a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11. UUIDs are commonly used as primary keys in distributed systems where multiple servers insert data simultaneously and you cannot rely on a single auto-incrementing sequence.
-- Enable the uuid extension (needed to generate UUIDs)
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE TABLE sessions (
session_id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id INTEGER,
started_at TIMESTAMPTZ DEFAULT NOW()
);
INSERT INTO sessions (user_id) VALUES (1), (2), (3);
SELECT * FROM sessions;
--------------------------------------+---------+-------------------------------
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 | 1 | 2024-06-01 14:22:05.123456+00
b3f2e1d0-8c7a-4f5e-9d2b-1c4a5e6f7890 | 2 | 2024-06-01 14:22:05.456789+00
c4d3e2f1-9b8a-5g6f-0e3c-2d5b6f7g8901 | 3 | 2024-06-01 14:22:05.789012+00
JSON and JSONB
PostgreSQL lets you store JSON directly in a column — making it possible to handle flexible, semi-structured data inside a relational database. There are two variants: JSON stores the raw text as-is, and JSONB stores it in a parsed binary format that is faster to query and can be indexed. Always use JSONB unless you specifically need to preserve the original formatting of the JSON text.
CREATE TABLE products_json (
id SERIAL PRIMARY KEY,
name TEXT,
attributes JSONB -- stores flexible product attributes
);
INSERT INTO products_json (name, attributes) VALUES
('Laptop', '{"brand": "Dell", "ram_gb": 16, "storage_gb": 512, "color": "silver"}'),
('Monitor', '{"brand": "LG", "size_in": 27, "resolution": "4K", "color": "black"}');
-- Query a specific field inside the JSON using the -> operator
SELECT name, attributes -> 'brand' AS brand FROM products_json;
-- Get the value as plain text using ->> operator
SELECT name, attributes ->> 'color' AS color FROM products_json;
-- Filter by a JSON field value
SELECT name FROM products_json WHERE attributes ->> 'brand' = 'Dell';
name | brand
---------+-------
Laptop | "Dell"
Monitor | "LG"
-- attributes ->> 'color':
name | color
---------+--------
Laptop | silver
Monitor | black
-- Filter result:
name
--------
Laptop
Array Type
PostgreSQL lets a single column hold an array — a list of values of the same type. This is useful for storing things like tags, phone numbers, or a list of skills without creating a separate table.
CREATE TABLE employees_arr (
id SERIAL PRIMARY KEY,
name TEXT,
skills TEXT[], -- array of text values
scores INTEGER[] -- array of integers
);
INSERT INTO employees_arr (name, skills, scores) VALUES
('Alice', ARRAY['SQL', 'Python', 'PostgreSQL'], ARRAY[95, 88, 92]),
('Bob', ARRAY['Excel', 'SQL'], ARRAY[78, 85]);
-- Query: find employees who know SQL
SELECT name FROM employees_arr WHERE 'SQL' = ANY(skills);
-- Access the first element of the array (arrays are 1-indexed in PostgreSQL)
SELECT name, skills[1] AS primary_skill FROM employees_arr;
name
-------
Alice
Bob
-- skills[1] result:
name | primary_skill
-------+---------------
Alice | SQL
Bob | Excel
Choosing the Right Type — Quick Guide
| What You Are Storing | Best Type | Why |
|---|---|---|
| Primary key / ID | SERIAL / BIGSERIAL | Auto-increments — no manual ID management |
| Money / price | NUMERIC(10,2) | Exact — no floating point rounding errors |
| Names, emails, short text | VARCHAR(n) | Enforces a max length limit |
| Descriptions, notes, content | TEXT | No length limit — flexible for any size |
| Yes/No flags | BOOLEAN | Cleaner than storing 0/1 or 'Y'/'N' |
| Date only | DATE | Birthdays, deadlines, event dates |
| Date + time of event | TIMESTAMPTZ | Timezone-aware — always correct globally |
| Distributed unique IDs | UUID | Unique across all systems — no collisions |
| Flexible structured data | JSONB | Queryable, indexable JSON storage |
| List of values in one column | TEXT[] / INTEGER[] | Stores multiple values without a join table |
🧪 Practice Questions
Answer based on what you learned in this lesson.
1. Which data type should you always use for storing prices and financial values to avoid rounding errors?
2. Which data type automatically generates a unique incrementing integer — commonly used for primary keys?
3. Which JSON data type stores data in binary format that can be indexed and queried faster?
4. Which timestamp type should you always use for recording when events happened to ensure timezone correctness?
5. Arrays in PostgreSQL are indexed starting at which number?
🎯 Quiz — Test Your Understanding
Q1. You are building an e-commerce checkout system and need to store product prices accurately. Which type do you choose?
Q2. Which JSONB operator extracts a field value as plain text rather than JSON?
Q3. What is the key difference between CHAR(n) and VARCHAR(n)?
Q4. Which function checks whether a value exists anywhere inside a PostgreSQL array column?
Q5. You are building a system where multiple servers insert records simultaneously and need unique IDs without any central coordination. Which type is best?
Next up: Putting data types to use — creating your first real tables in PostgreSQL.