SQLCREATE TABLEDDLtutorial

Что такое CREATE TABLE в SQL? Создание таблицы для начинающих

CREATE TABLE — это команда «создать новую таблицу». Простыми словами: какие колонки и какого типа, что такое NOT NULL, DEFAULT, PRIMARY KEY и FOREIGN KEY, и почему важно подумать о схеме сразу. С таблицами before/after, частыми ошибками новичков и тремя задачками.

5 мин чтенияСправочникSQL · CREATE TABLE · DDL · tutorial

CREATE TABLE — это команда «сделай мне новую таблицу с такими-то колонками и правилами». Это первая SQL-команда любого нового проекта: пока нет таблиц, и в INSERT нечего писать, и в SELECT неоткуда читать.

Представь, что таблица — это бланк анкеты. CREATE TABLE — это момент, когда ты решаешь, какие поля будут в анкете: «Имя», «Дата рождения», «Email». Какие из них обязательные, какие можно пропустить, какие должны быть только числами. Один раз продумал — и потом тысячи людей заполняют один и тот же бланк.

Зачем нужен CREATE TABLE

Любая работа с данными начинается с того, что данные где-то хранятся. CREATE TABLE — это место.

Хорошо продуманная таблица:

  • Защищает от мусора (NOT NULL, CHECK, UNIQUE).
  • Связана с другими через FOREIGN KEY, поэтому данные согласованы между собой.
  • Заранее заточена под нужные типы (числа — числами, даты — датами).

Плохая — наоборот, превращает любой SELECT в страдание: «у трети юзеров email пустой, у пятёрки age — строка, ещё у двоих age — '-1'».

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

CREATE TABLE users (
  id            SERIAL       PRIMARY KEY,
  email         VARCHAR(255) UNIQUE NOT NULL,
  display_name  VARCHAR(100) NOT NULL,
  created_at    TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

Что тут происходит, по строчкам:

  • CREATE TABLE users (...) — создаём таблицу с именем users.
  • id SERIAL PRIMARY KEY — колонка id, тип «автоинкремент» (1, 2, 3...), она же primary key.
  • email VARCHAR(255) UNIQUE NOT NULL — текст до 255 символов, уникальный, обязательный.
  • display_name VARCHAR(100) NOT NULL — текст до 100 символов, обязательный.
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() — дата-время с часовым поясом, обязательная, по умолчанию равна моменту создания строки.

Запятые — между колонками, не в конце. Точка с запятой — в самом конце.

Пример: бланк анкеты

Допустим, ты делаешь сайт для записи на стрижку. Нужна таблица записей:

CREATE TABLE appointments (
  id          SERIAL PRIMARY KEY,
  client_name VARCHAR(100) NOT NULL,
  phone       VARCHAR(20)  NOT NULL,
  service     VARCHAR(50)  NOT NULL,
  starts_at   TIMESTAMPTZ  NOT NULL,
  notes       TEXT
);

Теперь в неё можно писать:

INSERT INTO appointments (client_name, phone, service, starts_at)
VALUES ('Аня', '+79991234567', 'стрижка', '2026-05-10 14:00');

Получается:

id client_name phone service starts_at notes
1 Аня +79991234567 стрижка 2026-05-10 14:00:00 NULL

Поле notes опционально (нет NOT NULL) — ничего не написали, в базе оно NULL («пусто»).

id автоматически стал 1SERIAL сам считает. Следующий INSERT получит id = 2, и так далее.

Типы колонок — короткая шпаргалка

Категория Тип в Postgres Когда
Целое число INTEGER, BIGINT id, счётчики, возраст
Дробное (точное) NUMERIC(p, s) деньги — только это
Дробное (неточное) REAL, DOUBLE PRECISION физика, графика; не деньги
Текст TEXT, VARCHAR(n) в Postgres TEXTVARCHAR без лимита
Дата + время TIMESTAMPTZ для истории — всегда с TZ
Только дата DATE день рождения, дата записи
Логика BOOLEAN TRUE/FALSE
UUID UUID id когда не хочешь последовательные

Главные правила:

  • Деньги — NUMERIC(precision, scale). Никогда FLOAT — копейки потеряются. Например, NUMERIC(12, 2) хранит до 10 цифр до запятой и 2 после.
  • Дата+время — TIMESTAMPTZ (с timezone). Без TZ Postgres хранит «голое» время, и расчёты «через час» зависят от настроек сессии.
  • Текст — TEXT в Postgres лучше, чем VARCHAR без причины. Скорость одинаковая, лимит символов накладываешь только если бизнес требует (например, email VARCHAR(255) — потому что RFC).

Ограничения (constraints) — дисциплина для данных

Constraints — правила, которые БД проверяет на каждый INSERT и UPDATE. Если правило нарушено — операция упадёт с ошибкой.

CREATE TABLE orders (
  id           SERIAL PRIMARY KEY,                       -- PRIMARY KEY
  number       TEXT NOT NULL UNIQUE,                     -- NOT NULL + UNIQUE
  customer_id  INT NOT NULL REFERENCES customers(id),    -- FOREIGN KEY
  amount       NUMERIC(12,2) NOT NULL CHECK (amount > 0),-- CHECK
  status       TEXT NOT NULL DEFAULT 'pending'           -- DEFAULT
    CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'))
);

Разберём:

  • PRIMARY KEY — уникальный идентификатор строки. На таблицу один. По нему обычно ищут.
  • NOT NULL — нельзя записать NULL. Если попробуешь — ошибка.
  • UNIQUE — никакая другая строка не повторит это значение.
  • FOREIGN KEY (REFERENCES customers(id)) — значение должно существовать в customers.id. Защита от «осиротевших» заказов.
  • CHECK — произвольное условие на одной строке: amount > 0, status IN (...), email LIKE '%@%'.
  • DEFAULT — какое значение поставить, если в INSERT его не указали.

Эти правила экономят сотни часов отладки. БД сразу не пропустит мусор — ошибка прилетит в INSERT, а не через две недели в отчёте.

FOREIGN KEY — связь между таблицами

CREATE TABLE comments (
  id        SERIAL PRIMARY KEY,
  user_id   INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  post_id   INT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
  body      TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

REFERENCES users(id) — значение user_id должно существовать в users.id. Если попытаешься вставить user_id = 999, а такого юзера нет — Postgres откажет.

ON DELETE CASCADE — если удалить users строку, все её comments удалятся автоматически. Бывают другие варианты:

  • ON DELETE RESTRICT — запретит удаление родителя, пока есть дети.
  • ON DELETE SET NULL — обнулит user_id у комментариев, но оставит сам комментарий (требует, чтобы колонка была nullable).

Важно: FK не создаёт индекс на дочерней колонке. Чтобы DELETE FROM users WHERE id = 5 не делал full scan по comments, нужен ручной:

CREATE INDEX comments_user_id_idx ON comments(user_id);

Это частая ловушка — все думают, что FK = индекс. Это не так.

Частые ошибки новичков

1. Деньги во FLOAT. price FLOAT — потеря копеек на каждом расчёте. Только NUMERIC(p, s). Это не теоретическая проблема: 0.1 + 0.2 во float-арифметике даёт 0.30000000000000004.

2. Слишком широкие типы. BIGINT для возраста (хватит SMALLINT), TEXT для двухбуквенного кода страны (хватит VARCHAR(2)). Не критично, но на больших таблицах экономит гигабайты.

3. Отсутствие FK там, где они логичны. comments.user_id INT NOT NULL без REFERENCES users(id) — это «договорённость», не гарантия. Когда-нибудь у тебя будет comment с user_id = 999, а такого юзера нет, и кто-то спишет полдня на «откуда».

4. Слишком много NULL-able колонок. NULL — это «нет значения». Куча NULL обычно означает, что схема плохо продумана. Возможно, надо разделить на разные таблицы или использовать enum/jsonb. NULL ломают = (нужно IS NULL), агрегаты (COUNT пропускает их), индексы (Postgres индексирует, но плохо).

5. Имена с пробелами и кавычками. CREATE TABLE "User Profiles" (...) — формально работает, но любой запрос требует кавычек вокруг имени. Используй snake_case (user_profiles).

6. Забыл DEFAULT NOW() для created_at. В каждом INSERT надо помнить про дату — забудешь раз, и в строке NULL. С дефолтом — Postgres сам подставит, ничего не надо передавать.

Мини-резюме

  • CREATE TABLE объявляет, какие колонки и какого типа будут в таблице. Это «бланк анкеты».
  • Колонки разделяются запятыми. Точка с запятой — в самом конце.
  • NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY, CHECK, DEFAULT — пять основных способов навести дисциплину в данных. Используй заранее, чтобы потом не страдать.
  • Деньги — NUMERIC. Дата+время — TIMESTAMPTZ. Текст — TEXT.
  • FK не создаёт индекс автоматически — для производительности добавь руками.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр