CREATE TABLE: создание таблиц

CREATE TABLE: создание таблиц

До этого модуля вы работали с уже существующими таблицами. Теперь — DDL (Data Definition Language): команды для создания и изменения структуры базы данных. CREATE TABLE — фундамент: именно с него начинается любая база данных.

Базовый синтаксис CREATE TABLE

CREATE TABLE имя_таблицы (
    колонка1 тип_данных [ограничения],
    колонка2 тип_данных [ограничения],
    ...
);

Простой пример:

CREATE TABLE customers (
    id    SERIAL      PRIMARY KEY,
    name  TEXT        NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    city  TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

Каждая строка в скобках описывает одну колонку: имя, тип данных, и опционально — ограничения.

CREATE TABLE IF NOT EXISTS

При повторном выполнении CREATE TABLE без условия — ошибка:

CREATE TABLE customers (...);
-- ERROR: relation "customers" already exists

С IF NOT EXISTS — команда пропускается, если таблица уже существует:

CREATE TABLE IF NOT EXISTS customers (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);
-- NOTICE: relation "customers" already exists, skipping

Это стандартный приём в миграционных скриптах — делает их идемпотентными.

Имена таблиц и колонок

PostgreSQL хранит имена в нижнем регистре. Правила именования:

  • Можно использовать буквы, цифры, подчёркивание
  • Должно начинаться с буквы или подчёркивания
  • Рекомендуется snake_case: order_items, created_at, user_id
  • Зарезервированные слова (например, user, order) лучше избегать в качестве имён; если необходимо — заключать в двойные кавычки: "user"
-- Рекомендуется
CREATE TABLE order_items (...);

-- Не рекомендуется (зарезервированное слово)
CREATE TABLE "order" (...);

Типы данных: обзор

Каждая колонка имеет тип данных — что именно в ней хранится. Основные категории:

Целые числа:

  • SMALLINT — от -32768 до 32767 (2 байта)
  • INTEGER (или INT) — от -2 млрд до 2 млрд (4 байта)
  • BIGINT — очень большие числа (8 байт)
  • SERIALINTEGER с автоинкрементом (псевдотип для PostgreSQL)

Дробные числа:

  • NUMERIC(precision, scale) — точные дробные (денежные суммы, расчёты)
  • REAL, DOUBLE PRECISION — числа с плавающей точкой (научные расчёты)

Строки:

  • TEXT — строка произвольной длины (рекомендуется в PostgreSQL)
  • VARCHAR(n) — строка максимальной длины n
  • CHAR(n) — строка фиксированной длины n (дополняется пробелами)

Дата и время:

  • DATE — только дата: 2024-03-15
  • TIME — только время: 14:30:00
  • TIMESTAMP — дата + время без часового пояса
  • TIMESTAMPTZ — дата + время с часовым поясом (рекомендуется)

Другие:

  • BOOLEANTRUE/FALSE
  • UUID — уникальный идентификатор
  • JSON/JSONB — JSON-данные

Подробнее о типах данных — в следующем уроке.

Первичный ключ и автоинкремент

-- Старый стиль (PostgreSQL-специфичный)
id SERIAL PRIMARY KEY

-- Современный стиль (SQL-стандарт, PostgreSQL 10+)
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY

Оба создают колонку id, которая автоматически получает следующее значение при каждой вставке. SERIAL — более короткий, GENERATED ALWAYS AS IDENTITY — стандартный и более явный.

Для очень больших таблиц используйте BIGSERIAL или BIGINT GENERATED ALWAYS AS IDENTITY.

Ограничения на уровне колонки

Ограничения (CONSTRAINTS) определяют правила для значений в колонке:

CREATE TABLE products (
    id          SERIAL  PRIMARY KEY,
    title       TEXT    NOT NULL,           -- нельзя NULL
    sku         VARCHAR(50) UNIQUE NOT NULL, -- уникальный и обязательный
    price       NUMERIC(10, 2) NOT NULL CHECK (price >= 0),  -- неотрицательная цена
    category    TEXT,                       -- может быть NULL
    in_stock    BOOLEAN DEFAULT TRUE,       -- по умолчанию TRUE
    created_at  TIMESTAMPTZ DEFAULT NOW()   -- текущее время по умолчанию
);
  • NOT NULL — запрещает пустые значения
  • UNIQUE — значение должно быть уникальным в таблице
  • CHECK (условие) — произвольное условие на значение
  • DEFAULT значение — значение по умолчанию при вставке

Подробнее об ограничениях — в уроке 9-4.

Ограничения на уровне таблицы

Некоторые ограничения касаются нескольких колонок — их удобнее задавать после перечисления колонок:

CREATE TABLE order_items (
    id         SERIAL,
    order_id   INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity   INTEGER NOT NULL CHECK (quantity > 0),
    unit_price NUMERIC(10, 2) NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (order_id)   REFERENCES orders(id)   ON DELETE CASCADE,
    FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE RESTRICT,
    UNIQUE (order_id, product_id)  -- одинаковый товар в одном заказе — только раз
);

Составной UNIQUE (order_id, product_id) — пример ограничения на уровне таблицы.

Проверь себя: зачем здесь составной UNIQUE (order_id, product_id)? Что произошло бы без него?

Комментарии к таблице и колонкам

PostgreSQL позволяет добавлять документацию прямо в схему:

COMMENT ON TABLE customers IS 'Клиенты интернет-магазина';
COMMENT ON COLUMN customers.email IS 'Основной email для уведомлений, уникальный';

Комментарии видны в psql через \d+ customers и в GUI-клиентах. Это живая документация схемы — обновляется вместе с таблицей.

Схемы (schemas) в PostgreSQL

Таблицы в PostgreSQL живут внутри схемы — пространства имён. По умолчанию используется схема public:

CREATE TABLE public.customers (...);  -- явно указана схема
CREATE TABLE customers (...);          -- то же самое (default = public)

Схемы полезны для организации больших баз: app.users, analytics.reports, legacy.old_data. Начинающему разработчику достаточно знать, что схема public есть по умолчанию.

Практический пример: схема блога

CREATE TABLE users (
    id         BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username   VARCHAR(50)  UNIQUE NOT NULL,
    email      VARCHAR(255) UNIQUE NOT NULL,
    bio        TEXT,
    created_at TIMESTAMPTZ  DEFAULT NOW()
);

CREATE TABLE posts (
    id         BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    author_id  BIGINT      NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title      TEXT        NOT NULL,
    content    TEXT        NOT NULL,
    published  BOOLEAN     DEFAULT FALSE,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE comments (
    id         BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    post_id    BIGINT NOT NULL REFERENCES posts(id)   ON DELETE CASCADE,
    author_id  BIGINT NOT NULL REFERENCES users(id)   ON DELETE CASCADE,
    content    TEXT   NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

Три таблицы, связи через внешние ключи, каскадное удаление. Это типичная схема простого блога.

CREATE TABLE AS: создать из запроса

Альтернативный вариант, который мы упоминали в модуле 8 — создать таблицу из результата SELECT:

CREATE TABLE top_customers AS
SELECT customer_id, SUM(amount) AS total
FROM   orders
WHERE  status = 'completed'
GROUP BY customer_id
ORDER BY total DESC
LIMIT 100;

Новая таблица top_customers получает структуру из колонок SELECT и сразу наполняется данными. Ограничения и индексы не копируются — только данные и типы. Удобно для быстрого анализа или создания материализованных снимков.

Временные таблицы

Иногда нужна таблица только на время сессии:

CREATE TEMPORARY TABLE temp_results (
    id   INTEGER,
    score NUMERIC
);

TEMP (или TEMPORARY) — таблица автоматически удаляется при завершении сессии подключения. Полезно для промежуточных вычислений в сложных аналитических запросах, когда CTE недостаточно.

Краткий итог

  • CREATE TABLE name (col type [constraints], ...) — создаёт новую таблицу
  • IF NOT EXISTS — пропускает создание если таблица уже есть (идемпотентность)
  • Типы данных: SERIAL/BIGSERIAL для PK, TEXT для строк, NUMERIC для денег, TIMESTAMPTZ для дат
  • Ограничения на уровне колонки: NOT NULL, UNIQUE, CHECK, DEFAULT, PRIMARY KEY
  • Составные ограничения задаются на уровне таблицы

Порядок создания таблиц при связях

Если orders ссылается на customers через внешний ключ, customers должна быть создана первой:

-- Правильный порядок
CREATE TABLE customers (...);
CREATE TABLE orders (
    ...,
    customer_id INTEGER REFERENCES customers(id)  -- customers уже существует
);

-- Ошибка: orders создаётся до customers
CREATE TABLE orders (customer_id INTEGER REFERENCES customers(id));  -- customers не существует!

PostgreSQL проверяет ссылку REFERENCES немедленно при создании. Поэтому в миграционных скриптах всегда соблюдайте порядок: сначала «родительские» таблицы, потом «дочерние».

Что дальше

Вы создали первую таблицу. Следующий урок подробнее разберёт типы данных: как выбрать правильный тип для чисел, строк и дат, чтобы не создавать проблем в будущем.

Попробуйте интерактивную версию

Практические задачи, квизы и AI-наставник — бесплатный старт без карты

Перейти к практике