<!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 22:47:08 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="schGATlmt0HGR9KNif2lOf9qe1PvCcj5R9HCXNAQhDBeGY02yxgaIXAE9hWF8lVOP2NW-ec-Nlv6MVgIghdjXg";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="Первая нормальная форма / Основы реляционных баз данных: Разбираемся с первой нормальной формой">
<link rel="canonical" href="https://ru.hexlet.io/courses/rdb-basics/lessons/1nf/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Первая нормальная форма">
<meta property="og:title" content="Основы реляционных баз данных">
<meta property="og:description" content="Первая нормальная форма / Основы реляционных баз данных: Разбираемся с первой нормальной формой">
<meta property="og:url" content="https://ru.hexlet.io/courses/rdb-basics/lessons/1nf/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="x4tqQ9xiMir-JPhABCbNFzaRywc1IZV0c4LMQOdBjNIoWqF0LhyfSkhn3NgIKT1g9pjmrT0Wa9bOYlYUtUZrvA" />
<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-26T22:47:08.059Z","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":"api9xr6WnOpddjqnq_sHzaNWJ_3MQBJ5H62_9oJ4dfSFSXbxTOgxius1Hj-n9Pe6Y18KV8R37NuiTSWi0H-Smg","topics":[{"id":49918,"title":"Здравствуйте. \nНаписал решение - оно не работает. https://ru.hexlet.io/code_reviews/345916?submission_id=438536\nВ итоге подсмотрел решение учителя. Подскажите, что не так в решение. ","plain_title":"Здравствуйте. Написал решение - оно не работает. https://ru.hexlet.io/codereviews/345916?submissionid=438536 В итоге подсмотрел решение учителя. Подскажите, что не так в решение. ","creator":{"public_name":"","id":168,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":106979,"body":"Добрый день.\n\nОшибка говорит об синтаксической ошибке.\nУ вас на строке `months smallint` нет запятой.","topic_id":49918}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":23616,"title":"Объясните, пожалуйста, разницу между первичным ключом и суррогатным?\nДля меня и то, и то, обычный айдишник в таблице, который автоинкрементируется при добавлении новой записи.","plain_title":"Объясните, пожалуйста, разницу между первичным ключом и суррогатным? Для меня и то, и то, обычный айдишник в таблице, который автоинкрементируется при добавлении новой записи. ","creator":{"public_name":"Denis Morozov","id":114345,"is_tutor":false},"comments":[{"creator":{"public_name":"Denis Morozov","id":114345,"is_tutor":false},"id":50341,"body":"Нагуглил, понял. До этого я думал, ставишь id автоинкремент и это решает все проблемы.\n\nhttps://stackoverflow.com/questions/36773011/what-is-the-difference-between-a-primary-key-and-a-surrogate-key","topic_id":23616},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":50333,"body":"Сравниваешь несравнимое. Первичный ключ может быть либо естественным либо суррогатным.","topic_id":23616}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":21885,"title":"А почему в примере с внешним ключом не используется конструкция FOREIGN KEY ? ","plain_title":"А почему в примере с внешним ключом не используется конструкция FOREIGN KEY ? ","creator":{"public_name":"Artem Gorbounov","id":153667,"is_tutor":false},"comments":[{"creator":{"public_name":"Artem Gorbounov","id":153667,"is_tutor":false},"id":46599,"body":"Не совсем понятно, а почему мы задаем тип у внешнего ключа, ведь эта как бы ссылка на другое поле, она ведь просто должна повторять тип поля на которое ссылается.","topic_id":21885},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":46641,"body":"Ну просто не надо такого допускать, этож аксиоматика. А так никаких спец правил именно для ключей, все работает по стандартной схеме, если поле одного типа а суют туда другой, то будет ошибка.","topic_id":21885},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":46552,"body":"Потому что в этом уроке (и в следующих) изучаются не конструкции sql, а концепции реляционной модели, конструкции будут дальше.","topic_id":21885},{"creator":{"public_name":"Artem Gorbounov","id":153667,"is_tutor":false},"id":46639,"body":"А как тогда разрешаются конфликты при приведении разных типов или длинн полей?","topic_id":21885},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":46617,"body":"Из урока:\n\n> Важно понимать что это не ссылка. В реляционной модели каждое отношение существует само по себе и во внешнем ключе указывается конкретное значение, которое должно совпадать с первичным ключем другого отношения. ","topic_id":21885}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":46874,"title":"Выполнил задание, не пропускает, я даже не выдержал и посмотрел решение, и там тоже самое, поэтому не понимаю почему не работает\nhttps://ru.hexlet.io/code_reviews/309047","plain_title":"Выполнил задание, не пропускает, я даже не выдержал и посмотрел решение, и там тоже самое, поэтому не понимаю почему не работает https://ru.hexlet.io/code_reviews/309047 ","creator":{"public_name":"Саша Ярошевич","id":295993,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":101006,"body":"Приветствую, Александр!\n\n`INSERT` не работает без ключевого слова `VALUES`. Проверьте, точно ли везде оно присутствует?","topic_id":46874},{"creator":{"public_name":"Саша Ярошевич","id":295993,"is_tutor":false},"id":101048,"body":"Спасибо, моя невнимательность, зря баллы потерял, за задание, сам все понял, но поторопился.","topic_id":46874}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":31759,"title":"Я как-то совершенно не понял этой строки\n > \"user_first_name - имя пользователя из таблицы users\" \n\nКак создается связь?","plain_title":"Я как-то совершенно не понял этой строки \"userfirstname - имя пользователя из таблицы users\" Как создается связь? ","creator":{"public_name":"Yura Kirillov","id":126461,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":110586,"body":"> Создается впечатление что нужно залинковать 2 столбика из разных таблиц. А этого мы еще не проходили на этом этапе.\n\n Если вы про внешние ключи, то они никак не влияют на связи, это всего лишь проверка корректности. А с точки зрения связей нет никаких реальных связей. Просто мы в одной таблице записываем id строк из другой.","topic_id":31759},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":69059,"body":"Эта связь существует только на логическом уровне. При вставке данных в таблицу заказов нужно указать правильное значение для поля _usre_first_name_, соответствующее имени того пользователя, который делает заказ.\n\np.s. Я добавил такой пример в задание, чтобы было понятнее. Его станет видно если сбросить прогресс.","topic_id":31759},{"creator":{"public_name":"Иван Дулепов","id":201153,"is_tutor":false},"id":110576,"body":"Как по мне так это наоборот запутывает. Единственная причина по которой мне пришлось зайти в коментарии это вот этот пункт. Создается впечатление что нужно залинковать 2 столбика из разных таблиц. А этого мы еще не проходили на этом этапе.","topic_id":31759}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":25990,"title":"> price numeric -- специальный тип данных, подходящий под работу с деньгами. Обеспечивает высокую точность при рассчетах.\n\nПоправьте на \"расчетах\".\n\nP.S. Курсы у вас качественные, спасибо!","plain_title":"price numeric -- специальный тип данных, подходящий под работу с деньгами. Обеспечивает высокую точность при рассчетах. Поправьте на \"расчетах\". P.S. Курсы у вас качественные, спасибо! ","creator":{"public_name":"Александр Виноградов","id":187297,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":55560,"body":"fixed. Спасибо!","topic_id":25990}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":94165,"title":"Доброго дня\nПотерял баллы на подсказке, т.к. между созданиями таблиц сделал 2 пункт, а судя по ответу нужно было указывать добавление информации в таблицы в самом конце :( ","plain_title":"Доброго дня Потерял баллы на подсказке, т.к. между созданиями таблиц сделал 2 пункт, а судя по ответу нужно было указывать добавление информации в таблицы в самом конце :( ","creator":{"public_name":"Алексей","id":676898,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":184473,"body":"Приветствую, Алексей! Это не принципиально. Добавлять данные в таблицу можно сразу после ее создания. Самое главное, чтобы на момент добавления данных таблица уже существовала. Возможно, дело было в чем то другом. Порекомендую вам анализировать вывод тестов, там содержится информация о том, почему решение не проходит","topic_id":94165}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":23414,"title":"Может ли естественным ключом (например, для таблицы пользователей) выступать ID который получают граждане США?","plain_title":"Может ли естественным ключом (например, для таблицы пользователей) выступать ID который получают граждане США? ","creator":{"public_name":"Виталий Есауленко","id":463,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":50000,"body":"Естественный ключ это всегда плохая идея, никогда не знаешь с какой стороны подведет.","topic_id":23414}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":31211,"title":"когда делал таблицу заказов, при вводе типа данных для `months` использовал `tinyint`, сомнительно, что для заказа обучения может потребоваться большее число. Но тест не прошел, сказав, что нет такого типа данных. заменил на `smallint` - стало все ок. вопрос: `tinyint` реально несуществующий тип данных или только в рамках данного задания? ","plain_title":"когда делал таблицу заказов, при вводе типа данных для months использовал tinyint, сомнительно, что для заказа обучения может потребоваться большее число. Но тест не прошел, сказав, что нет такого типа данных. заменил на smallint - стало все ок. вопрос: tinyint реально несуществующий тип данных или только в рамках данного задания? ","creator":{"public_name":"Сергей Егупов","id":184023,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей Егупов","id":184023,"is_tutor":false},"id":67772,"body":"Это не для первичного ключа, а для количества месяцев. \nА тип данных - криво прочитал когда гуглил","topic_id":31211},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":67773,"body":"В таком случае просто integer, во-первых он во всех базах есть, во-вторых нет смысла экономить.","topic_id":31211},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":67769,"body":"Хорошо бы видеть вывод тестов) Но вообще это достаточно легко проверить загуглив `postgresql datatypes tinyint`.\n\n> сомнительно, что для заказа обучения может потребоваться большее число\n\nВот такие предположения лучше не делать. Если данных будет мало, то размер первичного ключа не повлияет на размер базы данных, а если много, то переделка может обойтись ооочень дорого. Первые ключи надо всегда по дефолту делать bigint.","topic_id":31211}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}},{"id":91692,"title":"Вопрос теста:\n\n> Предположим, что в базе данных есть таблица пользователей и поле children (дети). В этом поле перечисляются имена детей данного человека. Соответствует ли такая реализация реляционной модели?\n\nКакого человека?)","plain_title":"Вопрос теста: Предположим, что в базе данных есть таблица пользователей и поле children (дети). В этом поле перечисляются имена детей данного человека. Соответствует ли такая реализация реляционной модели? Какого человека?) ","creator":{"public_name":"Александр Ларрива","id":477260,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":181314,"body":"Переформулировал немного вопрос в тесте","topic_id":91692},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":181205,"body":"Человека - пользователя","topic_id":91692},{"creator":{"public_name":"Александр Ларрива","id":477260,"is_tutor":false},"id":181299,"body":"**Maksim Litvinov**, Что значит - в базе данных есть таблица и поле? Поле может быть в таблице, но не в базе данных.","topic_id":91692}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Первая нормальная форма","entity_url":null,"active":true}}],"lesson":{"exercise":null,"units":[{"id":2638,"name":"theory","url":"/courses/rdb-basics/lessons/1nf/theory_unit"},{"id":2799,"name":"quiz","url":"/courses/rdb-basics/lessons/1nf/quiz_unit"}],"links":[{"id":422879,"name":"Первичный ключ","url":"https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B2%D0%B8%D1%87%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BB%D1%8E%D1%87"},{"id":422880,"name":"Внешний ключ","url":"https://ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9_%D0%BA%D0%BB%D1%8E%D1%87"},{"id":422881,"name":"Суррогатный ключ","url":"https://ru.wikipedia.org/wiki/%D0%A1%D1%83%D1%80%D1%80%D0%BE%D0%B3%D0%B0%D1%82%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BB%D1%8E%D1%87"}],"ordered_units":[{"id":2638,"name":"theory","url":"/courses/rdb-basics/lessons/1nf/theory_unit"},{"id":2799,"name":"quiz","url":"/courses/rdb-basics/lessons/1nf/quiz_unit"}],"id":1268,"slug":"1nf","state":"approved","name":"Первая нормальная форма","course_order":470,"goal":"Разбираемся с первой нормальной формой","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Чтобы облегчить считываемость информации в таблице, программисты приводят данные, которые представлены в реляционной модели, к нормальной форме. В этом уроке мы узнаем, что это за форма, а также разберем ее первый уровень.\n\n## Нормальная форма\n\nВозьмем для примера интернет-магазин, в котором продается электроника. Когда пользователь делает заказ, в базу данных заносится запись об этом. В нее входит вся необходимая информация: данные пользователя, какой товар он купил, сколько он стоил и адрес доставки. Затем эти данные используются всеми подразделениями интернет-магазина — от бухгалтеров до службы доставки.\n\n**Таблица order_items**\n\n| first_name | last_name | address | item | price |\n|------------|-----------|--------------------------|-----------------|------------------|\n| Сергей | Иванов | Москва, ул. Промышленная | утюг | 15.00 |\n| Иван | Петров | Самара, ул. Энгельса | кофеварка | 5000.00 |\n| Виктор | Сидоров | Омск, ул. Дворцовая | утюг, телевизор | 1000.00, 6500.00 |\n| Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n| Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n\nВ первой строке последнего столбца цена указана в долларах, в остальных записях — это рубли. Последняя запись повторяет предыдущую, потому что этот заказ выполнил тот же человек, но сделал это в другое время.\n\nВ этой табличке много повторяющейся информации. Приведем ее к правильной структуре с точки зрения реляционной модели. Для этого приведем данные к **нормальной форме** — это требования, которые минимизируют избыточность данных, потенциально приводящих к логическим ошибкам.\n\nВсего существует шесть нормальных форм, которые включают определенные требования. С каждым следующим уровнем требования все жестче, так как включают в себя предыдущие уровни.\n\nВ рамках курса мы разберем три нормальные формы. В этом уроке познакомимся с первой.\n\n## Первая нормальная форма\n\nПервая нормальная форма сводится к трем правилам:\n\n* Каждая ячейка таблицы может хранить только одно значение\n* Все данные в одной колонке могут быть только одного типа\n* Каждая запись в таблице должна однозначно отличаться от других записей\n\nРазберем каждое правило подробнее.\n\n### Каждая ячейка – одно значение\n\nВернемся к примеру выше. У одной записи поля _item_ и _price_ содержат два значения через запятую. У такого способа организации данных много недостатков. Например, пропадает возможность делать обычную выборку по условиям:\n\n```sql\n-- Как задать условие, чтобы получить все записи о проданных утюгах?\nSELECT * FROM order_items WHERE item = ...;\n```\n\nДругая проблема связана с типами данных. Поле _price_ в таблице _order_items_ имеет числовой тип (numeric). Если мы захотим хранить там более одного значения, то тип превратится в строковый, а все данные станут обычными строками.\n\nПри такой организации невозможно проверить корректность данных и формат числа. Становится проблематично выполнить агрегирующие запросы, например, считать выручку за определенный месяц одним запросом.\n\nЧтобы избавиться от перечислений в ячейках, можно создать новые записи:\n\n| first_name | last_name | address | item | price |\n|------------|-----------|--------------------------|-----------|----------|\n| Сергей | Иванов | Москва, ул. Промышленная | утюг | 15.00 |\n| Иван | Петров | Самара, ул. Энгельса | кофеварка | 5000.00 |\n| Виктор | Сидоров | Омск, ул. Дворцовая | утюг | 1000.00 |\n| Виктор | Сидоров | Омск, ул. Дворцовая | телевизор | 6500.00 |\n| Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n| Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n\nТеперь на одной строке находится информация только по одному товару. Так мы избавились от перечислений в поле, что позволит выполнять агрегирующие запросы, а также не будет путаницы с типами данных.\n\n### Данные одного типа\n\nСнова вернемся к таблице. Верхняя запись в ней содержит цену в долларах, хотя все остальные цены указаны в рублях. Технически база никак не укажет на это. И доллары, и рубли представлены числами, но с точки зрения программы у этих чисел разная природа.\n\nРазные данные в рамках одного поля тоже не дают выполнить агрегирующие запросы, например, поиск сумм, максимального, минимального. Еще усложняется обработка данных на уровне кода. В коде придется каким-то образом понимать, что из себя представляют данные.\n\nВот еще несколько примеров с похожей ситуацией:\n\n* Хранение даты свадьбы в поле «день рождения»\n* Хранение номера телефона вместо адреса в поле «адрес»\n\nИсправленная версия таблицы:\n\n| first_name | last_name | address | item | price |\n|------------|-----------|--------------------------|-----------|----------|\n| Сергей | Иванов | Москва, ул. Промышленная | утюг | 1000.00 |\n| Иван | Петров | Самара, ул. Энгельса | кофеварка | 5000.00 |\n| Виктор | Сидоров | Омск, ул. Дворцовая | утюг | 1000.00 |\n| Виктор | Сидоров | Омск, ул. Дворцовая | телевизор | 6500.00 |\n| Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n| Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n\nМы сконвертировали цену утюга в первой строке из долларов в рубли. Теперь у данных в поле *price* один тип. Так программе будет легче выполнять агрегирующие запросы.\n\n### Уникальные записи\n\nПоследние две записи в таблице выглядят идентично, хотя это два разных заказа. Их сделал один человек, но в разное время:\n\n| first_name | last_name | address | item | price |\n|------------|-----------|--------------------------|-----------|----------|\n| Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n| Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n\nРеляционная модель требует от нас уникальности каждой записи. Иначе нельзя понять, что к чему относится и с какой записью нужно работать при изменениях. Можно начать править не то и потерять важную информацию. При этом мы не можем полагаться на порядок данных внутри таблицы, так как он не гарантирован.\n\nРеализовать уникальность можно несколькими способами, например, добавить новое поле с датой заказа, которое сделает запись уникальной. Этот способ не очень надежный и не очень удобный в работе. Придется постоянно анализировать весь набор полей.\n\nЛучше добавить **первичный ключ** (_PRIMARY KEY_) — поле или набор полей, которые содержат уникальное значение для каждой записи. Первичный ключ не может меняться, его значение однозначно определяет любую запись в таблице.\n\nРазберем два вида первичного ключа:\n\n* **Естественный** — когда используются значения из окружающего мира, например, email, ФИО или паспортные данные. При этом нужно убедиться, что ключ не будет повторяться. Такие первичные ключи используют редко из-за их ненадежности. Часто они не уникальны и могут изменяться или повторяться. Например, номер паспорта меняется при смене документа\n* **Суррогатный** — когда используются автоматически генерируемые уникальные значения. Такой ключ поддерживается любой базой данных «из коробки». Иногда это просто числа, а иногда — сложные число-буквенные строки или хеши\n\nДобавим в нашу таблицу первичный ключ:\n\n| id | first_name | last_name | address | item | price |\n|----|------------|-----------|--------------------------|-----------|----------|\n| 8 | Сергей | Иванов | Москва, ул. Промышленная | утюг | 1000.00 |\n| 2 | Иван | Петров | Самара, ул. Энгельса | кофеварка | 5000.00 |\n| 7 | Виктор | Сидоров | Омск, ул. Дворцовая | утюг | 1000.00 |\n| 4 | Виктор | Сидоров | Омск, ул. Дворцовая | телевизор | 6500.00 |\n| 9 | Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n| 6 | Сергей | Иванов | Москва, ул. Матросова | ноутбук | 20000.00 |\n\nПервичный ключ принято создавать первым полем с названием _id_. Для первичного ключа обязательно указывать _PRIMARY KEY_ в описании таблицы:\n\n```sql\n-- Первичный ключ только один на таблицу\nCREATE TABLE products (\n id bigint PRIMARY KEY,\n first_name varchar(255),\n last_name varchar(255),\n address varchar(255),\n item varchar(255),\n price numeric -- специальный тип данных, который подходит под работу с деньгами. Обеспечивает высокую точность при расчетах.\n);\n```\n\nТакой ключ все еще нужно формировать самостоятельно, но теперь база данных сама следит за уникальностью. При попытке создать запись с повторяющимися первичными ключами возникнет ошибка.\n\n## Выводы\n\nВ этом уроке мы узнали, что такое нормальная форма в реляционной модели. Она помогает облегчить считываемость информации в таблице. Еще мы подробно разобрали первый уровень формы. Так мы узнали, что каждая ячейка таблицы может хранить только одно значение. Иначе может произойти путаница с типами данных, из-за чего будет невозможно проверить их корректность.\n\nТакже все данные в одной колонке могут быть только одного типа. Данные разного типа усложняют работу с таблицей, так как в коде придется каким-то образом понимать, что из себя представляют данные. А чтобы отличать записи друг от друга, нужно использовать суррогатный первичный ключ, который сделает каждую запись уникальной.\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/1nf/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>
<h2 id="heading-2-1">Нормальная форма</h2>
<p>Возьмем для примера интернет-магазин, в котором продается электроника. Когда пользователь делает заказ, в базу данных заносится запись об этом. В нее входит вся необходимая информация: данные пользователя, какой товар он купил, сколько он стоил и адрес доставки. Затем эти данные используются всеми подразделениями интернет-магазина — от бухгалтеров до службы доставки.</p>
<p><strong>Таблица order_items</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>first_name</th><th>last_name</th><th>address</th><th>item</th><th>price</th></tr></thead><tbody><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Промышленная</td><td>утюг</td><td>15.00</td></tr><tr><td>Иван</td><td>Петров</td><td>Самара, ул. Энгельса</td><td>кофеварка</td><td>5000.00</td></tr><tr><td>Виктор</td><td>Сидоров</td><td>Омск, ул. Дворцовая</td><td>утюг, телевизор</td><td>1000.00, 6500.00</td></tr><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr></tbody></table></div></div></div></div>
<p>В первой строке последнего столбца цена указана в долларах, в остальных записях — это рубли. Последняя запись повторяет предыдущую, потому что этот заказ выполнил тот же человек, но сделал это в другое время.</p>
<p>В этой табличке много повторяющейся информации. Приведем ее к правильной структуре с точки зрения реляционной модели. Для этого приведем данные к <strong>нормальной форме</strong> — это требования, которые минимизируют избыточность данных, потенциально приводящих к логическим ошибкам.</p>
<p>Всего существует шесть нормальных форм, которые включают определенные требования. С каждым следующим уровнем требования все жестче, так как включают в себя предыдущие уровни.</p>
<p>В рамках курса мы разберем три нормальные формы. В этом уроке познакомимся с первой.</p>
<h2 id="heading-2-2">Первая нормальная форма</h2>
<p>Первая нормальная форма сводится к трем правилам:</p>
<ul>
<li>Каждая ячейка таблицы может хранить только одно значение</li>
<li>Все данные в одной колонке могут быть только одного типа</li>
<li>Каждая запись в таблице должна однозначно отличаться от других записей</li>
</ul>
<p>Разберем каждое правило подробнее.</p>
<h3 id="heading-3-3">Каждая ячейка – одно значение</h3>
<p>Вернемся к примеру выше. У одной записи поля <em>item</em> и <em>price</em> содержат два значения через запятую. У такого способа организации данных много недостатков. Например, пропадает возможность делать обычную выборку по условиям:</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 order_items WHERE item = ...;</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>Другая проблема связана с типами данных. Поле <em>price</em> в таблице <em>order_items</em> имеет числовой тип (numeric). Если мы захотим хранить там более одного значения, то тип превратится в строковый, а все данные станут обычными строками.</p>
<p>При такой организации невозможно проверить корректность данных и формат числа. Становится проблематично выполнить агрегирующие запросы, например, считать выручку за определенный месяц одним запросом.</p>
<p>Чтобы избавиться от перечислений в ячейках, можно создать новые записи:</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>first_name</th><th>last_name</th><th>address</th><th>item</th><th>price</th></tr></thead><tbody><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Промышленная</td><td>утюг</td><td>15.00</td></tr><tr><td>Иван</td><td>Петров</td><td>Самара, ул. Энгельса</td><td>кофеварка</td><td>5000.00</td></tr><tr><td>Виктор</td><td>Сидоров</td><td>Омск, ул. Дворцовая</td><td>утюг</td><td>1000.00</td></tr><tr><td>Виктор</td><td>Сидоров</td><td>Омск, ул. Дворцовая</td><td>телевизор</td><td>6500.00</td></tr><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr></tbody></table></div></div></div></div>
<p>Теперь на одной строке находится информация только по одному товару. Так мы избавились от перечислений в поле, что позволит выполнять агрегирующие запросы, а также не будет путаницы с типами данных.</p>
<h3 id="heading-3-4">Данные одного типа</h3>
<p>Снова вернемся к таблице. Верхняя запись в ней содержит цену в долларах, хотя все остальные цены указаны в рублях. Технически база никак не укажет на это. И доллары, и рубли представлены числами, но с точки зрения программы у этих чисел разная природа.</p>
<p>Разные данные в рамках одного поля тоже не дают выполнить агрегирующие запросы, например, поиск сумм, максимального, минимального. Еще усложняется обработка данных на уровне кода. В коде придется каким-то образом понимать, что из себя представляют данные.</p>
<p>Вот еще несколько примеров с похожей ситуацией:</p>
<ul>
<li>Хранение даты свадьбы в поле «день рождения»</li>
<li>Хранение номера телефона вместо адреса в поле «адрес»</li>
</ul>
<p>Исправленная версия таблицы:</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>first_name</th><th>last_name</th><th>address</th><th>item</th><th>price</th></tr></thead><tbody><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Промышленная</td><td>утюг</td><td>1000.00</td></tr><tr><td>Иван</td><td>Петров</td><td>Самара, ул. Энгельса</td><td>кофеварка</td><td>5000.00</td></tr><tr><td>Виктор</td><td>Сидоров</td><td>Омск, ул. Дворцовая</td><td>утюг</td><td>1000.00</td></tr><tr><td>Виктор</td><td>Сидоров</td><td>Омск, ул. Дворцовая</td><td>телевизор</td><td>6500.00</td></tr><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr></tbody></table></div></div></div></div>
<p>Мы сконвертировали цену утюга в первой строке из долларов в рубли. Теперь у данных в поле <em>price</em> один тип. Так программе будет легче выполнять агрегирующие запросы.</p>
<h3 id="heading-3-5">Уникальные записи</h3>
<p>Последние две записи в таблице выглядят идентично, хотя это два разных заказа. Их сделал один человек, но в разное время:</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>first_name</th><th>last_name</th><th>address</th><th>item</th><th>price</th></tr></thead><tbody><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr><tr><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr></tbody></table></div></div></div></div>
<p>Реляционная модель требует от нас уникальности каждой записи. Иначе нельзя понять, что к чему относится и с какой записью нужно работать при изменениях. Можно начать править не то и потерять важную информацию. При этом мы не можем полагаться на порядок данных внутри таблицы, так как он не гарантирован.</p>
<p>Реализовать уникальность можно несколькими способами, например, добавить новое поле с датой заказа, которое сделает запись уникальной. Этот способ не очень надежный и не очень удобный в работе. Придется постоянно анализировать весь набор полей.</p>
<p>Лучше добавить <strong>первичный ключ</strong> (<em>PRIMARY KEY</em>) — поле или набор полей, которые содержат уникальное значение для каждой записи. Первичный ключ не может меняться, его значение однозначно определяет любую запись в таблице.</p>
<p>Разберем два вида первичного ключа:</p>
<ul>
<li><strong>Естественный</strong> — когда используются значения из окружающего мира, например, email, ФИО или паспортные данные. При этом нужно убедиться, что ключ не будет повторяться. Такие первичные ключи используют редко из-за их ненадежности. Часто они не уникальны и могут изменяться или повторяться. Например, номер паспорта меняется при смене документа</li>
<li><strong>Суррогатный</strong> — когда используются автоматически генерируемые уникальные значения. Такой ключ поддерживается любой базой данных «из коробки». Иногда это просто числа, а иногда — сложные число-буквенные строки или хеши</li>
</ul>
<p>Добавим в нашу таблицу первичный ключ:</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>address</th><th>item</th><th>price</th></tr></thead><tbody><tr><td>8</td><td>Сергей</td><td>Иванов</td><td>Москва, ул. Промышленная</td><td>утюг</td><td>1000.00</td></tr><tr><td>2</td><td>Иван</td><td>Петров</td><td>Самара, ул. Энгельса</td><td>кофеварка</td><td>5000.00</td></tr><tr><td>7</td><td>Виктор</td><td>Сидоров</td><td>Омск, ул. Дворцовая</td><td>утюг</td><td>1000.00</td></tr><tr><td>4</td><td>Виктор</td><td>Сидоров</td><td>Омск, ул. Дворцовая</td><td>телевизор</td><td>6500.00</td></tr><tr><td>9</td><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr><tr><td>6</td><td>Сергей</td><td>Иванов</td><td>Москва, ул. Матросова</td><td>ноутбук</td><td>20000.00</td></tr></tbody></table></div></div></div></div>
<p>Первичный ключ принято создавать первым полем с названием <em>id</em>. Для первичного ключа обязательно указывать <em>PRIMARY KEY</em> в описании таблицы:</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">-- Первичный ключ только один на таблицу
CREATE TABLE products (
id bigint PRIMARY KEY,
first_name varchar(255),
last_name varchar(255),
address varchar(255),
item varchar(255),
price numeric -- специальный тип данных, который подходит под работу с деньгами. Обеспечивает высокую точность при расчетах.
);</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>
<h2 id="heading-2-6">Выводы</h2>
<p>В этом уроке мы узнали, что такое нормальная форма в реляционной модели. Она помогает облегчить считываемость информации в таблице. Еще мы подробно разобрали первый уровень формы. Так мы узнали, что каждая ячейка таблицы может хранить только одно значение. Иначе может произойти путаница с типами данных, из-за чего будет невозможно проверить их корректность.</p>
<p>Также все данные в одной колонке могут быть только одного типа. Данные разного типа усложняют работу с таблицей, так как в коде придется каким-то образом понимать, что из себя представляют данные. А чтобы отличать записи друг от друга, нужно использовать суррогатный первичный ключ, который сделает каждую запись уникальной.</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/1nf/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/1nf/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>