Ключи в SQL-таблицах: виды ключей в БД и как их создать

Ключи в SQL-таблицах: виды ключей в БД и как их создать Полезное

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

Ниже — виды ключей в БД (первичный, внешний, уникальный, составной, потенциальный, суррогатный, естественный), рабочие примеры CREATE TABLE на PostgreSQL, типовые ошибки СУБД и сводные таблицы. Все примеры собраны на одной схеме из трёх таблиц.

Виды ключей в БД

Слово «ключ» в реляционной модели используют в нескольких смыслах, и полезно развести два уровня: логическую модель (про смысл данных) и SQL-ограничения (которыми её реализуют в таблице).

Логическая модель — это потенциальный ключ (минимальный набор столбцов, уникально определяющий строку; их может быть несколько), естественный (из реальных данных: ИНН, паспорт, код товара) и суррогатный (искусственный идентификатор без смысла, автоинкремент или UUID).

На SQL-уровне модель реализуют ограничения PRIMARY KEY, UNIQUE, FOREIGN KEY и составной ключ (сводка ниже).

Важная оговорка про UNIQUE: это ограничение уникальности, и потенциальный ключ реализует только минимальный набор UNIQUE NOT NULL. Nullable UNIQUE в PostgreSQL по умолчанию допускает несколько NULL, поэтому не всякое UNIQUE — это потенциальный ключ.

Зачем нужны ключи: идентификация и целостность

Первая задача — идентификация: без первичного ключа строки можно перепутать (два заказа с одной суммой), а с ним каждая строка адресуема.

Вторая задача — целостность связей: внешний ключ не даёт сослаться на несуществующего клиента и не даёт удалить клиента с заказами, пока вы не укажете, что делать с зависимыми строками. По умолчанию PostgreSQL такое удаление запрещает (NO ACTION); варианты разберём ниже.

Про индексы важна привязка к диалекту. В PostgreSQL под PRIMARY KEY и UNIQUE СУБД создаёт уникальный индекс, поэтому поиск и соединение по ним быстры. А вот FOREIGN KEY индекс на ссылающихся столбцах не создаёт автоматически — при необходимости его создают отдельно через CREATE INDEX.

Примеры на SQL

Основной диалект дальше — PostgreSQL; для MySQL 8 отличается генерация ключа, она вынесена отдельной врезкой, а не смешана в одном блоке.

Простой первичный ключ задают в CREATE TABLE; значение генерирует столбец-идентификатор:

-- PostgreSQL: значение supplier_id генерирует сама СУБД
CREATE TABLE suppliers (
    supplier_id  INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,  -- суррогатный первичный ключ
    inn          VARCHAR(12) UNIQUE,     -- естественный уникальный ключ: дубли ИНН запрещены
    name         VARCHAR(200) NOT NULL
);

Здесь supplier_id — суррогатный первичный ключ, inn — уникальный. Вариант GENERATED ALWAYS запрещает подставлять значение вручную, а GENERATED BY DEFAULT AS IDENTITY — разрешает (его используем ниже для goods, чтобы воспроизвести ошибки).

В MySQL 8 генерацию задаёт AUTO_INCREMENT:

-- MySQL 8: та же таблица, значение задаёт AUTO_INCREMENT
CREATE TABLE suppliers (
    supplier_id  INT AUTO_INCREMENT PRIMARY KEY,
    inn          VARCHAR(12) UNIQUE,
    name         VARCHAR(200) NOT NULL
);

Рабочая схема: связь «многие ко многим»

Связь «многие ко многим» требует нескольких таблиц. Соберём единый запускаемый блок: справочник товаров goods, заказы orders и позиции order_items.

-- PostgreSQL: справочник товаров
CREATE TABLE goods (
    good_id  INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,  -- суррогатный первичный ключ
    name     VARCHAR(200) NOT NULL,
    type     VARCHAR(50)  NOT NULL
);

-- заказы
CREATE TABLE orders (
    order_id    INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    created_at  DATE NOT NULL
);

-- позиции заказа: таблица-связка «многие ко многим»
CREATE TABLE order_items (
    order_id    INT NOT NULL,
    product_id  INT NOT NULL,
    quantity    INT NOT NULL,
    PRIMARY KEY (order_id, product_id),                      -- составной первичный ключ
    FOREIGN KEY (order_id)   REFERENCES orders(order_id),    -- позиция ссылается на заказ
    FOREIGN KEY (product_id) REFERENCES goods(good_id)       -- и на товар из справочника
);

Это типовая связка «многие ко многим»: заказ содержит много товаров, товар встречается во многих заказах, а пара «заказ + товар» уникальна за счёт составного ключа. Два внешних ключа держат целостность: order_id обязан существовать в orders, а product_id — в goods.

Наполним схему — три INSERT заводят товары, заказ и состав:

INSERT INTO goods (name, type) VALUES ('Стол', 'equipment'), ('Стул', 'equipment');
INSERT INTO orders (created_at) VALUES ('2026-09-07');
INSERT INTO order_items (order_id, product_id, quantity) VALUES (1, 1, 4), (1, 2, 8);

Идентификаторы сгенерировались сами: у товаров good_id = 1 и 2, у заказа order_id = 1. Позиции ссылаются на существующие строки — вставка проходит.

Ошибки с ключами: нарушение, сообщение СУБД, исправление

Ключи проявляются в момент нарушения. Три частые ошибки на схеме goods/orders/order_items (сообщения PostgreSQL).

Ошибка 1 — дубликат по первичному ключу.

INSERT INTO goods (good_id, name, type) VALUES (1, 'Лампа', 'equipment');  -- good_id=1 уже занят
ERROR:  duplicate key value violates unique constraint "goods_pkey"
DETAIL:  Key (good_id)=(1) already exists.

Исправление: не задавать ключ вручную — INSERT INTO goods (name, type) VALUES ('Лампа', 'equipment');.

Ошибка 2 — NULL в первичном ключе.

INSERT INTO goods (good_id, name, type) VALUES (NULL, 'Стол', 'equipment');
ERROR:  null value in column "good_id" violates not-null constraint

Исправление: опустить столбец или указать DEFAULT и дать ключ сгенерировать; NULL в первичный ключ вставлять нельзя.

Ошибка 3 — нарушение внешнего ключа.

INSERT INTO order_items (order_id, product_id, quantity) VALUES (1, 999, 2);  -- товара 999 нет
ERROR:  insert or update on table "order_items" violates foreign key constraint
DETAIL:  Key (product_id)=(999) is not present in table "goods".

Исправление: сначала завести товар в goods, потом ссылаться на его good_id, либо указать существующий.

Частый учебный приём COUNT(*) + 1 для good_id в разовом примере проходит, но ненадёжен: при удалении строк или одновременной вставке два товара получат одинаковый good_id и сработает ошибка 1. Генерацию отдают СУБД (GENERATED ... AS IDENTITY или AUTO_INCREMENT).

FOREIGN KEY и удаление родителя: ON DELETE

Что происходит при удалении строки, на которую ссылается внешний ключ, задаёт ON DELETE. По умолчанию PostgreSQL использует NO ACTION. Три частых варианта:

ON DELETE Что делает при удалении родительской строки Когда выбирать
RESTRICT / NO ACTION Запрещает удаление, пока есть зависимые строки Осиротевшие данные недопустимы
CASCADE Удаляет зависимые строки вместе с родителем Дочерние строки без родителя бессмысленны (позиции удаляемого заказа)
SET NULL Обнуляет внешний ключ в зависимых строках Связь необязательна (столбец должен допускать NULL)

Вариант задают при объявлении: FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE.

Успешный запрос: SELECT с JOIN

Соединение по ключам собирает читаемый результат:

SELECT o.order_id, g.name AS good, oi.quantity
FROM order_items oi
JOIN orders o ON o.order_id = oi.order_id
JOIN goods  g ON g.good_id  = oi.product_id
ORDER BY o.order_id, g.name;
 order_id | good | quantity
----------+------+----------
        1 | Стол |        4
        1 | Стул |        8

Внешние ключи гарантируют, что в order_items нет позиций без заказа или товара, а составной ключ — что пара не задвоится.

Виды ключей: сводные таблицы

Логическая модель — про смысл данных:

Понятие Что значит Пример
Потенциальный (candidate) Минимальный уникальный набор столбцов Пара (order_id, product_id)
Естественный Ключ из реальных данных ИНН, паспорт, код товара
Суррогатный Искусственный идентификатор без смысла good_id из IDENTITY

SQL-ограничения — чем это реализуют в таблице:

Ограничение Назначение Пример объявления
PRIMARY KEY Главный идентификатор строки, без NULL, один на таблицу good_id INT ... PRIMARY KEY
UNIQUE Запрет дублей; UNIQUE NOT NULL может задавать потенциальный ключ inn VARCHAR(12) UNIQUE
FOREIGN KEY Ссылка на ключ другой таблицы, целостность связей FOREIGN KEY (product_id) REFERENCES goods(good_id)
Составной (composite) Ключ из двух и более столбцов; уникальна комбинация PRIMARY KEY (order_id, product_id)

Выводы

  • Разводите два уровня: логическая модель (потенциальный, естественный, суррогатный) и SQL-ограничения (PRIMARY KEY, UNIQUE, FOREIGN KEY), которыми её реализуют.
  • Первичный ключ один, без NULL; UNIQUE может быть несколько, а nullable UNIQUE в PostgreSQL допускает несколько NULL, поэтому потенциальный ключ задаёт только минимальный UNIQUE NOT NULL.
  • Внешний ключ держит целостность; поведение при удалении родителя задаёт ON DELETE (NO ACTION/RESTRICT, CASCADE, SET NULL).
  • В PostgreSQL PRIMARY KEY и UNIQUE создают уникальный индекс, а FOREIGN KEY — нет; индекс на его столбцах при необходимости создают отдельно. Генерацию ключа отдавайте СУБД, а не считайте записи вручную.

Где применяется / связь с практикой

Освойте тему на практике

Ключи — фундамент любой схемы данных, с ними ежедневно работают бэкенд-разработчики, аналитики и администраторы БД. Разобрать проектирование схем, нормализацию и работу с ключами помогает курс по базам данных subd. Если хочется сначала присмотреться к теме, посмотрите бесплатные вебинары — там разбирают темы по базам данных и SQL вживую.

Смежные темы: Введение в SQL: что требуется знать новичку, Изучение SQL-команд на базе MySQL, Знакомство с SQLite: что нужно знать о СУБД.

FAQ

Чем потенциальный ключ отличается от первичного? Потенциальный (candidate) — любой минимальный набор столбцов, уникально определяющий строку; их бывает несколько. Первичный — выбранный из них главным.

Может ли внешний ключ ссылаться не на первичный ключ? Да, на первичный или уникальный (UNIQUE) ключ другой таблицы — важно, чтобы целевой столбец гарантировал уникальность.

Любое ли UNIQUE — это потенциальный ключ? Нет. Nullable UNIQUE в PostgreSQL по умолчанию разрешает несколько строк без значения, поэтому потенциальный ключ задаёт только минимальный набор UNIQUE NOT NULL.

OTUS Журнал
Скидка 10% 7-13 сентября на курсы из спецкаталога (pop-up)