Таблицы, первичный и внешний ключ

Фундамент реляционной базы данных за один урок

Представьте школьный журнал: на одной странице список учеников, на другой — список классов. Чтобы не переписывать «5А» рядом с каждым учеником, мы пишем номер класса и один раз заводим справочник классов. Реляционная база устроена точно так же: данные раскладываются по таблицам, а связываются между собой ключами. Разберёмся, что это за ключи и почему без них база быстро превращается в кашу.

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

Таблица, строка, столбец

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

ТерминЧто этоПример в таблице сотрудников
ТаблицаСущность целикомemployees
Строка (запись)Один объект сущности«Sarah Connor, зарплата 8000»
Столбец (атрибут)Одно свойство объектаemp_name, salary

Важная идея: строки в таблице не упорядочены и их нельзя различать «по позиции». Если у двух сотрудников совпадут имя и зарплата, как база поймёт, что это разные люди? Для этого нужен ключ.

Первичный ключ (PRIMARY KEY)

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

В нашей базе у сотрудников ключ — emp_id, у отделов — dept_id. База данных сама следит за правилами: вставить двух сотрудников с одинаковым emp_id она не даст, и оставить emp_id пустым — тоже.

Хороший первичный ключ стабилен и бессмысленен сам по себе. Номер emp_id = 3 не меняется, даже если сотрудник сменит имя или отдел. Поэтому в ключи берут технические идентификаторы, а не имена или телефоны.

Внешний ключ (FOREIGN KEY)

Сущности связаны: у сотрудника есть отдел. Но хранить название отдела в каждой строке сотрудников — плохо: при переименовании отдела пришлось бы править десятки строк, и легко допустить опечатку. Вместо этого в таблице employees есть столбец dept_id, который ссылается на dept_id в таблице departments. Это и есть внешний ключ.

departmentsemployees
dept_id (PK)dept_namedept_id (FK)emp_name
3Finance3Rick Deckard
5Sales5Sarah Connor

Внешний ключ — это указатель: «отдел этого сотрудника описан вон в той строке таблицы отделов». Названия отделов хранятся один раз, а сотрудники держат только короткую ссылку-число.

Соединяем таблицы по ключу

Попробуйте запустить запрос. Он берёт каждого сотрудника и подставляет название его отдела, соединяя таблицы по совпадению dept_id (это и есть JOIN по внешнему ключу).

SELECT e.emp_name,
       e.salary,
       d.dept_name
FROM employees AS e
JOIN departments AS d
  ON e.dept_id = d.dept_id
ORDER BY d.dept_name;

В результате каждый сотрудник встал рядом со своим отделом. Заметьте: сотрудник Martin Blank в ответ не попал — у него dept_id равен NULL (он пока ни к какому отделу не привязан), и совпадения не нашлось. Так внешний ключ превращает разрозненные таблицы в единую картину.

Целостность данных

Главная польза ключей — целостность: база сама не даёт записать бессмыслицу. Если внешний ключ включён, нельзя поставить сотруднику dept_id = 99, когда отдела с таким номером не существует — база отвергнет такую строку. Это называется ссылочной целостностью: ссылка всегда ведёт на реально существующую запись.

Создадим свою маленькую базу «авторы и книги» и убедимся, что связь работает. Здесь author_id в таблице books — внешний ключ на authors.

CREATE TABLE authors (
    author_id INTEGER PRIMARY KEY,
    name      TEXT NOT NULL
);

CREATE TABLE books (
    book_id   INTEGER PRIMARY KEY,
    title     TEXT NOT NULL,
    author_id INTEGER REFERENCES authors(author_id)
);

INSERT INTO authors (author_id, name) VALUES
    (1, 'Лев Толстой'),
    (2, 'Александр Пушкин');

INSERT INTO books (book_id, title, author_id) VALUES
    (10, 'Война и мир',     1),
    (11, 'Анна Каренина',   1),
    (12, 'Евгений Онегин',  2);

SELECT b.title AS книга,
       a.name  AS автор
FROM books AS b
JOIN authors AS a
  ON b.author_id = a.author_id
ORDER BY b.book_id;

Запрос показал три книги с именами авторов. Имя «Лев Толстой» хранится в базе ровно один раз, а две его книги ссылаются на него через число 1. Захотим исправить имя автора — поправим одну строку, и оно тут же изменится для всех его книг.

Частые ошибки

  • Брать в первичный ключ изменчивые данные (имя, e-mail, телефон). Они меняются — и ключ ломается. Используйте стабильный идентификатор.
  • Дублировать название отдела в таблице сотрудников вместо ссылки dept_id. Это ведёт к рассинхрону: где-то «Sales», где-то «Продажи».
  • Путать направление ссылки: внешний ключ живёт в «дочерней» таблице (employees) и указывает на первичный ключ «родительской» (departments), а не наоборот.
  • Забывать про NULL: если внешний ключ может быть пустым, такие строки выпадут из обычного JOIN.

Коротко

  • Таблица описывает одну сущность; строка — это запись, столбец — атрибут.
  • PRIMARY KEY уникально и без пропусков идентифицирует каждую строку — это «паспорт» записи.
  • FOREIGN KEY хранит ссылку на первичный ключ другой таблицы, связывая данные без дублирования.
  • Ключи обеспечивают целостность: база не пускает дубликаты ключей и ссылки на несуществующие записи.
  • JOIN по ключу собирает данные из разных таблиц в одну осмысленную выборку.
Проверьте себя
1. Что в реляционной таблице соответствует одному атрибуту сущности?
AСтрока
BСтолбец
CТаблица целиком
DПервичный ключ
2. Какие два свойства обязательно выполняются для значений первичного ключа?
AОни отсортированы и положительны
BОни уникальны и не равны NULL
CОни текстовые и короткие
DОни совпадают со значениями другой таблицы
3. В нашей базе столбец employees.dept_id — это:
AПервичный ключ таблицы employees
BВнешний ключ, ссылающийся на departments.dept_id
CОбычный текстовый столбец без связей
DПервичный ключ таблицы departments
4. Почему имя сотрудника — плохой кандидат в первичный ключ?
AИмена нельзя хранить в базе данных
BИмена занимают слишком много памяти
CИмена могут повторяться и меняться, нарушая уникальность и стабильность ключа
DПо именам нельзя выполнять JOIN
5. Что обеспечивает ссылочная целостность при включённых внешних ключах?
AЗапрет на удаление любых строк
BНевозможность сослаться на несуществующую запись родительской таблицы
CАвтоматическую сортировку строк
DШифрование значений ключей
6. Сотрудник Martin Blank с dept_id = NULL не попал в результат запроса с обычным JOIN. Почему?
AJOIN не работает с таблицей employees
BДля NULL не нашлось совпадения в departments, поэтому строка отсеялась
CУ него слишком маленькая зарплата
DNULL запрещён в реляционных базах
Поддержать проект