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 байт)SERIAL—INTEGERс автоинкрементом (псевдотип для PostgreSQL)
Дробные числа:
NUMERIC(precision, scale)— точные дробные (денежные суммы, расчёты)REAL,DOUBLE PRECISION— числа с плавающей точкой (научные расчёты)
Строки:
TEXT— строка произвольной длины (рекомендуется в PostgreSQL)VARCHAR(n)— строка максимальной длины nCHAR(n)— строка фиксированной длины n (дополняется пробелами)
Дата и время:
DATE— только дата:2024-03-15TIME— только время:14:30:00TIMESTAMP— дата + время без часового поясаTIMESTAMPTZ— дата + время с часовым поясом (рекомендуется)
Другие:
BOOLEAN—TRUE/FALSEUUID— уникальный идентификатор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 немедленно при создании. Поэтому в миграционных скриптах всегда соблюдайте порядок: сначала «родительские» таблицы, потом «дочерние».
Что дальше
Вы создали первую таблицу. Следующий урок подробнее разберёт типы данных: как выбрать правильный тип для чисел, строк и дат, чтобы не создавать проблем в будущем.