Skip to content
← Production DevOps & SRE

Learning bite

Tables, data types, and constraints

Model a small SQL fixture and distinguish database guarantees from application conventions.

  • PostgreSQL
Documentation reviewed2026-10-01 · 3 min read
  1. Why
  2. Predict
  3. Try
  4. Inspect
  5. Explain
  6. Save evidence

Predict before you try

Does one successful query establish the data invariant and the application’s recovery behaviour?

Try it: Create the shared fixture once →
On this page

Make rules visible in the schema

An application check can be bypassed by another writer. Put rules the database can enforce into constraints. A primary key identifies a row; a foreign key checks a reference; a unique constraint rejects duplicate keys; a check constraint limits the values a row can contain. NOT NULL is separate: a check that evaluates to unknown does not reject a null. Constraint behavior↗.

Use integer cents for this single-currency fixture. For values that need an exact decimal scale, evaluate numeric; binary floating point is unsuitable when exact decimal arithmetic is required. Specify units and bounds. We use timestamptz for instants: display depends on the session time zone, and the original named zone is not retained. Numeric types↗, date/time types↗.

Create the shared fixture once

Start the container from the previous bite. Save this as schema.sql in your lab directory. It intentionally fails if the schema already exists so it cannot silently replace your work.

sql
BEGIN;
CREATE SCHEMA study;
CREATE TABLE study.accounts (
  account_id bigint PRIMARY KEY,
  label text NOT NULL UNIQUE
);
CREATE TABLE study.entries (
  entry_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  account_id bigint NOT NULL REFERENCES study.accounts(account_id),
  request_key text NOT NULL,
  amount_cents bigint NOT NULL CHECK (amount_cents <> 0),
  created_at timestamptz NOT NULL,
  note text,
  UNIQUE (account_id, request_key)
);
INSERT INTO study.accounts VALUES (1, 'demo-a'), (2, 'demo-b'), (3, 'demo-empty');
INSERT INTO study.entries (account_id, request_key, amount_cents, created_at, note)
VALUES
  (1, 'r-1', 1000, '2026-09-01T09:00:00Z', 'opening fixture'),
  (1, 'r-2', -250, '2026-09-01T09:05:00Z', NULL),
  (2, 'r-3', 2000, '2026-09-01T10:00:00Z', NULL);
COMMIT;
bash
docker exec -i learnwithsk-postgres psql -X -v ON_ERROR_STOP=1 \
  -U postgres -d study < schema.sql
docker exec learnwithsk-postgres psql -X -U postgres -d study \
  -c '\d study.entries'

The fixture has three accounts and three entries. Its signed entries are a teaching device, not double-entry accounting. It permits a negative total and has no currency conversion or authorization model. No real customer information belongs here.

The account label lives in one table, with references from entries. This lets you update the label in one place without leaving inconsistent copies on individual entries. Normalization helps represent dependencies; it does not mean every field must become its own table.

Test one rejected write

In interactive psql, deliberately try a zero amount, then roll back:

sql
BEGIN;
INSERT INTO study.entries (account_id, request_key, amount_cents, created_at)
VALUES (1, 'invalid-zero', 0, now());
ROLLBACK;

Expect a check-constraint error and an unchanged entry count. In separate transactions, predict the results for an unknown account, a null request key, and a repeated (account_id, request_key). Roll back each trial. Sequence values can have gaps after failed or rolled-back inserts; IDs are not row counts.

Checkpoint

Explain which rule prevents each invalid row. A uniqueness rule is only part of an idempotency contract: the application must also return the prior result and detect a different payload sent under the same key. Do not add these fixture constraints to MicroBank without checking its real schema and migration plan.

Check the rejected rows

An unknown account violates the foreign key. A null request key violates NOT NULL. Repeating account 1 plus r-1 violates the composite unique constraint, even with a different amount. A zero amount violates the check. After each deliberate error, rollback ends the failed transaction; otherwise subsequent statements in that transaction remain blocked by its failed state.

Notice a limit: the same request key on a different account is permitted by this fixture's composite rule. The database also does not automatically return the earlier operation to a retrying API caller. State that application contract separately. Next, read the valid rows with SQL before interpreting their totals.

Prove what you learned

Inspect the result

Record database/schema identity, relevant query/assertion and the expected versus actual result.

Explain it in your own words

Explain why your observation supports the stated outcome, and name one thing it does not prove.

Try a small variation

Use one extra row, rejected input or second-session observation supported by this fixture; preserve the baseline.

Your evidence should show

  • Record the context/revision or label the work as a paper or prepared fixture.
  • Compare the prediction with the actual result from “Create the shared fixture once”.
  • Record database/schema identity, relevant query/assertion and the expected versus actual result.
  • Explain the transfer challenge and retain the same verification question.
Hint

Separate database identity, constraint/transaction behaviour, query result and restore scope.

Answer guide: compare after predicting

No. Verify the declared data rule and its negative case, keeping the recovery claim within the tested scope.

Compare this with your actual observation. A paper answer is useful reasoning practice; record execution separately.

Recall the MicroBank saved case and the distinction between persistence and backup. Revisit the previous lesson.

Save the prediction, result and explanation ↓

Your notes and evidence

Record observations, questions, or links to your work. Keep credentials out of your notes.

Loading saved progress…

Back up or restore this path

Progress and notes stay in this browser. A backup contains only this learning path.