Database Basics

Before choosing a database or writing a query, you need to understand the relational model — how data is structured, how tables relate to each other, and what rules the engine enforces automatically. These fundamentals apply whether you end up using PostgreSQL, MySQL, or any other relational database.

Tables

A relational database organises data into tables (also called relations). Each table represents a single entity type — users, orders, products. A table is made up of:

  1. Columns (fields / attributes) — define the structure. Each column has a name and a fixed data type. The schema is declared once and enforced on every row.
  2. Rows (records / tuples) — the actual data. Every row conforms to the column schema of its table.
  3. Primary key — one or more columns that uniquely identify each row. No two rows in a table may share the same primary key value. A surrogate key (auto-incremented integer or UUID) is common; a natural key (e.g. email) works when it is truly unique and stable.

Common data types

TypeUse forExamples
INT / BIGINTWhole numbers, IDs, countersuser_id, quantity, age
VARCHAR(n)Variable-length strings up to n charsusername, email, country_code
TEXTUnbounded stringspost body, description
BOOLEANTrue / false flagsis_active, email_verified
DECIMAL(p, s)Exact fixed-point numbersprice, balance (never use FLOAT for money)
TIMESTAMPDate and time, usually UTCcreated_at, updated_at
UUIDGlobally unique identifiersSurrogate PKs in distributed systems

Relationships

Tables rarely exist in isolation. A foreign key is a column in one table that references the primary key of another, creating a link between the two. There are three fundamental relationship types:

One-to-many

The most common relationship. One row in table A relates to many rows in table B. The foreign key lives on the "many" side.

users orders ────── ────────────────── id (PK) ←── user_id (FK) name id (PK) total

One user can have many orders. Each order belongs to exactly one user.

Many-to-many

When rows on both sides can relate to multiple rows on the other side, a direct foreign key is not enough. A junction table (also called a bridge or associative table) sits between the two and holds a pair of foreign keys.

students enrollments courses ──────── ─────────────── ─────── id (PK) ←── student_id (FK) id (PK) name course_id (FK) ──→ title

A student can enrol in many courses; a course can have many students. The junction table enrollments can carry its own attributes too — such as enrolled_at or grade.

One-to-one

Each row in table A maps to at most one row in table B. Used to split a table when some columns are rarely accessed (vertical partitioning) or when two entities have different access-control requirements.

users user_profiles ────── ───────────────────────── id (PK) ←── user_id (FK, UNIQUE) email bio avatar_url

Constraints

Constraints are rules enforced by the database engine at write time. They are the first line of defence for data integrity — they prevent invalid data from ever reaching the disk, regardless of application-level bugs.

ConstraintWhat it enforcesExample
PRIMARY KEYUniqueness + NOT NULL on the identifier column(s)id SERIAL PRIMARY KEY
FOREIGN KEYReferenced row must exist in the parent tableuser_id INT REFERENCES users(id)
UNIQUENo two rows may share the same value in this columnemail VARCHAR UNIQUE
NOT NULLColumn must always have a valuecreated_at TIMESTAMP NOT NULL
CHECKValue must satisfy a boolean expressionCHECK (price > 0)
DEFAULTProvides a value when none is supplied on insertis_active BOOLEAN DEFAULT true

ON DELETE behaviour

When a parent row is deleted, the database must decide what to do with its child rows. This is configured on the foreign key:

  1. RESTRICT / NO ACTION — reject the delete if any child rows exist. The default in most databases. Safe but requires manual cleanup.
  2. CASCADE — automatically delete all child rows. Convenient for tightly-coupled entities (e.g. deleting a post should delete its comments), but dangerous if applied carelessly.
  3. SET NULL — set the foreign key column to NULL in all child rows. Useful when the child can exist independently (e.g. a comment orphaned after a user account is deleted).
  4. SET DEFAULT — set the foreign key to its default value. Rarely used.

SQL vs NoSQL

The first fork in every data layer decision: relational or non-relational. The answer depends on your data model, consistency requirements, and scale targets.

SQL (Relational)NoSQL
Data modelTables with rows and columns, strict schemaDocuments, key-value, wide-column, or graph — flexible schema
ConsistencyACID transactionsUsually eventual consistency (BASE)
JoinsFirst-class supportTypically avoided — denormalize instead
ScalingVertical primarily; horizontal via read replicas or shardingHorizontal by design
Best forComplex relationships, strong consistency, reportingHigh write throughput, flexible schema, massive scale
ExamplesPostgreSQL, MySQL, SQLiteMongoDB, Cassandra, DynamoDB, Redis

NoSQL data models

  1. Document store (MongoDB, Firestore) — stores JSON-like documents. Good for nested, variable-shape data. Queries within a document are fast; cross-document joins are expensive.
  2. Key-value store (Redis, DynamoDB) — the simplest model: a key maps to a blob. Extremely fast for lookups by key; poor for range scans or complex queries.
  3. Wide-column store (Cassandra, HBase) — rows identified by a partition key, columns grouped into families. Optimised for writing and reading entire rows at high throughput across a distributed cluster.
  4. Graph database (Neo4j) — nodes and edges with properties. Native support for relationship traversal. Ideal for social graphs, recommendation engines, and fraud detection — anywhere the connections between data matter as much as the data itself.