Реляционная модель: таблицы, строки, колонки
Реляционная модель: таблицы, строки, колонки
В прошлом уроке мы разобрались, чем база данных принципиально отличается от россыпи CSV или JSON: она навязывает структуру, следит за типами, умеет искать и согласовывать данные между собой. Теперь пора заглянуть внутрь — посмотреть, как именно устроены данные в самой популярной разновидности баз. Эта разновидность называется реляционной, и именно с ней мы будем работать весь курс. Если ты раньше видел Excel-таблицу, ты уже на 70% знаком с тем, что увидишь дальше. Оставшиеся 30% — это как раз то, ради чего реляционные базы существуют, и мы их аккуратно разберём.
Зачем нам отдельная «модель», если есть просто файлы
Вспомни боль из урока 1-1: десятки CSV-файлов, в одном из них поле email написано как e_mail, в другом — как EmailAddress, у одного клиента три записи в customers.csv, у второго — две, и никто не помнит, какая из них «правильная». Когда данных мало, файлов хватает. Когда их становится много и они начинают ссылаться друг на друга, нужен общий язык: способ договориться, как именно мы храним информацию, как описываем «одну сущность» и как соединяем сущности между собой.
Этот общий язык и называется моделью данных. Модель — это набор правил и понятий, которыми мы пользуемся, чтобы описывать предметную область: клиентов, заказы, товары, события. Моделей в мире несколько (есть документные, есть графовые, есть «ключ-значение»), но самая распространённая, проверенная временем и стоящая за большинством бизнес-систем — реляционная модель. Её придумал Эдгар Кодд в 1970 году, и с тех пор она в том или ином виде живёт почти во всех «серьёзных» базах данных.
Главная мысль реляционной модели звучит обманчиво просто: данные хранятся в виде таблиц. Но за этим стоит цепочка строгих идей: что такое таблица, что такое строка, что такое колонка, и почему между ними есть жёсткие правила, которых нет в Excel. Давай разбираться по слоям.
Таблица — это «отношение»
В реляционной модели таблица называется красивым словом отношение (по-английски — relation, отсюда и название модели). Не пугайся слова: оно не про «отношения» в человеческом смысле, а про математическое понятие — некий набор фактов одной и той же формы. Например, отношение «пользователи» — это набор фактов вида «у пользователя есть идентификатор, имя, email и дата регистрации». Каждый такой факт — отдельная запись в этом наборе.
Когда мы говорим «таблица users», мы имеем в виду именно отношение, описывающее пользователей. Один из канонических разделов в документации PostgreSQL формулирует это так: «Таблица в реляционной базе данных похожа на таблицу на бумаге: у неё есть строки и столбцы. Число и порядок столбцов фиксированы, у каждого столбца есть имя. Число строк переменное — оно отражает, сколько данных хранится в данный момент». Заметь две важные вещи. Во-первых, столбцы фиксированы — мы заранее договариваемся, какая информация хранится. Во-вторых, строки переменны — их может быть ноль, десять или десять миллионов, и это нормально.
Вот как это выглядит на практике. Представим таблицу users нашего учебного приложения:
| id | name | created_at | |
|---|---|---|---|
| 1 | Анна | anna@example.com | 2024-03-12 |
| 2 | Борис | boris@example.com | 2024-04-01 |
| 3 | Вика | vika.dev@example.com | 2024-04-17 |
| 4 | Глеб | gleb@example.com | 2024-05-02 |
Это — отношение. Четыре столбца с заранее объявленными именами и смыслами, и четыре строки, каждая из которых рассказывает про одного конкретного пользователя.
Анатомия: строки и колонки
Теперь по частям. У реляционной таблицы два «измерения»:
колонка(она же column, она же атрибут в академической терминологии) — это вертикальный срез: одно конкретное свойство, общее для всех записей. У нашейusersчетыре колонки:id,name,email,created_at. У каждой колонки есть имя и тип (об этом чуть ниже). Колонка задаёт вопрос, на который каждая строка должна ответить: «как зовут?», «когда зарегистрировался?».строка(она же row, она же кортеж — tuple — в академической терминологии) — это горизонтальный срез: одна конкретная запись, один конкретный факт, один пользователь. Строка обязана содержать значения для всех колонок, которые описаны в схеме (или явное «значения нет», но об этом — в более поздних уроках).
В формальной терминологии реляционной модели говорят: «кортеж — это набор из n значений, где n — это степень отношения, и каждое значение в кортеже соответствует уникальному атрибуту». Расшифровка: «строка состоит ровно из стольких значений, сколько колонок объявлено в таблице, и каждое значение принадлежит своей колонке». Это не свобода Excel, где в одну ячейку можно ввести что угодно — это строгое правило, которое база данных проверяет за тебя.
И ещё одно важное свойство, которое обычно становится сюрпризом для новичков: порядок строк в реляционной таблице не определён. Если ты смотришь на таблицу users и видишь Анну сверху, а Глеба снизу, это не значит, что «так оно и хранится». База имеет право показывать строки в любом порядке, если ты явно не попросишь её отсортировать. Тот же раздел документации PostgreSQL прямо предупреждает: «SQL не даёт никаких гарантий относительно порядка строк в таблице». Запомни этот тезис — он отличает реляционные таблицы от списков и от Excel-листов, где порядок ячеек — часть смысла.
Проверь себя. Если в таблице
usersпоменять местами строки Анны и Бориса, изменится ли смысл данных? — Нет. Та же самая таблица, та же самая информация. Смысл несут значения в строках, а не их визуальный порядок.
Схема и данные — это разные вещи
Реляционная модель аккуратно разделяет два уровня:
схема— это «рецепт» таблицы. Имя таблицы, список её колонок, имя и тип каждой колонки, ограничения (например, «email обязателен» или «id уникален»). Схема меняется редко, она описывает форму данных.- данные — это сами строки внутри таблицы. Они меняются постоянно: пользователи регистрируются, что-то обновляют, что-то удаляют.
Это очень похоже на разницу между бланком документа и заполненными бланками. Бланк (схема) — один, и в нём заранее напечатано: «Имя: ___», «Дата рождения: ___». Заполненных бланков (строк) могут быть тысячи, и все они одинаковой формы. Если завтра ты захочешь добавить новое поле в бланк — это изменение схемы, и оно затронет все будущие записи разом. Если ты просто записал нового пользователя — это изменение данных, схема осталась прежней.
Из академического определения реляционной модели: «Схема задаёт имена отношений, их заголовки и ограничения, которые должны выполняться для каждого экземпляра базы данных». Перевод на человеческий: схема — это контракт между тобой и базой о том, как выглядят данные. База будет проверять каждую новую запись на соответствие этому контракту.
Тип колонки: дисциплина, которой нет в Excel
Каждая колонка в реляционной таблице имеет тип данных. Тип — это правило, какие значения вообще допустимы в этой колонке. Утрированно, типы можно сгруппировать так:
- целые числа —
idпользователя, количество товара в заказе. - числа с дробной частью — сумма заказа, цена.
- текст — имя, email, описание.
- даты и моменты времени — когда пользователь зарегистрировался, когда сделан заказ.
- логические значения — «подтверждён ли email», «активен ли пользователь».
Важный момент: тип колонки одинаков для всех строк. Если колонка created_at объявлена как «дата и время», то ни в одной строке там не появится текст «вчера утром». Документация PostgreSQL поясняет смысл типа так: «Тип данных ограничивает множество возможных значений, которые можно присвоить колонке, и придаёт смысл хранимым данным, чтобы их можно было использовать в вычислениях». Иначе говоря, тип нужен не только для проверки, но и чтобы база понимала, что вообще можно делать со значением: складывать его, сравнивать как дату, искать как текст.
Сравни с Excel: там в одну колонку «дата» легко проскальзывает строка «не помню точно», и формулы перестают работать. Реляционная база такой строки просто не примет — она отклонит запись с осмысленной ошибкой ещё до того, как «грязные» данные попадут внутрь. Это и есть та самая дисциплина, которую мы получаем взамен на чуть более строгие правила игры.
(В этом курсе мы пока намеренно говорим о типах общо — «текст», «число», «дата». Конкретные имена типов разнятся между разными СУБД, и мы вернёмся к ним детально в более поздних модулях.)
Уникальная строка и идея «первичного ключа»
Допустим, у нас в users появились два Бориса с одинаковыми именами и одинаковыми email — что делать? Как мы поймём, какой из них тот самый, к которому относится сегодняшний заказ? Реляционная модель предлагает простое и сильное решение: у каждой строки должен быть способ её однозначно опознать.
Для этого выбирается одна или несколько колонок, чьи значения не повторяются от строки к строке. Такая колонка называется первичный ключ (primary key). В нашей таблице это колонка id: значения 1, 2, 3, 4 — все разные, и каждое из них указывает ровно на одного пользователя. Канонически: «первичный ключ — это ключ-кандидат, выбранный по соглашению как предпочтительный уникальный идентификатор кортежей». Без паники: на этом этапе тебе достаточно понять идею — у каждой строки есть «адрес», по которому к ней можно вернуться.
Зачем нужен такой адрес? Главным образом — чтобы:
- обращаться к конкретной строке («покажи мне пользователя с id = 3»),
- обновлять её, не задевая остальные («у пользователя id = 3 поменялся email»),
- ссылаться на неё из других таблиц — об этом следующий раздел.
Часто первичным ключом делают именно техническую колонку id с автоматически возрастающими числами. Это удобно: число короткое, не зависит от смены email или имени, и его легко передавать. Но в принципе ключом может быть и сам email, и любая другая колонка, лишь бы значения в ней не повторялись и не менялись со временем.
Связи между таблицами: один-ко-многим
Самое интересное в реляционной модели — то, что таблицы умеют ссылаться друг на друга. Без этого мы бы просто получили один большой Excel. А с этим — настоящий граф взаимосвязанных сущностей.
Представим, что наши пользователи делают заказы. Заказ — это отдельная сущность: у него есть свой идентификатор, своё содержимое, своя сумма. Завести колонку «заказы» прямо внутри таблицы users плохо: у одного пользователя может быть и ноль, и десять, и сто заказов, мы просто не уместим их в одну ячейку. Поэтому в реляционной модели заказы живут в отдельной таблице, а связь с пользователем хранится в виде ссылки на его id:
| id | user_id | product | amount |
|---|---|---|---|
| 10 | 1 | Курс «SQL basics» | 4900 |
| 11 | 2 | Курс «SQL basics» | 4900 |
| 12 | 1 | Курс «JS basics» | 5900 |
| 13 | 3 | Подписка Pro | 1490 |
| 14 | 1 | Доп. модуль | 990 |
Прочитай эту таблицу вместе с предыдущей. Колонка user_id в orders — это не имя пользователя и не его email. Это значение первичного ключа id из таблицы users. Строка orders.id = 10 сообщает: «заказ номер 10, который сделал пользователь с id = 1, на сумму 4900». Глядя в users, мы видим, что id = 1 — это Анна. Значит, заказ принадлежит Анне.
У одного пользователя может быть несколько строк в orders (Анна сделала три заказа: 10, 12 и 14). У одного заказа — ровно один пользователь. Такая связь называется один-ко-многим: «один пользователь — много заказов». Это самый частый и самый базовый вид связи в реляционных базах. Есть и другие виды (например, многие-ко-многим), но к ним мы вернёмся в отдельном модуле.
Важно усвоить идею, а не имя. Идея: вместо того чтобы дублировать данные о пользователе в каждой записи о заказе, мы храним пользователя один раз — в users, а в orders держим только короткую ссылку на его id. Если Анна изменит email, нам не придётся переписывать пять её заказов: email живёт в одной строке users, а заказы продолжают спокойно ссылаться на её id.
Проверь себя. В таблице
ordersвыше — сколько разных пользователей сделали заказы и сколько всего заказов у Анны? Подсказка: посмотри на колонкуuser_id.
Типичные заблуждения новичков
Когда люди впервые слышат «таблицы, строки, колонки», у них в голове чаще всего всплывает Excel. Это нормально и даже полезно как стартовая аналогия, но у неё есть три ловушки, в которые легко попасть:
- «Порядок строк — это часть данных». Нет. В реляционной таблице строки — множество, а не список. Если тебе важно показать данные в каком-то порядке (например, сначала самые новые регистрации), ты должен явно попросить базу отсортировать. Без явного запроса порядок не гарантируется, даже если он выглядит стабильным.
- «Таблица — это лист Excel». Похоже, но строже. В Excel в одну колонку можно вписать число, в другую — текст, в третью — пустоту, и никто не возмутится. В реляционной таблице каждый столбец имеет свой тип, и значения, не подходящие под тип, просто не примутся. И ещё: в Excel ячейка может содержать формулу, которая считается «на лету», а реляционная таблица хранит готовые значения (вычисляемые поля бывают, но это отдельная тема и сейчас не наша забота).
- «Колонки можно добавлять прямо во время работы, как удобно». Можно, но это изменение схемы — то есть контракта, на который опирается весь остальной код. Такие изменения делаются осознанно и обычно — отдельным шагом, а не «на ходу». В Excel ты просто пишешь в колонку H первое попавшееся слово, в реляционной базе так делать нельзя: колонки H там нет, пока ты её явно не объявил.
Если ты будешь помнить эти три отличия, ментальный переход от Excel к реляционным таблицам пройдёт почти безболезненно.
Что унести из урока
- Реляционная модель — это способ описывать данные как набор таблиц (
отношений) со строгими правилами. - В таблице есть
колонки(фиксированные свойства с типом) истроки(отдельные записи). Строка обязана соответствовать форме, заданной колонками. Схема(рецепт таблицы) и сами данные — разные вещи. Схема меняется редко, данные — постоянно.- Каждая колонка имеет тип, и этот тип одинаков для всех строк. Тип защищает от мусора и придаёт значениям смысл.
- У каждой строки должен быть способ её опознать —
первичный ключ. Через него же другие таблицы ссылаются на неё, образуя связи (например, один-ко-многим).
Мост к следующему уроку
Мы поняли, как реляционная модель устроена концептуально. Но как именно говорить с базой данных на эту тему — какими словами просить «покажи мне всех пользователей» или «добавь заказ»? Для этого существует специальный язык — SQL, и у него есть несколько диалектов, которые тонко отличаются друг от друга. Об этом — в уроке 1-3.