Ключи в базе данных: практический обзор для начинающих системных аналитиков

634137eb3768d038e2aa822da5e497c1.jpg

a0ccc0b8cb0ff2471adb97ee068211e2.jpgОксана Соболевская (Давыдович)

Системный аналитик EvApps

Всем привет! Меня зовут Оксана, я системный аналитик из компании EvApps. Что побудило меня написать эту статью? Я обучаю стажеров — системных аналитиков, и недавно столкнулась с такими вопросами, о которых раньше даже не задумывалась.

Вопросы были связаны с разными видами ключей в базе данных и с тем, как они связаны между собой (тему с реляционными БД мы разбираем на примере PostgreSQL). Я начала искать разные статьи по этой теме, очень много крутого материала на том же «Хабре», но многие вопросы так и остались не раскрытыми. И мне стало интересно разобраться с этими вопросами и «пощупать» все это на практике. В итоге начала изучать документацию PostgreSQL и теорию реляционных баз данных, но чтобы получить ответы, пришлось все проверять на практических примерах.

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

  • ограничение уникальности;

  • первичные ключи;

  • внешние ключи.

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

Итак, погнали.

1. Можно ли создать таблицу без первичного ключа?

Да, можно. И технически это получится сделать. Но! Согласно теории баз данных, каждая таблица должна иметь первичный ключ. И в документации постгре также на это ссылаются.

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

То есть я предварительно создала таблицу User, в которой не установлен первичный ключ:

CREATE TABLE public.user (  id int,  "name" varchar,  age int );

Далее хочу создать таблицу Order и связать ее с таблицей User. И при выполнении запроса получаю ошибку.

8a90223c24d2efb75524d9000a088af6.png

2. Можно ли использовать ограничение уникальности вместо первичного? В чём особенности?

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

Вариант с использованием UNIQUE:

CREATE TABLE public.user1 (  id int UNIQUE,  "name" varchar,  age int );

Вариант с использованием PRIMARY KEY:

CREATE TABLE public.user2 (  id int PRIMARY KEY,  "name" varchar,  age int );

Теперь попробуем создать запись в таблице user1, но для id установим значение null:

INSERT INTO public.user1 (id, "name", age) VALUES(null, 'Иван', 10); select * from public.user1

Как видим, запись в таблицу добавилась успешно.

8c7cc9553f2e565d39d45ba356d13470.png

Теперь попробуем то же самое сделать в таблице user2:

INSERT INTO public.user2 (id, "name", age) VALUES(null, 'Иван', 10); 

И видим, что при создании записи возникает ошибка.

4bd0ae8fe87918d39effece7d7f29869.png

Какие выводы можно сделать:

  • PRIMARY KEY — первичный ключ, позволяет сделать запись уникальной и включает в себя ограничение NOT NULL.

  • UNIQUE — ограничение уникальности, обеспечивает уникальность значений в столбце/столбцах, при этом не имеет проверку на NULL. Чтобы этого избежать, нужно использовать UNIQUE NOT NULL.

По факту, вместо PRIMARY KEY можно использовать UNIQUE NOT NULL и Postgres позволяет это сделать, но рекомендуют следовать теории реляционных баз данных и устанавливать первичные ключи.

3. Можно ли использовать первичный ключ и ограничение уникальности вместе (т. е. для одного столбца установить два ограничения)?

Да, можно, но это бессмысленно. В данном случае учитывается ограничение первичного ключа, а UNIQUE считается избыточным и отбрасывается.

Например, я создаю таблицу:

CREATE TABLE public.user3 (  id int PRIMARY KEY UNIQUE,  "name" varchar,  age int );

Но в DDL можем увидеть, что в ограничениях остался только первичный ключ.

142ca72478a49d7bbfb05a702ce7e7d6.png

При этом можно использовать и первичный ключ и ограничение уникальности в рамках одной таблицы.

4. Можно ли сделать связь с ограничением уникальности?

Да, можно.

Пример:

Создаем таблицу, где установлено ограничение уникальности:

CREATE TABLE public.user1 (  id int UNIQUE,  "name" varchar,  age int );

Далее создаем таблицу с ссылкой на таблицу user1:

CREATE TABLE public.order (  id int,  order_number varchar,  user_id int references public.user1(id),  sum float );

Таблица создана успешно, связь настроена.

ff0256fee3ceb0eb0a497baa45b40d39.png

И если мы попытаемся в таблицу Order добавить запись с пользователем, которого нет в таблице user1, то получим ошибку.

a56f586214c5a73cf14c0f2a21aebea8.png

Можно ли установить связь между таблицами без внешнего ключа?

Нет, это невозможно. Для установления связи между таблицами обязательно использование REFERENCES.

Примечание:
Технически можно условно связать таблицы, без references, на уровне кода. Т. е. не создавая в БД внешние ключи. Но тогда программист берет на себя заботу следить за связями и целостностью данных программно. Без создания внешних ключей в БД можно породить хаос в данных. В идеале за связями нужно следить с двух сторон: и в программе, и в БД. Но только БД позволит не довести до беды и вовремя сигнализировать о возможной ошибке. В данном вопросе мы рассматриваем связи именно на уровне БД.

5. Можно ли в одной таблице (разных полях) использовать первичный ключ и ограничение уникальности? Может ли ограничение уникальности включать несколько столбцов?

Ответ на оба вопроса — да.

Давайте для примера рассмотрим два случая:

CREATE TABLE public.user4 (  id int PRIMARY KEY,  "name" varchar,  series_passport varchar UNIQUE NOT NULL,  number_passport varchar UNIQUE NOT NULL,  age int );

и

CREATE TABLE public.user5 (  id int PRIMARY KEY,  "name" varchar,  series_passport varchar,  number_passport varchar,  age int,  UNIQUE (series_passport, number_passport)  );

В первом случае при добавлении записи будет проверяться каждое значение на уникальность в столбцах, где установлен UNIQUE.

Например, если у меня есть такая запись в таблице:

15304399d37c24bdc8d1546c64b4860c.png

То я не смогу добавить строку со значением series_passport=7777 или number_passport=123454. Будет выдаваться ошибка. То есть проверка уникальности значений происходит по КАЖДОМУ столбцу, где установлен UNIQUE.

Теперь рассмотрим второй случай.

d228c5cba52965ad5422e3008d93d2f3.png

Я могу создавать строки с одинаковыми значениями series_passport или number_passport. Но пара этих значений должна быть уникальной. То есть во втором случае происходит проверка СОЧЕТАНИЯ значений всех столбцов.

6. Сколько первичных ключей может иметь таблица?

Один и только один. Иначе получаем такую ошибку:

5ed824ff0312df3bc9e7ebd8f015bbb0.png

7. Сколько ограничений уникальности может иметь таблица?

Сколько угодно. Ограничения на количество нет, но важно понимать целесообразность использования UNIQUE.

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

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

Рассмотрим связь, которую установили в примерах выше.

a11cb7349f231514eeb77da614671311.png

У полей user_id и id, одинковый тип данных — int. Если бы тип отличался, или мы бы пытались связать поле user_id с двуми полями, а не одним (id), то установить связь не получилось бы.

9. Могут ли на одном столбце быть установлены и первичный и внешний ключи?

Да, могут. Рассмотрим такую задачу. У нас есть три таблицы: продукты, пользователи и отзывы. И есть условие: пользователь на один товар может оставить только один отзыв.

Я выбрала вот такую реализацию.

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

CREATE TABLE public.user_new (  id int PRIMARY KEY,  "name" varchar,  age int );
CREATE TABLE public.product (  id int PRIMARY KEY,  "name" varchar,  price float );

Далее при создании таблицы Отзывы я установила первичный ключ на два столбца user_id, product_id (то есть сочетание их значений мне нужно проверять):

CREATE TABLE public.review (  user_id int REFERENCES public.user_new(id),   product_id int REFERENCES public.product(id) ,  "text" varchar,  grade int,  PRIMARY KEY (user_id,product_id) );

И с этих же полей я установила связь с внешними таблицами user_new и product.

И если я захочу добавить запись, где повторяется сочетание user_id + product_id, у меня возникнет ошибка:

ffd4a83e30e07032ea76b3da9e5d4af1.png

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

Вопрос

Ответ

1. Можно ли таблицу без первичного ключа?

Да, но согласно теории баз данных каждая таблица должна иметь первичный ключ

2. Можно ли использовать UNIQUE вместо PRIMARY KEY? В чем особенности?

PostgreSQL позволяет это сделать (важно учитывать ограничение NOT NULL), но рекомендуют устанавливать первичный ключ

3. Можно ли использовать первичный ключ и ограничение уникальности вместе (то есть для одного столбца установить два ограничения)?

Можно, но это не имеет смыла. В этом случае UNIQUE будет избыточным и не будет учитываться

4. Можно ли сделать связь с таблицей, в которой не установлено PRIMARY KEY и UNIQUE?

Нет, это невозможно

5. Можно ли сделать связь с полем в таблице, где установлен UNIQUE, но не установлен PRIMARY KEY?

Да, можно

6. Можно ли установить связь между таблицами без внешнего ключа?

Нет, для установления связи необходимо использовать REFERENCES (на уровне БД)

7. Можно ли в одной таблице использовать первичный ключ и ограничение уникальности?

Да, но в разных столбцах. Если использовать для одного столбца UNIQUE и PRIMARY KEY, то UNIQUE будет считаться избыточным и не будет учитываться.

8. Может ли первичный ключ состоять из нескольких столбцов?

Да, может

9. Может ли в одной таблице быть установлено несколько PRIMARY KEY?

Нет

10. Может ли ограничение уникальности включать несколько столбцов?

Да

11. Сколько ограничений уникальности может иметь таблица?

Несколько

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

Нет, число столбцов и тип данных должен быть одинаковый

13. Может ли на одном поле быть уставлены и первичный и внешний ключи?

Да

© Habrahabr.ru