SQLCREATE TABLEDDLtutorial

Что такое CREATE TABLE в SQL?

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

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

CREATE TABLE — это команда, которая создаёт новую таблицу в базе данных.

Если говорить совсем просто, она отвечает на вопрос:

Какие данные мы хотим хранить и по каким правилам?

Таблица в базе похожа на хорошо продуманную анкету. До того как люди начнут её заполнять, нужно решить:

  • какие поля в ней будут;
  • какие поля обязательные;
  • где можно хранить текст, а где только числа;
  • какие значения должны быть уникальными;
  • какие строки связаны с другими таблицами;
  • что делать, если пользователь что-то не указал.

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

Вот всё это и описывает CREATE TABLE.

Пока таблицы нет, в неё нельзя вставить данные через INSERT и из неё нельзя ничего прочитать через SELECT. Поэтому CREATE TABLE — одна из первых команд, с которой начинается любой реальный проект.

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

База данных — это не просто «коробка, куда складывают информацию». Хорошая база сразу помогает хранить данные аккуратно.

Плохо спроектированная таблица быстро превращается в кладовку без полок: вроде всё где-то лежит, но найти, проверить и использовать это больно.

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

id email age
1 anna@example.com 25
2 NULL 31
3 anna@example.com 44
4 wrong_email -5
5 boris@example.com unknown

Сразу несколько проблем:

  • у одного пользователя нет email;
  • email повторяется;
  • возраст отрицательный;
  • возраст записан текстом;
  • email может быть в неправильном формате.

Часть таких проблем можно ловить в коде приложения. Но надёжнее заложить базовые правила прямо в таблицу. Тогда база сама не пропустит мусор.

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

  • хранит числа как числа, даты как даты, текст как текст;
  • не даёт записать NULL там, где значение обязательно;
  • следит, чтобы уникальные значения не повторялись;
  • проверяет простые условия через CHECK;
  • связывает строки между таблицами через FOREIGN KEY;
  • подставляет значения по умолчанию через DEFAULT.

Именно поэтому CREATE TABLE — не просто техническая команда. Это момент, когда вы проектируете порядок в данных.

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

Посмотрим на пример:

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()
);

Здесь мы создаём таблицу users с четырьмя колонками:

  • id — уникальный идентификатор пользователя;
  • email — почта пользователя;
  • display_name — отображаемое имя;
  • created_at — дата и время создания строки.

Разберём по частям.

CREATE TABLE users (
  ...
);

Так начинается создание таблицы. После CREATE TABLE указываем имя таблицы, а внутри скобок перечисляем колонки и правила для них.

id SERIAL PRIMARY KEY

Колонка id получает тип SERIAL. В PostgreSQL это удобный способ сказать: «пусть база сама выдаёт значения 1, 2, 3, 4 и так далее».

PRIMARY KEY означает, что это главный идентификатор строки. Значение должно быть уникальным и не должно быть NULL.

email VARCHAR(255) UNIQUE NOT NULL

Колонка email хранит текст длиной до 255 символов.

UNIQUE запрещает повторения. Два пользователя с одним и тем же email не пройдут.

NOT NULL говорит, что email обязателен. Нельзя создать пользователя без email.

display_name VARCHAR(100) NOT NULL

Колонка display_name тоже текстовая и тоже обязательная.

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

Колонка created_at хранит дату и время. Тип TIMESTAMPTZ в PostgreSQL удобен для событий во времени: регистраций, заказов, оплат, сообщений.

DEFAULT NOW() означает: если при вставке строки мы не указали created_at, база сама поставит текущий момент.

Запятые и точка с запятой

Внутри CREATE TABLE колонки разделяются запятыми:

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  title TEXT NOT NULL,
  price NUMERIC(12, 2) NOT NULL
);

После последней колонки запятая обычно не ставится.

В конце всей команды ставится точка с запятой:

);

Это как точка в конце предложения: команда закончилась, можно выполнять.

Пример из жизни: таблица записей на услугу

Допустим, мы делаем небольшой сервис для записи клиентов. Нужно хранить записи: кто пришёл, какой телефон оставил, какую услугу выбрал и на какое время записался.

Создадим таблицу:

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
);

Что здесь важно:

  • client_name — обязательное имя клиента;
  • phone — обязательный телефон;
  • service — обязательное название услуги;
  • starts_at — обязательные дата и время записи;
  • notes — необязательная заметка.

У notes нет NOT NULL, значит это поле можно не заполнять. Если заметки нет, в колонке будет NULL.

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

INSERT INTO appointments (client_name, phone, service, starts_at)
VALUES ('Anna', '+79991234567', 'haircut', '2026-05-10 14:00:00+03');

Результат в таблице будет примерно таким:

id client_name phone service starts_at notes
1 Anna +79991234567 haircut 2026-05-10 14:00:00+03 NULL

Мы не передавали id, потому что SERIAL сделал это сам.

Мы не передавали notes, потому что заметка необязательная.

Так таблица работает как аккуратный бланк: обязательные поля требует, необязательные разрешает пропустить.

Колонка — это имя, тип и правила

Внутри таблицы каждая колонка обычно состоит из трёх частей:

column_name data_type constraints

Например:

email VARCHAR(255) UNIQUE NOT NULL

Здесь:

  • email — имя колонки;
  • VARCHAR(255) — тип данных;
  • UNIQUE NOT NULL — ограничения.

Можно читать эту строку почти обычным языком:

Создай колонку email, в ней будет текст до 255 символов, значение не должно повторяться и не может быть пустым.

Чем лучше вы описали колонки на этапе создания таблицы, тем меньше хаоса получите позже.

Основные типы данных

Тип данных отвечает за то, что можно хранить в колонке.

Если колонка числовая, в неё нельзя спокойно положить произвольный текст. Если колонка с датой, с ней можно делать операции как с датой: сравнивать, сортировать, считать интервалы.

Вот короткая шпаргалка по частым типам в PostgreSQL.

Что храним Тип Пример
Целое число INTEGER количество товаров
Большое целое число BIGINT крупные счётчики
Небольшое целое число SMALLINT возраст, рейтинг от 1 до 5
Деньги и точные дробные числа NUMERIC(12, 2) цена, сумма заказа
Неточные дробные числа REAL, DOUBLE PRECISION измерения, расчёты, координаты
Текст TEXT, VARCHAR(n) имя, описание, комментарий
Дата DATE день рождения
Дата и время TIMESTAMPTZ дата регистрации, время оплаты
Логическое значение BOOLEAN активен пользователь или нет
UUID UUID непоследовательный уникальный идентификатор

Деньги — только NUMERIC

Для денег лучше использовать точный тип:

price NUMERIC(12, 2) NOT NULL

NUMERIC(12, 2) означает: всего до 12 цифр, из них 2 после запятой.

Например:

9999999999.99

Почему не FLOAT?

Потому что FLOAT, REAL и DOUBLE PRECISION хранят числа приблизительно. Это нормально для научных расчётов, графики или координат, но плохо для денег.

Классический пример проблемы:

SELECT 0.1::FLOAT + 0.2::FLOAT AS result;

Результат может оказаться не ровно 0.3, а чем-то вроде:

0.30000000000000004

Для цены товара, баланса пользователя или суммы заказа такое поведение не подходит.

Поэтому правило простое:

Деньги, цены, комиссии, балансы — через NUMERIC, а не через FLOAT.

Дата и время — TIMESTAMPTZ

Для событий во времени в PostgreSQL чаще всего выбирают TIMESTAMPTZ.

Например:

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

Так удобно хранить:

  • дату регистрации;
  • дату оплаты;
  • дату создания заказа;
  • дату отправки сообщения;
  • момент изменения статуса.

Важно понимать: TIMESTAMPTZ не хранит «название часового пояса» рядом с каждой строкой. PostgreSQL приводит момент времени к единому виду и показывает его с учётом настроек текущей сессии.

Практический смысл такой: если событие произошло в конкретный момент времени, TIMESTAMPTZ обычно безопаснее, чем «голое» TIMESTAMP.

А вот если вам нужна просто дата без времени, например день рождения, используйте DATE:

birth_date DATE

День рождения не зависит от часового пояса. Это просто календарная дата.

TEXT или VARCHAR

В PostgreSQL TEXT и VARCHAR(n) очень похожи по производительности.

Например:

description TEXT

подходит для описаний, комментариев и длинных текстов.

А вот VARCHAR(255) нужен, когда у ограничения есть смысл:

email VARCHAR(255) NOT NULL

Если бизнес-правило говорит, что поле не должно быть длиннее определённого размера, можно использовать VARCHAR(n).

Но не стоит ставить ограничения просто «на всякий случай». Если текст может быть любой разумной длины, в PostgreSQL часто спокойно используют TEXT.

Ограничения: правила, которые защищают данные

Ограничения, или constraints, — это правила, которые база проверяет при INSERT и UPDATE.

Если правило нарушено, база не запишет строку.

Посмотрим на пример таблицы заказов:

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

Здесь сразу несколько правил.

PRIMARY KEY

PRIMARY KEY — главный идентификатор строки.

id SERIAL PRIMARY KEY

Он должен быть:

  • уникальным;
  • обязательным;
  • стабильным.

По PRIMARY KEY удобно ссылаться на строку из других таблиц. Например, заказ может ссылаться на пользователя через user_id.

Обычно в таблице один PRIMARY KEY.

NOT NULL

NOT NULL запрещает пустое значение.

email TEXT NOT NULL

Если попробовать вставить строку без email, база выдаст ошибку.

Это полезно для всего, без чего строка теряет смысл:

  • email пользователя;
  • сумма заказа;
  • дата создания;
  • название товара;
  • статус платежа.

Если поле обязательно по смыслу, лучше написать NOT NULL сразу.

UNIQUE

UNIQUE запрещает повторяющиеся значения.

email TEXT UNIQUE NOT NULL

Так база не даст создать двух пользователей с одной и той же почтой.

Ещё примеры:

number TEXT UNIQUE NOT NULL

для номера заказа;

slug TEXT UNIQUE NOT NULL

для адреса страницы;

phone TEXT UNIQUE

для телефона, если по правилам бизнеса он должен быть уникальным.

UNIQUE — это не просто украшение. Это защита от дублей, которые потом очень неприятно чистить.

DEFAULT

DEFAULT подставляет значение автоматически, если его не указали в INSERT.

Например:

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

Теперь при вставке строки не нужно каждый раз передавать дату создания.

INSERT INTO users (email, display_name)
VALUES ('anna@example.com', 'Anna');

created_at база заполнит сама.

Другой пример:

status TEXT NOT NULL DEFAULT 'pending'

Если при создании заказа статус не указали, заказ станет pending.

Это удобно: меньше ручной работы и меньше забытых значений.

CHECK

CHECK проверяет условие для строки.

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

amount NUMERIC(12, 2) NOT NULL CHECK (amount > 0)

Статус должен быть одним из разрешённых:

status TEXT NOT NULL CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'))

Возраст не должен быть отрицательным:

age INT CHECK (age >= 0)

CHECK помогает перенести простую здравую логику прямо в базу.

Без CHECK можно случайно получить заказ на отрицательную сумму или пользователя с возрастом -5.

С CHECK база просто не примет такую строку.

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

FOREIGN KEY нужен, чтобы связать одну таблицу с другой.

Например, есть таблица пользователей:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT UNIQUE NOT NULL
);

И есть таблица комментариев:

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

Колонка user_id ссылается на users(id).

Это значит: комментарий не может принадлежать пользователю, которого нет.

Если попробовать вставить комментарий с несуществующим user_id, база не даст этого сделать:

INSERT INTO comments (user_id, body)
VALUES (999, 'Nice post');

Если пользователя с id = 999 нет, будет ошибка.

Это защищает от «осиротевших» данных: комментариев без пользователя, заказов без клиента, платежей без заказа.

Что такое ON DELETE

Когда есть связь между таблицами, возникает вопрос: что делать с дочерними строками, если удалить родительскую?

Например, есть пользователь и его комментарии.

Можно выбрать разное поведение.

ON DELETE CASCADE

CREATE TABLE comments (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  body TEXT NOT NULL
);

ON DELETE CASCADE означает: если удалить пользователя, его комментарии удалятся автоматически.

Это удобно, когда дочерние данные не имеют смысла без родителя.

Например:

  • позиции заказа без заказа;
  • временные токены пользователя;
  • черновики, принадлежащие удалённому объекту.

Но с CASCADE нужно быть осторожным: одно удаление может потянуть за собой много других строк.

ON DELETE RESTRICT

CREATE TABLE comments (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
  body TEXT NOT NULL
);

ON DELETE RESTRICT запрещает удалять пользователя, пока у него есть комментарии.

Это безопасный вариант, когда вы не хотите случайно потерять связанные данные.

ON DELETE SET NULL

CREATE TABLE comments (
  id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(id) ON DELETE SET NULL,
  body TEXT NOT NULL
);

ON DELETE SET NULL означает: если удалить пользователя, комментарий останется, но user_id станет NULL.

Важно: для этого колонка user_id должна разрешать NULL. Если написать user_id INT NOT NULL, то SET NULL не сможет сработать.

Такой вариант подходит, когда сам комментарий можно оставить, но связь с пользователем уже неизвестна или не нужна.

FOREIGN KEY не создаёт индекс автоматически

Это частая ловушка новичков.

В PostgreSQL внешний ключ на дочерней колонке не создаёт индекс автоматически.

Например:

CREATE TABLE comments (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL REFERENCES users(id),
  body TEXT NOT NULL
);

Связь есть. База проверяет, что user_id существует в users.

Но индекса на comments(user_id) автоматически нет.

Если комментариев много, запросы по user_id могут работать медленнее:

SELECT *
FROM comments
WHERE user_id = 10;

Поэтому индекс часто добавляют отдельно:

CREATE INDEX comments_user_id_idx ON comments(user_id);

Особенно это важно, если вы часто:

  • ищете дочерние строки по родителю;
  • удаляете родительские строки;
  • делаете JOIN по внешнему ключу;
  • проверяете связанные данные в больших таблицах.

Запомните коротко:

FOREIGN KEY отвечает за правильность данных, а индекс — за скорость поиска.

Это разные вещи.

Полный пример: пользователи и заказы

Соберём небольшой пример ближе к реальному проекту.

Есть пользователи:

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

Есть заказы:

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL REFERENCES users(id),
  number TEXT NOT NULL UNIQUE,
  amount NUMERIC(12, 2) NOT NULL CHECK (amount > 0),
  status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'cancelled')),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

И добавим индекс на внешний ключ:

CREATE INDEX orders_user_id_idx ON orders(user_id);

Теперь база защищает нас от многих ошибок:

  • нельзя создать заказ без пользователя;
  • нельзя создать заказ на отрицательную сумму;
  • нельзя записать неизвестный статус;
  • нельзя повторить номер заказа;
  • дата создания подставится сама;
  • заказы пользователя можно быстрее находить по user_id.

Вот так CREATE TABLE превращается из простой команды в фундамент нормальной структуры данных.

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

Имена лучше писать в стиле snake_case:

CREATE TABLE user_profiles (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  birth_date DATE
);

Такой стиль удобен:

  • всё в нижнем регистре;
  • слова разделены подчёркиванием;
  • не нужны кавычки;
  • легко читать в запросах.

Плохая идея — имена с пробелами и заглавными буквами:

CREATE TABLE "User Profiles" (
  "User ID" INT
);

Формально это работает, но потом в каждом запросе придётся помнить кавычки и точное написание:

SELECT "User ID"
FROM "User Profiles";

Это быстро надоедает и создаёт лишние ошибки.

Лучше сразу писать просто:

SELECT user_id
FROM user_profiles;

NULL: разрешать или запрещать

Каждая колонка либо разрешает NULL, либо нет.

Если вы пишете:

phone TEXT

значение можно не указывать, и тогда будет NULL.

Если пишете:

phone TEXT NOT NULL

значение обязательно.

NULL — это не ноль и не пустая строка. Это «значение отсутствует» или «значение неизвестно».

Иногда NULL полезен. Например, у записи может не быть заметки:

notes TEXT

Но если почти все важные поля nullable, схема становится слабой. База перестаёт защищать данные, а код приложения начинает постоянно проверять: «а тут точно что-то есть?»

Хорошее правило:

  • если поле обязательно по смыслу — ставьте NOT NULL;
  • если поле действительно может отсутствовать — разрешайте NULL;
  • если колонка часто заполнена только для части строк, возможно, стоит подумать об отдельной таблице.

Например, не всегда хорошо делать одну огромную таблицу пользователей с десятками необязательных полей. Иногда лучше вынести дополнительные данные в user_profiles, user_settings или другую связанную таблицу.

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

Ошибка 1. Хранить деньги во FLOAT

Плохо:

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  price FLOAT NOT NULL
);

Для денег так делать не стоит. FLOAT хранит приблизительные значения, а деньги требуют точности.

Лучше:

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  price NUMERIC(12, 2) NOT NULL
);

Ошибка 2. Не ставить NOT NULL на обязательные поля

Плохо:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT,
  display_name TEXT
);

Так база разрешит пользователя без email и имени.

Лучше:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT NOT NULL,
  display_name TEXT NOT NULL
);

Если поле обязательно для нормальной работы приложения, это должно быть видно в схеме.

Ошибка 3. Забыть UNIQUE для email или номера заказа

Плохо:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT NOT NULL
);

Так можно создать двух пользователей с одним email.

Лучше:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT NOT NULL UNIQUE
);

То же самое с номерами заказов, slug страниц, внешними идентификаторами платежей и другими значениями, которые не должны повторяться.

Ошибка 4. Хранить связь без FOREIGN KEY

Плохо:

CREATE TABLE comments (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  body TEXT NOT NULL
);

На вид всё нормально: есть user_id, значит комментарий связан с пользователем.

Но база этого не знает. Она спокойно примет комментарий с user_id, которого нет в таблице пользователей.

Лучше:

CREATE TABLE comments (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL REFERENCES users(id),
  body TEXT NOT NULL
);

Теперь связь — не просто договорённость в голове разработчика, а настоящее правило базы данных.

Ошибка 5. Думать, что FOREIGN KEY сам создаст индекс

Плохо думать так:

user_id INT NOT NULL REFERENCES users(id)

и считать, что индекс появился автоматически.

В PostgreSQL индекс на дочернюю колонку нужно добавить отдельно:

CREATE INDEX comments_user_id_idx ON comments(user_id);

FOREIGN KEY защищает данные, но не всегда ускоряет ваши запросы.

Ошибка 6. Использовать неудобные имена

Плохо:

CREATE TABLE "Order Items" (
  "Order ID" INT
);

Лучше:

CREATE TABLE order_items (
  order_id INT
);

Чем проще имена, тем приятнее потом писать SELECT, JOIN, UPDATE и любые другие запросы.

Ошибка 7. Забыть DEFAULT для created_at

Плохо:

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  created_at TIMESTAMPTZ NOT NULL
);

Теперь при каждом INSERT нужно помнить про created_at.

Лучше:

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Теперь база сама подставит дату создания.

Ошибка 8. Делать слишком много nullable-колонок

Плохо:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT,
  company_name TEXT,
  company_tax_id TEXT,
  student_group TEXT,
  teacher_subject TEXT
);

Если в одной таблице смешиваются обычные пользователи, компании, студенты и преподаватели, половина колонок будет постоянно NULL.

Это сигнал, что модель данных, возможно, стоит разделить на несколько таблиц.

Например:

  • users;
  • company_profiles;
  • student_profiles;
  • teacher_profiles.

Не всегда нужно дробить сразу, но если таблица превращается в поле из NULL, стоит остановиться и подумать.

Как читать CREATE TABLE глазами разработчика

Когда видите CREATE TABLE, не пытайтесь читать его как набор случайных слов.

Читайте по смыслу:

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL REFERENCES users(id),
  amount NUMERIC(12, 2) NOT NULL CHECK (amount > 0),
  status TEXT NOT NULL DEFAULT 'pending',
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Это можно перевести так:

Создаём таблицу заказов.
У каждого заказа есть уникальный id.
Каждый заказ принадлежит существующему пользователю.
Сумма заказа обязательна и должна быть больше нуля.
Статус обязателен, по умолчанию pending.
Дата создания обязательна, по умолчанию текущий момент.

Вот это и есть хорошее понимание SQL: вы видите не просто синтаксис, а правила предметной области.

Главное

CREATE TABLE создаёт таблицу и описывает её структуру: какие в ней будут колонки, какие у них типы и какие правила база должна проверять.

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

Самое важное, что нужно запомнить:

  • CREATE TABLE создаёт новую таблицу;
  • каждая колонка имеет имя, тип данных и дополнительные ограничения;
  • PRIMARY KEY задаёт главный идентификатор строки;
  • NOT NULL запрещает пустые значения;
  • UNIQUE запрещает дубли;
  • CHECK проверяет условие;
  • DEFAULT подставляет значение автоматически;
  • FOREIGN KEY связывает таблицы и защищает от «осиротевших» данных;
  • деньги лучше хранить в NUMERIC, а не во FLOAT;
  • дату и время событий в PostgreSQL чаще всего удобно хранить в TIMESTAMPTZ;
  • внешний ключ не создаёт индекс на дочерней колонке автоматически, поэтому индекс часто нужно добавить отдельно.

Хорошая таблица экономит много времени. Она не молчит, когда в данные пытаются положить мусор, а сразу говорит: «Так нельзя». И это именно то поведение, которое нужно от надёжной базы данных.

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

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

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