Чтобы овладеть принципами организации данных в реляционных базах, нужно не только знать формы нормализации и реляционную алгебру. Важно понимать смыслы, которые лежат вне области программирования.
Программы, которые пишут программисты, всегда создаются под определенную предметную область. Например, бухгалтерский софт основывается на правилах ведения бухгалтерского учета, а сайт для просмотра сериалов — на таких понятиях из телеиндустрии, как «сезон» или «эпизод».
Понимать предметную область, для которой вы пишете программу, так же важно, как и уметь программировать. Иногда область может быть по-настоящему сложной, например, бухгалтерия или технологическое производство. Но общее понимание все же требуется.
В этом уроке мы узнаем, что такое онтология и как она помогает описывать предметную область программы и показывать связи ее сущностей. В итоге мы разберем каждый тип связи и поймем, в каких случаях они будут полезны. Также рассмотрим, как эти связи визуализируются.
Что такое онтология
Рассмотрим Хекслет, так как вы с ним знакомы. Чтобы понимать предметную область Хекслета, нужно выделить ключевые понятия — сущности, вокруг которых строится вся логика. У обучающих ресурсов это, как правило, «курс» и «урок». Но на самом деле сущностей гораздо больше.
В случае Хекслета еще можно выделить «профессию», «испытание», «code review», «квиз», «участника курса», «проект». Этот список можно продолжать еще долго. Но сущности не существуют сами по себе, а находятся во взаимоотношениях друг с другом.
Например, квиз содержит или агрегирует в себе вопросы, в которых есть ответы. Профессия состоит из курсов, а курсы из уроков, уроки — из теории, квиза и практики. Эти связи имеют конкретные названия:
-
Один ко многим, one-to-many или o2m. Например, когда один урок может находиться только в одном курсе, но курс содержит множество уроков
-
Один к одному, one-to-one или o2o. Например, на Хекслете такая связь установлена между пользователем и аккаунтом на Facebook
-
Многие ко многим, many-to-many или m2m. Например, когда один курс могут проходить много пользователей, и один пользователь может проходить много курсов
Так это выглядит на схеме:
Описание объектов рассматриваемой области и связей между ними называется онтологией предметной области. Эту онтологию хорошо знают эксперты соответствующей области: в бухгалтерии — бухгалтер, в обучении — преподаватель. Но, в отличие от программистов, они часто представляют ее на интуитивном уровне, неформально.
На практике программисты, бизнес-аналитики или менеджеры общаются с заказчиками, которые могут сами выступать в роли экспертов, и строят вместе с ними формальную онтологию. То есть выделяют конкретные термины, договариваются, что они означают и как связаны друг с другом.
Затем с помощью ER-модели программист формирует необходимую модель данных. Необязательно на бумаге или в специализированных программах. Чаще такая модель существует только в голове и коде.
Эта модель и становится основной для проектирования базы данных. Каждая сущность в реляционной базе данных представлена таблицей, а связи между сущностями реализуются через внешние ключи.
Чтобы визуализировать ER-модель, используют диаграмму Entity-Relationship Diagram (ERD). Познакомимся с ней.
Что такое ERD
У ER-модели нет графического представления, поэтому используют ERD. Многие понимают под ER-моделью и ERD одно и то же. Хотя ER-модель можно представить и с помощью других нотаций.
В ERD каждая сущность представлена блоком, в котором перечисляются поля. Между блоками рисуются линии, у которых есть некоторые заранее определенные концы. Они определяют тип связи между сущностями:
Рассмотрим подробнее каждый вид связи.
Один ко многим (one-to-many)
Один ко многим — наиболее распространенный вид связи. Например, один лектор может вести несколько курсов:
Технически, такая связь организуется через внешний ключ, который добавляется в зависимую сущность — many.
Допустим, у нас есть две исходные таблицы:
users
emails
Чтобы узнать все адреса почты, которые есть у пользователя с идентификатором 1, нужно выполнить такой запрос:
Один к одному (one-to-one)
На схеме такая связь выглядит так:
Например, у каждой страны есть одна столица:
countries
cities
Связь one-to-one обычно существует не сама по себе, а внутри связи one-to-many. То есть у каждой страны есть города, но только один из них столица.
Многие ко многим (many-to-many)
Такие связи можно отобразить так:
Многие ко многим встречается очень часто. Например, у каждого человека множество друзей, каждый человек друг для множества других. Или один человек проходит множество курсов, один курс проходится множеством людей.
Эта связь реализуется уже не так просто. Технически невозможно связать две таблицы связью many-to-many без введения третьей таблицы.
Например, у нас есть две исходные таблицы:
users
courses
Эта таблица будет связующей:
course_members
В таблице course_members есть свой первичный ключ, и каждая запись содержит ссылки как на конкретный курс, так и на конкретного пользователя. На Хекслете эта таблица начинает заполняться в тот момент, когда пользователь нажимает кнопку «Вступить в курс». Для юзера создается запись с его идентификатором и идентификатором курса, который он собрался проходить.
Если мы захотим узнать все курсы, которые проходит пользователь, то выполним такой запрос:
Если захотим узнать, кто проходит данный курс, то такой запрос:
Такая структура соблюдается для любых двух сущностей, которые надо связать. В общем виде схема выглядит так:
Есть исходные таблицы A и B. Для них создается новая таблица AB. Внутри нее есть два внешних ключа — a_id и b_id, которые связаны с исходными таблицами.
Как показывает практика, такая промежуточная таблица часто становится самостоятельной сущностью. Если брать курсы, то важно понимать, закончил ли пользователь курс или нет, когда конкретно он это сделал, сколько заданий решил. Вся эта информация может храниться только в одном месте — в связанной таблице.
Выводы
В этом уроке мы узнали, что онтология помогает описывать предметную область программы и показывать связи ее сущностей. В реляционных базах данных используют три типа связи: один ко многим, один к одному и многие ко многим. А визуализируются они с помощью Entity-Relationship Diagram.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 18:24:41 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="U5xnt9xSsgfRtRv_uuJfmPjtEdxrcVD4sRmLOGEjSiy8TayALiwfZ2f2P2e27a_vOOQ8dmNGrloM-RFsMyStQg";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Онтология | Основы реляционных баз данных</title>
<meta name="description" content="Онтология / Основы реляционных баз данных: Знакомимся с сущностями, связями и ERD">
<link rel="canonical" href="https://ru.hexlet.io/courses/rdb-basics/lessons/ontology/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Онтология">
<meta property="og:title" content="Основы реляционных баз данных">
<meta property="og:description" content="Онтология / Основы реляционных баз данных: Знакомимся с сущностями, связями и ERD">
<meta property="og:url" content="https://ru.hexlet.io/courses/rdb-basics/lessons/ontology/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="N_GgsU2B2o7WyFDLedTtyRdyy6fzd8nthj0ANuWpNEnYIGuGv_937mCLdFN12x2-13vmDftAN0873Zpit67TJw" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T18:24:40.885Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"eWdo2dEm8kilv1tOwUZVxdQ9F1K_9_niq56B4G0pJSqWtqPuI1hfKBP8f9bNSaWyFDQ6-LfAB0AWfhu0Py7CRA","topics":[{"id":42130,"title":"На мой локальной машине выполнить такие запросы невозможно \nУ меня установлен psql 12, выдаёт ошибку\n\nCREATE TABLE goods (\n id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n name varchar(255),\n price integer\n);\n\nERROR: syntax error at or near \"GENERATED\"\nLINE 2: id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n\n\n","plain_title":"На мой локальной машине выполнить такие запросы невозможно У меня установлен psql 12, выдаёт ошибку CREATE TABLE goods ( id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY, name varchar(255), price integer ); ERROR: syntax error at or near \"GENERATED\" LINE 2: id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, ","creator":{"public_name":"Viktor Shiyan","id":226996,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":91731,"body":"Приветствую, Виктор!\n\nНа 12-й версии всё должно работать, я как раз проверил данный код на версии 12.2. Порекомендую вам свериться не допускаете ли где-то ошибку при вводе. Попробуйте ввести в psql поочередно строку за строкой, напишите потом что у вас получилось.","topic_id":42130}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":32208,"title":"А почему в таблице orders_item есть поле item_id, а не good_id? Слово good лучше не использовать?)","plain_title":"А почему в таблице ordersitem есть поле itemid, а не good_id? Слово good лучше не использовать?) ","creator":{"public_name":"Денис Потехин","id":239601,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":69912,"body":"Черт попутал) Поправил, спасибо!","topic_id":32208}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":44506,"title":"а почему у учителя price это integer?\n \nВ уроках выше говорилось что для денЯк луше использовать numeric спциальный для этого тип даных?\n\nhttps://ru.hexlet.io/code_reviews/283032","plain_title":"а почему у учителя price это integer? В уроках выше говорилось что для денЯк луше использовать numeric спциальный для этого тип даных? https://ru.hexlet.io/code_reviews/283032 ","creator":{"public_name":"Igor Linder","id":261935,"is_tutor":false},"comments":[{"creator":{"public_name":"Sergei Melodyn","id":162475,"is_tutor":true},"id":96281,"body":"**Igor Linder**, приветствую.\n\nВ данном случае нам не важна высокая точность, поэтому сделано такое допущение.","topic_id":44506},{"creator":{"public_name":"Maksim Do","id":228677,"is_tutor":false},"id":103027,"body":"А как же специальный тип Money?","topic_id":44506}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":30455,"title":"При создании таблиц имеет значение последовательность, я правильно понял? Например у вас в решении это\nCREATE TABLE goods ();\nCREATE TABLE orders ();\nCREATE TABLE order_items ();\n\nт.е. возможен такой порядок, при котором тест не проходит?\n\n","plain_title":"При создании таблиц имеет значение последовательность, я правильно понял? Например у вас в решении это CREATE TABLE goods (); CREATE TABLE orders (); CREATE TABLE order_items (); т.е. возможен такой порядок, при котором тест не проходит? ","creator":{"public_name":"Konstantin Zharinov","id":181349,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":66045,"body":"Конечно, ведь если вы создаете внешний ключ, то таблица на которую он ссылается должна существовать.","topic_id":30455},{"creator":{"public_name":"Олег Шалгуев","id":183794,"is_tutor":false},"id":86325,"body":"**Kirill Mokevnin**, вот это для меня было вообще неочевидно и не понимал почему тест не проходит.\nБудто это не Structured, а какой-нибудь Sequential Query Language =)","topic_id":30455}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":22159,"title":"Entity-Relationship **Diagramm** - с одной m :)","plain_title":"Entity-Relationship Diagramm - с одной m :) ","creator":{"public_name":"Zahar Steblovskiy","id":111870,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":47177,"body":"fixed! Кстати, вы можете отправлять ошибки с помощью `Ctrl + Enter`","topic_id":22159}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":44746,"title":"Пофиксите картинку плз: https://prnt.sc/t9w1z3","plain_title":"Пофиксите картинку плз: https://prnt.sc/t9w1z3 ","creator":{"public_name":"Валдис Пельш","id":275012,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":96790,"body":"Валдис, приветствую!\n\nСейчас проверил, картинка отображается. Попробуйте выполнить сброс упражнения, думаю это должно помочь. Напишите потом, пожалуйста, какой в итоге результат.","topic_id":44746}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":28883,"title":"\"Понимание предметной области, для которой вы пишите программу, также важно, как и умение программировать\"\nВ этом случае \"так же\" пишется раздельно. Потому что \"же\" можно вообще опустить.","plain_title":"\"Понимание предметной области, для которой вы пишите программу, также важно, как и умение программировать\" В этом случае \"так же\" пишется раздельно. Потому что \"же\" можно вообще опустить. ","creator":{"public_name":"Pavel Morozov","id":231143,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":62473,"body":"Спасибо! Поправил.","topic_id":28883}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":72240,"title":"https://ru.hexlet.io/code_reviews/669821\n\nОбъясните, пожалуйста, почему порядок создания таблиц влияет на результат. Сперва сделал в порядке, как указано на иллюстрации: orders, orders_items, goods. Выпадала ошибка. Пришлось открыть решение и сравнить - мое решение отличалось от учительского только порядком создания таблиц. ","plain_title":"https://ru.hexlet.io/code_reviews/669821 Объясните, пожалуйста, почему порядок создания таблиц влияет на результат. Сперва сделал в порядке, как указано на иллюстрации: orders, orders_items, goods. Выпадала ошибка. Пришлось открыть решение и сравнить - мое решение отличалось от учительского только порядком создания таблиц. ","creator":{"public_name":"Игорь","id":438960,"is_tutor":false},"comments":[{"creator":{"public_name":"Игорь","id":438960,"is_tutor":false},"id":150803,"body":"Ошибка:\n\nmake: Entering directory '/usr/src/app'\nDEBUG=knex:query npm test -s\nBrowserslist: caniuse-lite is outdated. Please run:\n npx browserslist@latest --update-db\n Why you should do it regularly: https://github.com/browserslist/browserslist#browsers-data-updating\n2022-08-08T09:15:53.395Z knex:query DROP TABLE IF EXISTS users CASCADE;\nCREATE TABLE users (\n id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n email varchar(255),\n first_name varchar(255)\n);\n undefined\n2022-08-08T09:15:53.412Z knex:query BEGIN; trx2\n2022-08-08T09:15:53.415Z knex:query drop table if exists \"order_items\" trx2\n2022-08-08T09:15:53.417Z knex:query drop table if exists \"orders\" trx2\n2022-08-08T09:15:53.418Z knex:query drop table if exists \"goods\" trx2\n2022-08-08T09:15:53.419Z knex:query --noqa: disable=L010\n-- BEGIN (write your solution here)\nCREATE TABLE orders (\n id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n user_id bigint REFERENCES users(id),\n created_at timestamp\n);\n\nCREATE TABLE order_items (\n id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n order_id bigint REFERENCES orders(id),\n price integer,\n good_id bigint REFERENCES goods(id)\n);\n\nCREATE TABLE goods (\n id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n name varchar(255),\n price integer\n);\n-- END\n trx2\n2022-08-08T09:15:53.429Z knex:query ROLLBACK trx2\n FAIL __tests__/test.js\n ✕ solution (55 ms)\n\n ● solution\n\n error: --noqa: disable=L010\n -- BEGIN (write your solution here)\n CREATE TABLE orders (\n id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n user_id bigint REFERENCES users(id),\n created_at timestamp\n );\n\n CREATE TABLE order_items (\n id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n order_id bigint REFERENCES orders(id),\n price integer,\n good_id bigint REFERENCES goods(id)\n );\n\n CREATE TABLE goods (\n id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n name varchar(255),\n price integer\n );\n -- END\n - relation \"goods\" does not exist\n\n at Parser.parseErrorMessage (../../local/lib/hexlet-javascript/node_modules/pg-protocol/src/parser.ts:369:69)\n at Parser.handlePacket (../../local/lib/hexlet-javascript/node_modules/pg-protocol/src/parser.ts:188:21)\n at Parser.parse (../../local/lib/hexlet-javascript/node_modules/pg-protocol/src/parser.ts:103:30)\n at Socket.<anonymous> (../../local/lib/hexlet-javascript/node_modules/pg-protocol/src/index.ts:7:48)\n\nTest Suites: 1 failed, 1 total\nTests: 1 failed, 1 total\nSnapshots: 0 total\nTime: 0.944 s, estimated 1 s\nRan all test suites.\nmake: *** [Makefile:5: test] Error 1\nmake: Leaving directory '/usr/src/app'","topic_id":72240},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":150908,"body":"> Объясните, пожалуйста, почему порядок создания таблиц влияет на результат.\n\nВы ссылаетесь на таблицу `goods` при создании `order_items`\n```\ngood_id bigint REFERENCES goods(id)\n```\nно ведь она еще не создана.","topic_id":72240}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":53563,"title":"Похоже у вас тесты [не рабочие](https://ru.hexlet.io/code_reviews/325618).\n```\nconsole.log\n user.id=> undefined\n\n at __tests__/test.js:28:15\n\n console.log\n good.id=> undefined\n\n at __tests__/test.js:30:15\n\n console.log\n order.id=> undefined\n```\nв `knex` возвращается результат отработаной функции, а не инстанс. Не хватает чего-то вроде `const user = await trx.from('users').first();`","plain_title":"Похоже у вас тесты не рабочие (https://ru.hexlet.io/code_reviews/325618). В тесты добавил console.log ``` console.log user.id=> undefined at __tests__/test.js:28:15 console.log good.id=> undefined at __tests__/test.js:30:15 console.log order.id=> undefined `` вknexвозвращается результат отработаной команды, а не инстанс. Не хватает чего-то вродеconst user = await trx.from('users').first();` ","creator":{"public_name":"Denys Bondarenko","id":262196,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":113998,"body":"Спасибо, Денис! Вы правы. Я допилил тесты, теперь всё окей. Чтобы обновить упражнение, нажмите на кнопку \"Сброс\".","topic_id":53563}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}},{"id":78806,"title":"Раздел многие ко многим, там 3 таблицы, дальше демонстрируется пример где в обоих примерах используется id = 3, ни в одной из трех таблиц я не вижу чтобы id было равно 3. Я чего то не понимаю или это ошибка?","plain_title":"Раздел многие ко многим, там 3 таблицы, дальше демонстрируется пример где в обоих примерах используется id = 3, ни в одной из трех таблиц я не вижу чтобы id было равно 3. Я чего то не понимаю или это ошибка? ","creator":{"public_name":"","id":291980,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":162610,"body":"Это не ошибка. Теоретически, у нас может быть пользователь с id равным 3, как и курс с таким же id. ","topic_id":78806}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Онтология","entity_url":null,"active":true}}],"lesson":{"exercise":null,"units":[{"id":2630,"name":"theory","url":"/courses/rdb-basics/lessons/ontology/theory_unit"},{"id":2804,"name":"quiz","url":"/courses/rdb-basics/lessons/ontology/quiz_unit"}],"links":[{"id":422883,"name":"Онтология (информатика)","url":"https://ru.wikipedia.org/wiki/%D0%9E%D0%BD%D1%82%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D1%8F_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)"}],"ordered_units":[{"id":2630,"name":"theory","url":"/courses/rdb-basics/lessons/ontology/theory_unit"},{"id":2804,"name":"quiz","url":"/courses/rdb-basics/lessons/ontology/quiz_unit"}],"id":1260,"slug":"ontology","state":"approved","name":"Онтология","course_order":495,"goal":"Знакомимся с сущностями, связями и ERD","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Чтобы овладеть принципами организации данных в реляционных базах, нужно не только знать формы нормализации и реляционную алгебру. Важно понимать смыслы, которые лежат вне области программирования.\n\nПрограммы, которые пишут программисты, всегда создаются под определенную предметную область. Например, бухгалтерский софт основывается на правилах ведения бухгалтерского учета, а сайт для просмотра сериалов — на таких понятиях из телеиндустрии, как «сезон» или «эпизод».\n\nПонимать предметную область, для которой вы пишете программу, так же важно, как и уметь программировать. Иногда область может быть по-настоящему сложной, например, бухгалтерия или технологическое производство. Но общее понимание все же требуется.\n\nВ этом уроке мы узнаем, что такое онтология и как она помогает описывать предметную область программы и показывать связи ее сущностей. В итоге мы разберем каждый тип связи и поймем, в каких случаях они будут полезны. Также рассмотрим, как эти связи визуализируются.\n\n## Что такое онтология\n\nРассмотрим Хекслет, так как вы с ним знакомы. Чтобы понимать предметную область Хекслета, нужно выделить ключевые понятия — **сущности**, вокруг которых строится вся логика. У обучающих ресурсов это, как правило, «курс» и «урок». Но на самом деле сущностей гораздо больше.\n\nВ случае Хекслета еще можно выделить «профессию», «испытание», «code review», «квиз», «участника курса», «проект». Этот список можно продолжать еще долго. Но сущности не существуют сами по себе, а находятся во взаимоотношениях друг с другом.\n\nНапример, квиз содержит или агрегирует в себе вопросы, в которых есть ответы. Профессия состоит из курсов, а курсы из уроков, уроки — из теории, квиза и практики. Эти связи имеют конкретные названия:\n\n* **Один ко многим**, **one-to-many** или **o2m**. Например, когда один урок может находиться только в одном курсе, но курс содержит множество уроков\n\n* **Один к одному**, **one-to-one** или **o2o**. Например, на Хекслете такая связь установлена между пользователем и аккаунтом на Facebook\n\n* **Многие ко многим**, **many-to-many** или **m2m**. Например, когда один курс могут проходить много пользователей, и один пользователь может проходить много курсов\n\nТак это выглядит на схеме:\n\n\n\nОписание объектов рассматриваемой области и связей между ними называется **онтологией** предметной области. Эту онтологию хорошо знают эксперты соответствующей области: в бухгалтерии — бухгалтер, в обучении — преподаватель. Но, в отличие от программистов, они часто представляют ее на интуитивном уровне, неформально.\n\nНа практике программисты, бизнес-аналитики или менеджеры общаются с заказчиками, которые могут сами выступать в роли экспертов, и строят вместе с ними формальную онтологию. То есть выделяют конкретные термины, договариваются, что они означают и как связаны друг с другом.\n\nЗатем с помощью [ER-модели](https://ru.wikipedia.org/wiki/ER-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C) программист формирует необходимую модель данных. Необязательно на бумаге или в специализированных программах. Чаще такая модель существует только в голове и коде.\n\nЭта модель и становится основной для проектирования базы данных. Каждая сущность в реляционной базе данных представлена таблицей, а связи между сущностями реализуются через внешние ключи.\n\nЧтобы визуализировать ER-модель, используют диаграмму Entity-Relationship Diagram (ERD). Познакомимся с ней.\n\n## Что такое ERD\n\nУ ER-модели нет графического представления, поэтому используют ERD. Многие понимают под ER-моделью и ERD одно и то же. Хотя ER-модель можно представить и с помощью других нотаций.\n\nВ ERD каждая сущность представлена блоком, в котором перечисляются поля. Между блоками рисуются линии, у которых есть некоторые заранее определенные концы. Они определяют тип связи между сущностями:\n\n\n\nРассмотрим подробнее каждый вид связи.\n\n### Один ко многим (one-to-many)\n\nОдин ко многим — наиболее распространенный вид связи. Например, один лектор может вести несколько курсов:\n\n\n\nТехнически, такая связь организуется через внешний ключ, который добавляется в зависимую сущность — many.\n\nДопустим, у нас есть две исходные таблицы:\n\n**users**\n\n| id | first_name | last_name | created_at |\n|----|------------|-----------|------------|\n| 1 | Сергей | Петров | 11.10.2005 |\n| 38 | Иван | Носов | 03.08.2000 |\n| 22 | Виктор | Пирогов | 23.12.2011 |\n\n**emails**\n\n| id | user_id | email |\n|----|---------|-------------------|\n| 1 | 1 | serj@gmail.com |\n| 2 | 1 | petrov@mail.ru |\n| 10 | 38 | ivan@yahoo.com |\n| 22 | 22 | vkurg@indbox.com |\n\nЧтобы узнать все адреса почты, которые есть у пользователя с идентификатором `1`, нужно выполнить такой запрос:\n\n```sql\nSELECT * FROM emails WHERE user_id = 1;\n```\n\n### Один к одному (one-to-one)\n\nНа схеме такая связь выглядит так:\n\n\n\nНапример, у каждой страны есть одна столица:\n\n**countries**\n\n| id | name | created_at |\n|----|------------|------------|\n| 2 | Russia | 11.10.2005 |\n| 38 | Spain | 03.08.2000 |\n| 22 | Germany | 23.12.2011 |\n\n**cities**\n\n| id | name | country_id | capital | created_at |\n|------|-----------|-------------|---------|------------|\n| 34 | Moscow | 2 | true | 11.10.2005 |\n| 33 | Valencia | 38 | | 03.08.2000 |\n| 99 | Voronezh | 2 | | 23.12.2011 |\n| 4 | Ulyanovsk | 2 | | 23.12.2011 |\n| 5 | Berlin | 22 | true | 23.12.2011 |\n\nСвязь one-to-one обычно существует не сама по себе, а внутри связи one-to-many. То есть у каждой страны есть города, но только один из них столица.\n\n```sql\nSELECT * FROM cities WHERE country_id = 38 AND capital = true;\n```\n\n### Многие ко многим (many-to-many)\n\nТакие связи можно отобразить так:\n\n\n\nМногие ко многим встречается очень часто. Например, у каждого человека множество друзей, каждый человек друг для множества других. Или один человек проходит множество курсов, один курс проходится множеством людей.\n\nЭта связь реализуется уже не так просто. Технически невозможно связать две таблицы связью many-to-many без введения третьей таблицы.\n\nНапример, у нас есть две исходные таблицы:\n\n**users**\n\n| id | first_name | created_at |\n|----|------------|------------|\n| 2 | Сергей | 11.10.2005 |\n| 38 | Иван | 03.08.2000 |\n| 22 | Виктор | 23.12.2011 |\n\n**courses**\n\n| id | name | created_at |\n|----|---------------|------------|\n| 8 | PHP basics | 11.10.2005 |\n| 55 | Python basics | 03.08.2000 |\n| 22 | Ruby basics | 23.12.2011 |\n\nЭта таблица будет связующей:\n\n**course_members**\n\n| id | user_id | course_id | created_at |\n|------|-----------|-------------|------------|\n| 34 | 2 | 8 | 11.10.2005 |\n| 33 | 38 | 55 | 03.08.2000 |\n| 99 | 22 | 22 | 23.12.2011 |\n| 4 | 22 | 8 | 23.12.2011 |\n| 5 | 38 | 22 | 23.12.2011 |\n\nВ таблице `course_members` есть свой первичный ключ, и каждая запись содержит ссылки как на конкретный курс, так и на конкретного пользователя. На Хекслете эта таблица начинает заполняться в тот момент, когда пользователь нажимает кнопку «Вступить в курс». Для юзера создается запись с его идентификатором и идентификатором курса, который он собрался проходить.\n\nЕсли мы захотим узнать все курсы, которые проходит пользователь, то выполним такой запрос:\n\n```sql\nSELECT course_id FROM course_members WHERE user_id = 3;\n```\n\nЕсли захотим узнать, кто проходит данный курс, то такой запрос:\n\n```sql\nSELECT user_id FROM course_members WHERE course_id = 3;\n```\n\nТакая структура соблюдается для любых двух сущностей, которые надо связать. В общем виде схема выглядит так:\n\nЕсть исходные таблицы _A_ и _B_. Для них создается новая таблица _AB_. Внутри нее есть два внешних ключа — _a_id_ и _b_id_, которые связаны с исходными таблицами.\n\nКак показывает практика, такая промежуточная таблица часто становится самостоятельной сущностью. Если брать курсы, то важно понимать, закончил ли пользователь курс или нет, когда конкретно он это сделал, сколько заданий решил. Вся эта информация может храниться только в одном месте — в связанной таблице.\n\n## Выводы\n\nВ этом уроке мы узнали, что онтология помогает описывать предметную область программы и показывать связи ее сущностей. В реляционных базах данных используют три типа связи: один ко многим, один к одному и многие ко многим. А визуализируются они с помощью Entity-Relationship Diagram.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":2631,"name":"theory","url":"/courses/rdb-basics/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":2631,"name":"theory","url":"/courses/rdb-basics/lessons/intro/theory_unit"}],"id":1261,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с курсом","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"\n\nЛюбая нетривиальная программная система состоит минимум из двух компонентов:\n\n* Программного кода, отвечающего за логику работы\n* Хранилища, в котором содержатся данные, необходимые для работы\n\nВозьмем для примера Хекслет. Это большой контентный проект, внутри которого есть данные: например, курсы и их содержимое.\n\nБолее того, довольно много контента создается самими пользователями: они вводят данные при регистрации, создают и заполняют профили, описывают свои решения и переписываются с поддержкой через топики. Все эти данные непрерывно добавляются на сайт и только увеличиваются в объемах.\n\nПри этом данные нужно не только хранить, но и непрерывно считывать и показывать пользователям. Причем иногда в довольно хитрых формах, например: «Показать на странице испытания три последних код-ревью по этому испытанию, исключая код-ревью пользователя, который прямо сейчас смотрит на эту страницу».\n\nПодобные сложные запросы к системе хранения делаются ежесекундно, и многие из них значительно сложнее приведенного примера.\n\nКроме того, пользователи на сайтах работают независимо друг от друга, что создает конкуренцию при обращении к одним и тем же данным. А что, если один пользователь читает данные, которые в это время модифицируются другим пользователем?\n\nЗа реализацию всех этих возможностей отвечают **системы управления базами данных (СУБД)**. Как правило, они отвязаны от того языка программирования, на котором идет разработка. СУБД — это отдельная программа, которая умеет эффективно сохранять и читать то, что ее попросят.\n\nТаких систем существует довольно много, но это не значит, что все они абсолютно разные. Схожие системы делят на классы — множества систем со схожими характеристиками.\n\nЧаще всего используются **реляционные СУБД (РСУБД)** — системы, работа которых основана на **реляционной модели данных**. С технической точки зрения такие базы данных основаны на табличном представлении данных.\n\nДля работы с ними нужно понимать две основные вещи:\n\n* Реляционная модель данных — звучит страшно, но в итоге все сводится к таблицам\n* SQL — специализированный язык для управления базой данных и данными внутри нее\n\nОба пункта практически не зависят от реализации конкретной РСУБД. Изучив одну СУБД, вы крайне легко сможете переключиться на другую. В этом курсе мы рассмотрим популярную СУБД PostgreSQL. Она поставляется со специальной программой *psql*, которая представляет собой интерактивную оболочку — REPL. Через нее можно управлять PostgreSQL и взаимодействовать с базами данных, набирая команды в реальном времени.\n\n## Основные темы курса\n\nВ этом курсе мы затронем такие основные темы:\n\n* Установка и настройка\n* Создание и модификация базы данных (DDL)\n* Наполнение базы данных (DML)\n* Выборка данных (`SELECT`, `WHERE`, `ORDER`, `LIMIT`, `DISTINCT`, `GROUP`, `HAVING`)\n* Агрегирующие функции (`COUNT`)\n* Соединения (`JOIN`, `LEFT JOIN`)\n* Транзакции (уровни изоляции)\n* Реляционная алгебра (основы теории множеств)\n* Ключи (первичные, внешние)\n* Нормализация данных\n* Связи (один-к-одному, один-ко-многим, многие-ко-многим)\n* Производительность (индексы)\n\nВся практика в этом курсе выполняется в нашей среде, но если вы разработчик или вам это необходимо, то настройте базу данных локально для экспериментов. Эффективное обучение невозможно без повторения примеров из уроков в своей среде. Поэтому в начале курса необходимо установить PostgreSQL локально. Сделайте это по [инструкции](https://github.com/Hexlet/ru-instructions/blob/main/postgresql.md).\n"},"id":172,"slug":"rdb-basics","challenges_count":0,"name":"Основы реляционных баз данных","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"free","description":"На этом курсе вы изучите основы реляционных баз данных. Вы узнаете больше об архитектуре СУБД и языке SQL. В итоге вы научитесь создавать таблицы, добавлять, модифицировать и удалять данные. Курс пригодится, если вы решите использовать базу данных в вашем приложении или вам нужно использовать данные из базы в любых других местах. Знания из этого курса помогают выполнять запросы для выборки данных, объединять таблицы и использовать транзакции.","kind":"sandbox","updated_at":"2026-01-20T11:39:09.852Z","language":"sql","duration_cache":25500,"skills":["Создавать полноценные базы данных для приложений на любых языках","Правильно организовывать (нормализовать) архитектуру хранения данных с помощью нормальных форм","Отображать предметную область на таблицы с учетом связей между сущностями (o2o, o2m, m2m)","Выполнять запросы на выборку данных по сложным условиям"],"keywords":["postgresql","транзакции","нормальные формы","СУБД"],"lessons_count":24,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjA4MSwicHVyIjoiYmxvYl9pZCJ9fQ==--18d92b90e9f04ddc693b77ace032c133fb2dfdd5/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.png"},"recommendedLandings":[],"lessonMemberUnit":null,"accessToLearnUnitExists":true,"accessToCourseExists":true},"url":"/courses/rdb-basics/lessons/ontology/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Основы реляционных баз данных</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Онтология</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Онтология","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Основы реляционных баз данных"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Чтобы овладеть принципами организации данных в реляционных базах, нужно не только знать формы нормализации и реляционную алгебру. Важно понимать смыслы, которые лежат вне области программирования.</p>
<p>Программы, которые пишут программисты, всегда создаются под определенную предметную область. Например, бухгалтерский софт основывается на правилах ведения бухгалтерского учета, а сайт для просмотра сериалов — на таких понятиях из телеиндустрии, как «сезон» или «эпизод».</p>
<p>Понимать предметную область, для которой вы пишете программу, так же важно, как и уметь программировать. Иногда область может быть по-настоящему сложной, например, бухгалтерия или технологическое производство. Но общее понимание все же требуется.</p>
<p>В этом уроке мы узнаем, что такое онтология и как она помогает описывать предметную область программы и показывать связи ее сущностей. В итоге мы разберем каждый тип связи и поймем, в каких случаях они будут полезны. Также рассмотрим, как эти связи визуализируются.</p>
<h2 id="heading-2-1">Что такое онтология</h2>
<p>Рассмотрим Хекслет, так как вы с ним знакомы. Чтобы понимать предметную область Хекслета, нужно выделить ключевые понятия — <strong>сущности</strong>, вокруг которых строится вся логика. У обучающих ресурсов это, как правило, «курс» и «урок». Но на самом деле сущностей гораздо больше.</p>
<p>В случае Хекслета еще можно выделить «профессию», «испытание», «code review», «квиз», «участника курса», «проект». Этот список можно продолжать еще долго. Но сущности не существуют сами по себе, а находятся во взаимоотношениях друг с другом.</p>
<p>Например, квиз содержит или агрегирует в себе вопросы, в которых есть ответы. Профессия состоит из курсов, а курсы из уроков, уроки — из теории, квиза и практики. Эти связи имеют конкретные названия:</p>
<ul>
<li>
<p><strong>Один ко многим</strong>, <strong>one-to-many</strong> или <strong>o2m</strong>. Например, когда один урок может находиться только в одном курсе, но курс содержит множество уроков</p>
</li>
<li>
<p><strong>Один к одному</strong>, <strong>one-to-one</strong> или <strong>o2o</strong>. Например, на Хекслете такая связь установлена между пользователем и аккаунтом на Facebook</p>
</li>
<li>
<p><strong>Многие ко многим</strong>, <strong>many-to-many</strong> или <strong>m2m</strong>. Например, когда один курс могут проходить много пользователей, и один пользователь может проходить много курсов</p>
</li>
</ul>
<p>Так это выглядит на схеме:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjEwMywicHVyIjoiYmxvYl9pZCJ9fQ==--438b4d5363e7818e5eabc93c6f7a442454cbea58/course-erd.png" alt="ERD" loading="lazy"/></p>
<p>Описание объектов рассматриваемой области и связей между ними называется <strong>онтологией</strong> предметной области. Эту онтологию хорошо знают эксперты соответствующей области: в бухгалтерии — бухгалтер, в обучении — преподаватель. Но, в отличие от программистов, они часто представляют ее на интуитивном уровне, неформально.</p>
<p>На практике программисты, бизнес-аналитики или менеджеры общаются с заказчиками, которые могут сами выступать в роли экспертов, и строят вместе с ними формальную онтологию. То есть выделяют конкретные термины, договариваются, что они означают и как связаны друг с другом.</p>
<p>Затем с помощью <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://ru.wikipedia.org/wiki/ER-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C" rel="noopener noreferrer" target="_blank">ER-модели</a> программист формирует необходимую модель данных. Необязательно на бумаге или в специализированных программах. Чаще такая модель существует только в голове и коде.</p>
<p>Эта модель и становится основной для проектирования базы данных. Каждая сущность в реляционной базе данных представлена таблицей, а связи между сущностями реализуются через внешние ключи.</p>
<p>Чтобы визуализировать ER-модель, используют диаграмму Entity-Relationship Diagram (ERD). Познакомимся с ней.</p>
<h2 id="heading-2-2">Что такое ERD</h2>
<p>У ER-модели нет графического представления, поэтому используют ERD. Многие понимают под ER-моделью и ERD одно и то же. Хотя ER-модель можно представить и с помощью других нотаций.</p>
<p>В ERD каждая сущность представлена блоком, в котором перечисляются поля. Между блоками рисуются линии, у которых есть некоторые заранее определенные концы. Они определяют тип связи между сущностями:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjEwNCwicHVyIjoiYmxvYl9pZCJ9fQ==--9f8d3c97bba05dbd15ebd4f723cfbe1df2be741c/erd-notation.png" alt="ERD Нотация" loading="lazy"/></p>
<p>Рассмотрим подробнее каждый вид связи.</p>
<h3 id="heading-3-3">Один ко многим (one-to-many)</h3>
<p>Один ко многим — наиболее распространенный вид связи. Например, один лектор может вести несколько курсов:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjEwNSwicHVyIjoiYmxvYl9pZCJ9fQ==--d2fa1212b33ffb3967f67b01a17df6f56f98770d/one2many.png" alt="Один ко Многим" loading="lazy"/></p>
<p>Технически, такая связь организуется через внешний ключ, который добавляется в зависимую сущность — many.</p>
<p>Допустим, у нас есть две исходные таблицы:</p>
<p><strong>users</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>id</th><th>first_name</th><th>last_name</th><th>created_at</th></tr></thead><tbody><tr><td>1</td><td>Сергей</td><td>Петров</td><td>11.10.2005</td></tr><tr><td>38</td><td>Иван</td><td>Носов</td><td>03.08.2000</td></tr><tr><td>22</td><td>Виктор</td><td>Пирогов</td><td>23.12.2011</td></tr></tbody></table></div></div></div></div>
<p><strong>emails</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>id</th><th>user_id</th><th>email</th></tr></thead><tbody><tr><td>1</td><td>1</td><td><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="mailto:serj@gmail.com">serj@gmail.com</a></td></tr><tr><td>2</td><td>1</td><td><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="mailto:petrov@mail.ru">petrov@mail.ru</a></td></tr><tr><td>10</td><td>38</td><td><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="mailto:ivan@yahoo.com">ivan@yahoo.com</a></td></tr><tr><td>22</td><td>22</td><td><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="mailto:vkurg@indbox.com">vkurg@indbox.com</a></td></tr></tbody></table></div></div></div></div>
<p>Чтобы узнать все адреса почты, которые есть у пользователя с идентификатором <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">1</code>, нужно выполнить такой запрос:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">SELECT * FROM emails WHERE user_id = 1;</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h3 id="heading-3-4">Один к одному (one-to-one)</h3>
<p>На схеме такая связь выглядит так:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjEwNiwicHVyIjoiYmxvYl9pZCJ9fQ==--61315009e45d44e5eed0b41658b627c022e9f917/one2one.png" alt="Один к Одному" loading="lazy"/></p>
<p>Например, у каждой страны есть одна столица:</p>
<p><strong>countries</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>id</th><th>name</th><th>created_at</th></tr></thead><tbody><tr><td>2</td><td>Russia</td><td>11.10.2005</td></tr><tr><td>38</td><td>Spain</td><td>03.08.2000</td></tr><tr><td>22</td><td>Germany</td><td>23.12.2011</td></tr></tbody></table></div></div></div></div>
<p><strong>cities</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>id</th><th>name</th><th>country_id</th><th>capital</th><th>created_at</th></tr></thead><tbody><tr><td>34</td><td>Moscow</td><td>2</td><td>true</td><td>11.10.2005</td></tr><tr><td>33</td><td>Valencia</td><td>38</td><td></td><td>03.08.2000</td></tr><tr><td>99</td><td>Voronezh</td><td>2</td><td></td><td>23.12.2011</td></tr><tr><td>4</td><td>Ulyanovsk</td><td>2</td><td></td><td>23.12.2011</td></tr><tr><td>5</td><td>Berlin</td><td>22</td><td>true</td><td>23.12.2011</td></tr></tbody></table></div></div></div></div>
<p>Связь one-to-one обычно существует не сама по себе, а внутри связи one-to-many. То есть у каждой страны есть города, но только один из них столица.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">SELECT * FROM cities WHERE country_id = 38 AND capital = true;</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h3 id="heading-3-5">Многие ко многим (many-to-many)</h3>
<p>Такие связи можно отобразить так:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjEwNywicHVyIjoiYmxvYl9pZCJ9fQ==--6155b00a4459ad0ee7e45fdf10c319ccfece09c5/many2many.jpg" alt="Многие ко многим" loading="lazy"/></p>
<p>Многие ко многим встречается очень часто. Например, у каждого человека множество друзей, каждый человек друг для множества других. Или один человек проходит множество курсов, один курс проходится множеством людей.</p>
<p>Эта связь реализуется уже не так просто. Технически невозможно связать две таблицы связью many-to-many без введения третьей таблицы.</p>
<p>Например, у нас есть две исходные таблицы:</p>
<p><strong>users</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>id</th><th>first_name</th><th>created_at</th></tr></thead><tbody><tr><td>2</td><td>Сергей</td><td>11.10.2005</td></tr><tr><td>38</td><td>Иван</td><td>03.08.2000</td></tr><tr><td>22</td><td>Виктор</td><td>23.12.2011</td></tr></tbody></table></div></div></div></div>
<p><strong>courses</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>id</th><th>name</th><th>created_at</th></tr></thead><tbody><tr><td>8</td><td>PHP basics</td><td>11.10.2005</td></tr><tr><td>55</td><td>Python basics</td><td>03.08.2000</td></tr><tr><td>22</td><td>Ruby basics</td><td>23.12.2011</td></tr></tbody></table></div></div></div></div>
<p>Эта таблица будет связующей:</p>
<p><strong>course_members</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>id</th><th>user_id</th><th>course_id</th><th>created_at</th></tr></thead><tbody><tr><td>34</td><td>2</td><td>8</td><td>11.10.2005</td></tr><tr><td>33</td><td>38</td><td>55</td><td>03.08.2000</td></tr><tr><td>99</td><td>22</td><td>22</td><td>23.12.2011</td></tr><tr><td>4</td><td>22</td><td>8</td><td>23.12.2011</td></tr><tr><td>5</td><td>38</td><td>22</td><td>23.12.2011</td></tr></tbody></table></div></div></div></div>
<p>В таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">course_members</code> есть свой первичный ключ, и каждая запись содержит ссылки как на конкретный курс, так и на конкретного пользователя. На Хекслете эта таблица начинает заполняться в тот момент, когда пользователь нажимает кнопку «Вступить в курс». Для юзера создается запись с его идентификатором и идентификатором курса, который он собрался проходить.</p>
<p>Если мы захотим узнать все курсы, которые проходит пользователь, то выполним такой запрос:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">SELECT course_id FROM course_members WHERE user_id = 3;</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если захотим узнать, кто проходит данный курс, то такой запрос:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">SELECT user_id FROM course_members WHERE course_id = 3;</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Такая структура соблюдается для любых двух сущностей, которые надо связать. В общем виде схема выглядит так:</p>
<p>Есть исходные таблицы <em>A</em> и <em>B</em>. Для них создается новая таблица <em>AB</em>. Внутри нее есть два внешних ключа — <em>a_id</em> и <em>b_id</em>, которые связаны с исходными таблицами.</p>
<p>Как показывает практика, такая промежуточная таблица часто становится самостоятельной сущностью. Если брать курсы, то важно понимать, закончил ли пользователь курс или нет, когда конкретно он это сделал, сколько заданий решил. Вся эта информация может храниться только в одном месте — в связанной таблице.</p>
<h2 id="heading-2-6">Выводы</h2>
<p>В этом уроке мы узнали, что онтология помогает описывать предметную область программы и показывать связи ее сущностей. В реляционных базах данных используют три типа связи: один ко многим, один к одному и многие ко многим. А визуализируются они с помощью Entity-Relationship Diagram.</p></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/rdb-basics/lessons/ontology/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 24</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/rdb-basics/lessons/ontology/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>