Модуль 10 · Урок 37 із 58

PostgreSQL schema: types, keys, constraints і migrations

База даних починається не з ORM-моделі, а з контракту: які факти зберігаємо, хто ними володіє, що є ідентичністю, які стани заборонені та як змінити схему без втрати даних.

PostgreSQLSchemaConstraintsMigrations

Безпечна PostgreSQL-практика модуля 10

Пакет містить synthetic schema/seed/query files, parameterized Python repository, contract tests і read-only verifier. Локальні тести не виконують PostgreSQL; integration run проводиться лише у власній disposable database без персональних чи production-даних, credentials і production endpoints.

Завантажити практичний пакет →

Таблиця зберігає факти, а не екран

Relational schema моделює сутності, атрибути та зв’язки незалежно від поточного UI. Для навчального issue tracker маємо projects і tickets: project існує самостійно, ticket належить рівно одному project. Це рішення визначає foreign key, а не розташування полів у формі.

IdentityPrimary key однозначно ідентифікує row.
ReferenceForeign key не дозволяє посилання на відсутній parent.
DomainТип і CHECK звужують допустимі значення.
LifecycleMigration описує контрольовану зміну contract.

Тип — частина доменного контракту

text, integer, boolean, date, timestamp with time zone та інші типи мають різні операції й межі. Не слід зберігати дату, суму або boolean як довільний text лише тому, що Python зможе його прочитати. Тип БД відсікає некоректні стани до того, як вони розійдуться між застосунками.

ПолеПриклад contractПоганий shortcut
created_attimestamptz NOT NULLЛокальний час у text
prioritysmallint CHECK (priority BETWEEN 1 AND 5)Будь-який integer
statusCHECK із контрольованим наборомНеперевірений label із UI
project_idForeign key до projectНазва project як зв’язок

NULL не означає порожній рядок

NULL означає відсутність відомого значення. У SQL порівняння з NULL має three-valued logic, тому для перевірки використовують IS NULL або IS NOT NULL. Якщо поле обов’язкове за доменом, ставимо NOT NULL; якщо значення справді може бути невідомим, описуємо цей стан і не підміняємо його '', 0 чи магічною датою.

Default не ремонтує поганий model

Default корисний, коли БД має право визначити значення, наприклад created_at DEFAULT now(). Він не повинен приховувати пропущений обов’язковий input або змінювати бізнес-семантику без явного рішення.

Constraints захищають усіх клієнтів

Validation у Python дає зручне повідомлення користувачеві, а constraint у PostgreSQL забезпечує останню межу цілісності для CLI, API, import job і майбутніх застосунків. Primary key, UNIQUE, foreign key, NOT NULL і CHECK виконують різні ролі; один не замінює інший.

CREATE TABLE tickets (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    project_id bigint NOT NULL REFERENCES projects(id),
    title text NOT NULL CHECK (length(trim(title)) BETWEEN 3 AND 120),
    status text NOT NULL CHECK (status IN ('open', 'closed')),
    created_at timestamptz NOT NULL DEFAULT now()
);

Delete policy треба обрати свідомо: RESTRICT, CASCADE або SET NULL змінюють доменну поведінку, а не лише синтаксис.

Migration — версійований перехід

Schema change планують як precondition, forward step, data backfill, validation, application rollout і rollback/forward-fix strategy. Для великої таблиці небезпечно мовчки додати важкий rewrite або довге блокування. У навчальному пакеті migration працює лише в окремій локальній schema; production connection string заборонений.

  1. Зафіксуйте поточний schema contract і залежні queries.
  2. Додайте сумісний nullable column або нову structure.
  3. Backfill виконуйте контрольованими batches із перевіркою counts.
  4. Увімкніть constraint після очищення старих rows.
  5. Тільки потім видаліть legacy path у наступному release.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію та Python Wiki.

Практична перевірка · урок 37 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. У таблиці tickets кожен запис повинен належати наявному project. Який database contract найкраще це гарантує для всіх клієнтів?
2. Поле priority допускає лише цілі значення від 1 до 5. Яка комбінація найкраще виражає цей contract у PostgreSQL?
3. Чому для optional due_at не варто зберігати магічну дату 1970-01-01 замість NULL?