Таблицы — необходимый компонент баз данных. Чтобы построить аналитику, мы часто агрегируем данные из таблиц и строим графики. Но иногда анализ самой таблицы без графиков оказывается удобнее.
В этом уроке мы рассмотрим, что такое таблица, какие существуют отношения между таблицами, и как проектировать таблицы. Мы агрегируем данные продаж по товарам и найдем самые прибыльные из них.
С более глубокими знаниями о таблицах можно проводить хорошую и разностороннюю аналитику и выбирать нужный метод анализа под каждую задачу.
Таблица
Для начала рассмотрим, что такое таблица в базе данных. Посмотрим на пример ниже:
Таблица содержит информация о разных пиццериях. В ней есть следующие столбцы:
- Название — это название пиццерии
- Доставка — есть у пиццерии доставка или нет
- Начало работы — с какого времени открыта пиццерия
Вся информация в таблице — это параметры разных пиццерий, то есть смысл этой таблицы. Смысл называется сущностью — это то, о чем хранится информация в таблице.
Для таблиц как сущностей есть такое правило: одна таблица — одна сущность. Например, в таблице Users хранится только информация о пользователях, в таблице Payments — о платежах, а в таблице выше — о пиццериях.
Таблицы в базах данных могут быть связаны друг с другом. Такие базы называются реляционными.
В реляционных базах содержатся предопределенные связи между таблицами. В каждой таблице хранится информация об отдельной сущности, но эти сущности могут быть связаны.
Например, таблица Users хранит информацию о пользователе — его ФИО, адрес электронной почты и другие его параметры. Но каждый пользователь совершает какой-то платеж, и эти платежи хранятся в таблице Payments.
Таблицы Users и Payments связаны. Такие связи называются отношениями между сущностями. Язык SQL — это основной инструмент работы с реляционными базами данных.
У таблиц в реляционных базах данных есть разные компоненты. Их использование является одним из необходимых условий реляционной модели, потому что они позволяют связывать разные сущности. Рассмотрим их подробнее.
Компоненты таблицы
Обязательные компоненты таблиц такие:
- Столбец
- Строка
- Домен
- Ключ
Столбец в таблицах еще называют колонкой*, полем или атрибутом. Атрибут таблицы выражает какой-то один тип информации о сущности. Например, в таблице Users может храниться поле Full name, и оно содержит информацию о ФИО пользователя. Поле User address будет хранить адрес пользователя. В колонке Название в таблице пиццерий мы видим название конкретной пиццерии.
Строки мы еще зовем записью или кортежем. Одна строка — это одна единица сущности. В таблице Users одна запись — это один пользователь, в Payments — это один платеж, в Пиццерии — одна пиццерия.
При создании таблицы в базе данных мы обязательно указываем тип данных информации в поле. Домен поля — это и есть тип данных столбца. К примеру, поле Full name в таблице Users может содержать строку и ничего кроме строки.
Вот примеры доменов полей:
- Строка
- Целое число
- Число с плавающей точкой и прочее
Мы уже упомянули, что таблицы в реляционных базах данных связаны. Чтобы связать две таблицы, мы используем специальное поле, которое называется ключ. В таблице Users и Payments такой ключ — это колонка UserID. Мы связываем эти две таблицы с помощью оператора join.
Ключ, который является уникальным для всей таблицы, — это первичный ключ. В таблице Users каждый пользователь имеет свой уникальный UserID, и два пользователя не могут иметь один UserID. Но в таблице Payments один пользователь может совершить несколько платежей, поэтому бывают строки с одинаковыми UserID.
В то же время первичный ключ PaymentID для этой таблицы бывает только уникальным, поскольку одна строка — это один платеж, и они не могут повторяться.
Таблицы в реляционных базах данных связаны друг с другом, и сейчас мы рассмотрим разные типы отношений между таблицами.
Отношения таблиц
Мы уже сказали, что мы можем связывать разные таблицы в базах данных. Есть такие типы связей или отношений:
- Один к одному
- Один ко многим
- Многие ко многим
Рассмотрим каждый из этих типов.
Один к одному
Представим, что у нас есть база данных пользователей интернет-магазина. Посмотрим на таблицу Users, в которой есть информация о пользователях:
users
В этой таблице мы видим три поля:
- UserID — ID покупателя
- Full name — имя и фамилия покупателя
- Email — адрес электронной почты
Теперь топ-менеджер магазина сообщил нам, что мы должны внести новое поле: есть ли у покупателя скидочная карта или нет. В нашей таблице уже хранится 10 000 строк, поэтому вписывать в каждую наличие скидочной карты будет долго. При этом один покупатель мог быть вписан в таблицу несколько раз, если он менял адрес электронной почты.
Чтобы решить эту задачу просто, мы создадим новую таблицу Users discount card, в которой будет всего два поля:
- ID покупателя
- Наличие у покупателя скидочной карты
При этом, чтобы не вписывать наличие карты у одного и того же покупателя несколько раз, мы сделаем поле UserID уникальным.
Посмотрим на такую таблицу:
users_discount_card
Таблица содержит только UserID и Discount card — есть у пользователя скидочная карта или нет. Теперь эту таблицу легко связать с таблицей Users по UserID с помощью SQL-функции join и получить для каждого пользователя наличие у него скидочной карты. При этом нам не нужно вносить в Users новое поле.
Такие отношения между таблицами называются один к одному. В таком типе отношений только одному пользователю соответствует только одна метка наличия карты.
Один ко многим
Теперь предположим, что мы хотим хранить в таблице Users номера телефонов покупателей. У одного покупателя может быть несколько номеров телефонов, но один номер телефона принадлежит только одному покупателю. Эта связь называется один ко многим.
Для этого мы создадим новую таблицу Phones:
users
В этой таблице есть три поля:
- PhoneID — ID номера телефона
- UserID — ID покупателя, которому принадлежит телефон
- Phone — номер телефона
При этом PhoneID должен быть в таблице уникален, но, как мы видим, UserID повторяется. Это значит, что у одного покупателя есть несколько номеров.
Многие ко многим
Чтобы понять отношение многие ко многим, рассмотрим еще одну таблицу Products:
products
В таблице содержится список товаров магазина — ID товара и наименование. Мы хотим связать, какие товары какой покупатель приобрел. При этом один покупатель может купить много разных товаров, но и один и тот же товар могут купить разные люди. Таблица-отношение между таблицами Users и Products будет выглядеть так:
users_products
В этой таблице мы видим, что пользователь с ID = 1 купил товары с ID 10 и 5, но товар 10 купили два пользователя: с ID = 1 и ID = 6. Такие отношения и есть «многие ко многим».
Кроме того, что существуют разные виды отношений между таблицами, само проектирование баз данных и таблиц возможно несколькими способами.
Проектирование таблиц
Есть такие виды проектирования баз данных:
- Концептуальное моделирование
- Логическое моделирование
- Физическое моделирование
Концептуальное проектирование заключается в том, что мы описываем, какие у нас будут сущности в базе данных и отношения. Сущности, как мы говорили выше — это отдельные таблицы. Отношения — какие сущности и как будут связаны друг с другом: «один к одному», «один ко многим» или «многие ко многим». То, о чем мы говорили выше, — это концептуальное проектирование.
Логическое проектирование дополняет концептуальное тем, что мы уже рассматриваем особенности модели данных, которые будут храниться в базе данных. Модели данных бывают реляционными, нереляционными и другими.
Физическое моделирование уже учитывает проектирование на уровне особенностей каждой отдельной базы данных: PostgreSQL, MySQL. При физическом моделировании мы выбираем, какие запросы нам важны больше всего: чтение из таблицы или запись в таблицу, и производим тестирование на пропускную способность.
Как правило, разные типы моделирования используются при проектировании одной и той же базы данных:
- Концептуально описываем схему базы данных и сущности, которые там содержатся
- Выбираем модель данных в логическом моделировании
- Рассматриваем более детально реализацию базы данных в конкретном хранилище с учетом конкретной базы, которую мы выбрали
Таким образом, проектирование одной базы данных обычно проходит через все три этапа. Каждый следующий этап дополняет и уточняет предыдущий.
Мы знаем, что называется таблицей в базе данных, какие отношения между таблицами существуют и какие есть типы проектирования баз данных. Теперь перейдем к практике и агрегируем таблицу продаж.
Агрегация таблицы продаж
Рассмотрим базу данных table. В ней есть одна таблица sales, которая содержит информацию о покупках в магазине. Посмотрим на эту таблицу:
sales
В этой таблице мы видим информацию о покупателях и купленных товарах.
Мы хотим определить из таблицы топ-5 самых прибыльных товаров. Наименование товара указано в поле product_name, а прибыль — в sales. Напишем SQL-запрос:
Ссылка на таблицу
В этом запросе мы произвели агрегацию по товарам и посчитали суммарную прибыль по всем товарам. Мы использовали ORDER BY SUM(sales) DESC, чтобы отсортировать товары по убыванию прибыли. LIMIT 5 позволяет отобрать только пять верхних строк таблицы.
Так мы нашли топ-5 самых прибыльных товаров. При этом самый удобный и наглядный способ решить нашу задачу — это просто таблица, без построения диаграмм.
Выводы
В этом уроке мы рассмотрели, что такое таблица, какие у нее есть компоненты, и какие существуют отношения между таблицами. Мы поговорили о том, как можно проектировать базы данных. Еще мы агрегировали таблицу продаж и нашли топ-5 самых прибыльных товаров в магазине.
Использование простой таблицы для этой задачи стало лучшим и более наглядным решением, чем построение диаграмм. С помощью знаний о таблицах вы станете лучше понимать, как работают базы данных, и сможете применить эти знания на практике.
<!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 17:10:48 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="qOR3gvQ5E7_QD55nk5eFtDSXl56gqSCBmQFVR73CsiRHNby1Bke-32ZMuv-fmHXD9J66NKie3iMk4c8T78VVSg";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>Таблица как визуализация | Продвинутая аналитика на SQL</title>
<meta name="description" content="Таблица как визуализация / Продвинутая аналитика на SQL: Подробнее изучаем таблицы">
<link rel="canonical" href="https://ru.hexlet.io/courses/analytics-with-sql/lessons/table/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Таблица как визуализация">
<meta property="og:title" content="Продвинутая аналитика на SQL">
<meta property="og:description" content="Таблица как визуализация / Продвинутая аналитика на SQL: Подробнее изучаем таблицы">
<meta property="og:url" content="https://ru.hexlet.io/courses/analytics-with-sql/lessons/table/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="ZxA1ie-IcUyGuwbyARI4-9s79Fnuxody4YXqyjxLpjWIwf6-HfbcLDD4ImoNHciMGzLZ8-bxedBcZXCebkxBWw" />
<script src="/vite/assets/inertia-INZxX8jp.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-nkZBEvfU.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-6pOtQ3OW.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">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzY1MywicHVyIjoiYmxvYl9pZCJ9fQ==--5107185de77b3481e0a836f9fc7326c4e1b77be4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Data%20extraction-pana.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk1MywicHVyIjoiYmxvYl9pZCJ9fQ==--963098414ddb264ba6c4deab9bd951f2d6778e4a/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Data%20analysis-amico.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDg3MSwicHVyIjoiYmxvYl9pZCJ9fQ==--8175585f43b5401994e29b3ae73d76963d942512/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Browser%20stats-bro.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDg2MiwicHVyIjoiYmxvYl9pZCJ9fQ==--04a703ca18d7bf689064f1f3c2721058bd5564e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Statistics-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><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-26T17:10:48.770Z","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":"YKzxAeKXwnApj_InX_1icR6K8YeOjZ7D3lyLpSzFLjiPfTo2EOlvEJ_M1r9T8pIG3oPcLYa6YGFjvBHxfsLJVg","topics":[{"id":88754,"title":"Добавьте, пожалуйста, текстовое описание в урок.","plain_title":"Добавьте, пожалуйста, текстовое описание в урок. ","creator":{"public_name":"Aleksandr Belan","id":364706,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":177056,"body":"Уже ответил вам в другом топике","topic_id":88754}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Таблица как визуализация","entity_url":null,"active":true}},{"id":88834,"title":"Есть подозрение, что тесты настроены не корректно.\nВ задании нас просят вывести три товара с самыми высокими продажами, предлагается вывести поле sales_sum, т.е. сумму продаж. Это количество (amount) умножить на цену (cost). Но в тестах и в решении учителя суммируются только цены, что не имеет смысла.\nКроме того, кажется, что решение учителя переусложнено, см. сохраненный вариант.","plain_title":"Есть подозрение, что тесты настроены не корректно. В задании нас просят вывести три товара с самыми высокими продажами, предлагается вывести поле sales_sum, т.е. сумму продаж. Это количество (amount) умножить на цену (cost). Но в тестах и в решении учителя суммируются только цены, что не имеет смысла. Кроме того, кажется, что решение учителя переусложнено, см. сохраненный вариант. ","creator":{"public_name":"Aleksandr Belan","id":364706,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":177518,"body":"Добрый день, поправил редми, в таблице предполагается что `cost` это стоимость продаж за день.","topic_id":88834}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Таблица как визуализация","entity_url":null,"active":true}},{"id":92394,"title":"Доброго времени суток! Ошибка в задании, в ответе учителя стоимость товара, а не сумма продажи https://ru.hexlet.io/code_reviews/1142135","plain_title":"Доброго времени суток! Ошибка в задании, в ответе учителя стоимость товара, а не сумма продажи https://ru.hexlet.io/code_reviews/1142135 ","creator":{"public_name":"Никита Вандышев","id":633239,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":182101,"body":"**Никита Вандышев**, здравствуйте! Спасибо! Поправил!","topic_id":92394}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Таблица как визуализация","entity_url":null,"active":true}},{"id":99812,"title":"В задании сказано:\n\n> Данные должны быть сгруппированы по полю product_name и отсортированы от большего к меньшему по полю sum.\n\nНужно написать не \"по полю sum\", а \"по полю sales_sum\". Поля `sum` в запросе нет.\n","plain_title":"В задании сказано: Данные должны быть сгруппированы по полю product_name и отсортированы от большего к меньшему по полю sum. Нужно написать не \"по полю sum\", а \"по полю sales_sum\". Поля sum в запросе нет. ","creator":{"public_name":"Игорь Пустошило","id":744978,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":191367,"body":"Поправил, спасибо!","topic_id":99812}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Таблица как визуализация","entity_url":null,"active":true}},{"id":99601,"title":"Мне думается, что моё решение данного задания верно:\nSELECT \n product_name,\n SUM(amount) AS sales_sum\nFROM \n sales\nWHERE \n EXTRACT(DAY FROM sale_date) BETWEEN 1 AND 3\nGROUP BY \n product_name\nORDER BY \n sales_sum DESC\nLIMIT 3;\n\nОднако, выдаётся ошибка.\nПрошу проверить моё решение как верно отвечающее на задание.","plain_title":"Мне думается, что моё решение данного задания верно: SELECT productname, SUM(amount) AS salessum FROM sales WHERE EXTRACT(DAY FROM saledate) BETWEEN 1 AND 3 GROUP BY productname ORDER BY sales_sum DESC LIMIT 3; Однако, выдаётся ошибка. Прошу проверить моё решение как верно отвечающее на задание. ","creator":{"public_name":"Ilia Semukhin","id":683765,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":191112,"body":"Количество это `amount`, цена это `cost` ","topic_id":99601},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":191095,"body":"Вам нужно расчитать сумму продаж каждого товара, т.е количество умноженное на цену","topic_id":99601},{"creator":{"public_name":"Ilia Semukhin","id":683765,"is_tutor":false},"id":191110,"body":"**Ivan Mamtsev**, а под \"количеством\" подразумевается колонка \"cost\"?","topic_id":99601}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Таблица как визуализация","entity_url":null,"active":true}},{"id":100981,"title":"Добрый день! \nМожете пояснить, почему если добавить в запрос \"WHERE sale_date BETWEEN '2023-02-01' and '2023-02-03'\"\nв вывод не попадают продажи за 2023-02-03 (проверял в repl'е)? BETWEEN же в поиск включает пограничные значения...\n","plain_title":"Добрый день! Можете пояснить, почему если добавить в запрос \"WHERE sale_date BETWEEN '2023-02-01' and '2023-02-03'\" в вывод не попадают продажи за 2023-02-03 (проверял в repl'е)? BETWEEN же в поиск включает пограничные значения... ","creator":{"public_name":"Максим Латухин","id":822058,"is_tutor":false},"comments":[{"creator":{"public_name":"Eugene","id":407464,"is_tutor":false},"id":192817,"body":"Все верно, `between` включает пограничные значения. Нюанс в другом. Обрати внимание, что тип поля `sale_date` - `timestamp without time zone `, т.е. дата + время. При этом в диапазоне ты указываешь только дату, оставшуюся часть (т.е. время) sql интерпретирует по своему, а именно:\n\n`between '2023-02-01 00:00:00' and '2023-02-03 00:00:00'`\n\nПопробуй явно указать время - до `2023-02-03 23:59:59`, или так:\n\n` where cast(sale_date as date) between '2023-02-01' and '2023-02-03'`","topic_id":100981},{"creator":{"public_name":"Максим Латухин","id":822058,"is_tutor":false},"id":192834,"body":"Спасибо!","topic_id":100981},{"creator":{"public_name":"Игорь Пустошило","id":744978,"is_tutor":false},"id":192818,"body":"Потому что в таблице продажи со временем. Когда вы пишете BETWEEN '2023-02-01' and '2023-02-03' - фактически это означает BETWEEN '2023-02-01 00:00:00' and '2023-02-03 00:00:00'. Очевидно, что продажа в граничную дату 2023-02-03 со временем (к примеру) 01:00:00 в этом случае в выборку не попадет.\n\nТак что нужно писать BETWEEN '2023-02-01' and '2023-02-04', но даже это неправильно, потому что нет гарантии, что не было покупки ровно в полночь 2023-02-04. Так что если мы хотим быть точными, от BETWEEN нужно отказаться, хотя с ним решение красивее, спору нет.\n\nНу или писать '2023-02-03 23:59:59'. Я так не писал, потому что точно формат дата-время не помню, а гуглить было лень :)","topic_id":100981}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Таблица как визуализация","entity_url":null,"active":true}},{"id":103961,"title":"Добрый день, а в чем ошибка в решении?\nhttps://ru.hexlet.io/code_reviews/1611325\n\nПишет, что ошибка - ERROR: syntax error at or near \"FROM\" у EXTRACT(DAY FROM ...)","plain_title":"Добрый день, а в чем ошибка в решении? https://ru.hexlet.io/code_reviews/1611325 Пишет, что ошибка - ERROR: syntax error at or near \"FROM\" у EXTRACT(DAY FROM ...) ","creator":{"public_name":"Никита Паренюк","id":251578,"is_tutor":false},"comments":[{"creator":{"public_name":"Игорь Пустошило","id":744978,"is_tutor":false},"id":196425,"body":"У вас слово `EXTRACT` неправильно написано - `EXCTRACT`. И данные вообще-то нужны за 3 дня, а не за 10.","topic_id":103961},{"creator":{"public_name":"Никита Паренюк","id":251578,"is_tutor":false},"id":196426,"body":"Прошу тогда команду Хекслета поменять файл Readme, блок подсказки, где находится название функции - EXCTRACT().","topic_id":103961},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":196456,"body":"Приветствую, Никита! Ага, там опечатка закралась. Поправил, спасибо","topic_id":103961}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Таблица как визуализация","entity_url":null,"active":true}},{"id":107592,"title":"Ошибка в задании упражнения.\nВ тексте задания указано, что исходная таблица имеет поле \n\n> cost - общая стоимость\n\nКак правило, общая стоимость представляет собой произведение цены и количества товара. \nПри этом текст задания подразумевает, что общая стоимость у нас уже есть и в запросе нет необходимости делать умножение цены товара на его количество.\nОднако без описанного выше перемножения проверка завершается ошибкой.","plain_title":"Ошибка в задании упражнения. В тексте задания указано, что исходная таблица имеет поле cost - общая стоимость Как правило, общая стоимость представляет собой произведение цены и количества товара. При этом текст задания подразумевает, что общая стоимость у нас уже есть и в запросе нет необходимости делать умножение цены товара на его количество. Однако без описанного выше перемножения проверка завершается ошибкой. ","creator":{"public_name":"Александр Александров","id":458449,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":201622,"body":"Да, поправлю, это не стоимость, а цена.","topic_id":107592}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Таблица как визуализация","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":2431,"slug":"analytics_with_sql_table_exercise","name":null,"state":"active","kind":"exercise","language":"sql","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"В таблице `sales` хранится информация о продажах за первые 10 дней февраля:\n\n**sales**\n\n| id | shop_name | product_name | amount | price | sale_date |\n|----|-----------|--------------|--------|------|---------------------|\n| 1 | Shop 1 | Product 1 | 10 | 10 | 2023-02-01 11:20:00 |\n| 2 | Shop 2 | Product 2 | 20 | 2 | 2023-02-02 11:25:00 |\n| 3 | Shop 3 | Product 3 | 15 | 15 | 2023-02-03 11:30:00 |\n\n* shop_name - имя магазина\n* product_name - имя товара\n* amount - количество\n* price - цена товара\n* sale_date - дата продажи\n\n## solution.sql\n\nВам нужно вывести три товара с самыми высокими продажами за первые три дня февраля.\n\nСоставьте запрос, который выводит:\n\n* Поле `product_name`\n* Поле `sales_sum`, в котором должна быть сумма продаж для каждого товара\n\nДанные должны быть сгруппированы по полю `product_name` и отсортированы от большего к меньшему по полю `sales_sum`. Вывести нужно три товара с наибольшими продажами.\n\n### Подсказки\n\n* Перед тем, как писать запросы в файл, можно зайти в репл `psql` в терминале и поэкспериментировать\n* [SUM()](https://ru.hexlet.io/courses/rdb-basics/lessons/functions/theory_unit#sum)\n* [GROUP BY](https://ru.hexlet.io/courses/rdb-basics/lessons/group/theory_unit)\n* [ORDER BY](https://ru.hexlet.io/courses/rdb-basics/lessons/order/theory_unit)\n* Вам может пригодиться функция `EXTRACT()` при работе с датами — [Date/Time Functions and Operators](https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT)\n","prepared_readme":"В таблице `sales` хранится информация о продажах за первые 10 дней февраля:\n\n**sales**\n\n| id | shop_name | product_name | amount | price | sale_date |\n|----|-----------|--------------|--------|------|---------------------|\n| 1 | Shop 1 | Product 1 | 10 | 10 | 2023-02-01 11:20:00 |\n| 2 | Shop 2 | Product 2 | 20 | 2 | 2023-02-02 11:25:00 |\n| 3 | Shop 3 | Product 3 | 15 | 15 | 2023-02-03 11:30:00 |\n\n* shop_name - имя магазина\n* product_name - имя товара\n* amount - количество\n* price - цена товара\n* sale_date - дата продажи\n\n## solution.sql\n\nВам нужно вывести три товара с самыми высокими продажами за первые три дня февраля.\n\nСоставьте запрос, который выводит:\n\n* Поле `product_name`\n* Поле `sales_sum`, в котором должна быть сумма продаж для каждого товара\n\nДанные должны быть сгруппированы по полю `product_name` и отсортированы от большего к меньшему по полю `sales_sum`. Вывести нужно три товара с наибольшими продажами.\n\n### Подсказки\n\n* Перед тем, как писать запросы в файл, можно зайти в репл `psql` в терминале и поэкспериментировать\n* [SUM()](https://ru.hexlet.io/courses/rdb-basics/lessons/functions/theory_unit#sum)\n* [GROUP BY](https://ru.hexlet.io/courses/rdb-basics/lessons/group/theory_unit)\n* [ORDER BY](https://ru.hexlet.io/courses/rdb-basics/lessons/order/theory_unit)\n* Вам может пригодиться функция `EXTRACT()` при работе с датами — [Date/Time Functions and Operators](https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT)\n","has_solution":true,"entity_name":"Таблица как визуализация"},"units":[{"id":8354,"name":"theory","url":"/courses/analytics-with-sql/lessons/table/theory_unit"},{"id":9310,"name":"quiz","url":"/courses/analytics-with-sql/lessons/table/quiz_unit"},{"id":8377,"name":"exercise","url":"/courses/analytics-with-sql/lessons/table/exercise_unit"}],"links":[],"ordered_units":[{"id":8354,"name":"theory","url":"/courses/analytics-with-sql/lessons/table/theory_unit"},{"id":9310,"name":"quiz","url":"/courses/analytics-with-sql/lessons/table/quiz_unit"},{"id":8377,"name":"exercise","url":"/courses/analytics-with-sql/lessons/table/exercise_unit"}],"id":3709,"slug":"table","state":"approved","name":"Таблица как визуализация","course_order":180,"goal":"Подробнее изучаем таблицы","self_study":"Для самостоятельной работы возьмите базу данных из урока. Найдите топ-10 покупателей, которые потратили больше всего денег в этом магазине.\n","theory_video_provider":"vimeo","theory_video_uid":"830174749","theory":"Таблицы — необходимый компонент баз данных. Чтобы построить аналитику, мы часто агрегируем данные из таблиц и строим графики. Но иногда анализ самой таблицы без графиков оказывается удобнее.\n\nВ этом уроке мы рассмотрим, что такое таблица, какие существуют отношения между таблицами, и как проектировать таблицы. Мы агрегируем данные продаж по товарам и найдем самые прибыльные из них.\n\nС более глубокими знаниями о таблицах можно проводить хорошую и разностороннюю аналитику и выбирать нужный метод анализа под каждую задачу.\n\n## Таблица\n\nДля начала рассмотрим, что такое таблица в базе данных. Посмотрим на пример ниже:\n\n\n\nТаблица содержит информация о разных пиццериях. В ней есть следующие столбцы:\n\n* Название — это название пиццерии\n* Доставка — есть у пиццерии доставка или нет\n* Начало работы — с какого времени открыта пиццерия\n\nВся информация в таблице — это параметры разных пиццерий, то есть смысл этой таблицы. Смысл называется **сущностью** — это то, о чем хранится информация в таблице.\n\nДля таблиц как сущностей есть такое правило: одна таблица — одна сущность. Например, в таблице `Users` хранится только информация о пользователях, в таблице `Payments` — о платежах, а в таблице выше — о пиццериях.\n\nТаблицы в базах данных могут быть связаны друг с другом. Такие базы называются **реляционными**.\n\nВ реляционных базах содержатся предопределенные связи между таблицами. В каждой таблице хранится информация об отдельной сущности, но эти сущности могут быть связаны.\n\nНапример, таблица `Users` хранит информацию о пользователе — его ФИО, адрес электронной почты и другие его параметры. Но каждый пользователь совершает какой-то платеж, и эти платежи хранятся в таблице `Payments`.\n\nТаблицы `Users` и `Payments` связаны. Такие связи называются **отношениями** между сущностями. Язык `SQL` — это основной инструмент работы с реляционными базами данных.\n\nУ таблиц в реляционных базах данных есть разные компоненты. Их использование является одним из необходимых условий реляционной модели, потому что они позволяют связывать разные сущности. Рассмотрим их подробнее.\n\n### Компоненты таблицы\n\nОбязательные компоненты таблиц такие:\n\n* Столбец\n* Строка\n* Домен\n* Ключ\n\nСтолбец в таблицах еще называют **колонкой***, **полем** или **атрибутом**. Атрибут таблицы выражает какой-то один тип информации о сущности. Например, в таблице `Users` может храниться поле `Full name`, и оно содержит информацию о ФИО пользователя. Поле `User address` будет хранить адрес пользователя. В колонке `Название` в таблице пиццерий мы видим название конкретной пиццерии.\n\nСтроки мы еще зовем **записью** или **кортежем**. Одна строка — это одна единица сущности. В таблице `Users` одна запись — это один пользователь, в `Payments` — это один платеж, в `Пиццерии` — одна пиццерия.\n\nПри создании таблицы в базе данных мы обязательно указываем **тип данных** информации в поле. **Домен** поля — это и есть тип данных столбца. К примеру, поле `Full name` в таблице `Users` может содержать строку и ничего кроме строки.\n\nВот примеры доменов полей:\n\n* Строка\n* Целое число\n* Число с плавающей точкой и прочее\n\nМы уже упомянули, что таблицы в реляционных базах данных связаны. Чтобы связать две таблицы, мы используем специальное поле, которое называется **ключ**. В таблице `Users` и `Payments` такой ключ — это колонка `UserID`. Мы связываем эти две таблицы с помощью оператора `join`.\n\nКлюч, который является уникальным для всей таблицы, — это **первичный ключ**. В таблице `Users` каждый пользователь имеет свой уникальный `UserID`, и два пользователя не могут иметь один `UserID`. Но в таблице `Payments` один пользователь может совершить несколько платежей, поэтому бывают строки с одинаковыми `UserID`.\n\nВ то же время первичный ключ `PaymentID` для этой таблицы бывает только уникальным, поскольку одна строка — это один платеж, и они не могут повторяться.\n\nТаблицы в реляционных базах данных связаны друг с другом, и сейчас мы рассмотрим разные типы отношений между таблицами.\n\n## Отношения таблиц\n\nМы уже сказали, что мы можем связывать разные таблицы в базах данных. Есть такие типы **связей** или **отношений**:\n\n* Один к одному\n* Один ко многим\n* Многие ко многим\n\nРассмотрим каждый из этих типов.\n\n### Один к одному\n\nПредставим, что у нас есть база данных пользователей интернет-магазина. Посмотрим на таблицу `Users`, в которой есть информация о пользователях:\n\n**users**\n\n| UserID | Full name | Email |\n| --- | --- | --- |\n| 1 | Александ Попов | <popov2763294783@mail.ru> |\n| 2 | Людмила Астафьева | <ludmila_ast_783242902@yandex.ru> |\n| 3 | Екатерина Антонова | <ekaterina_antonova_99113984302@gmail.com> |\n| ... | ... | ... |\n\nВ этой таблице мы видим три поля:\n\n* UserID — ID покупателя\n* Full name — имя и фамилия покупателя\n* Email — адрес электронной почты\n\nТеперь топ-менеджер магазина сообщил нам, что мы должны внести новое поле: есть ли у покупателя скидочная карта или нет. В нашей таблице уже хранится 10 000 строк, поэтому вписывать в каждую наличие скидочной карты будет долго. При этом один покупатель мог быть вписан в таблицу несколько раз, если он менял адрес электронной почты.\n\nЧтобы решить эту задачу просто, мы создадим новую таблицу `Users discount card`, в которой будет всего два поля:\n\n* ID покупателя\n* Наличие у покупателя скидочной карты\n\nПри этом, чтобы не вписывать наличие карты у одного и того же покупателя несколько раз, мы сделаем поле `UserID` уникальным.\n\nПосмотрим на такую таблицу:\n\n**users_discount_card**\n\n| UserID | Discount card |\n| --- | --- |\n| 1 | true |\n| 2 | true |\n| 3 | false |\n| ... | ... |\n\nТаблица содержит только `UserID` и `Discount card` — есть у пользователя скидочная карта или нет. Теперь эту таблицу легко связать с таблицей `Users` по `UserID` с помощью SQL-функции `join` и получить для каждого пользователя наличие у него скидочной карты. При этом нам не нужно вносить в `Users` новое поле.\n\nТакие отношения между таблицами называются **один к одному**. В таком типе отношений только одному пользователю соответствует только одна метка наличия карты.\n\n### Один ко многим\n\nТеперь предположим, что мы хотим хранить в таблице `Users` номера телефонов покупателей. У одного покупателя может быть несколько номеров телефонов, но один номер телефона принадлежит только одному покупателю. Эта связь называется **один ко многим**.\n\nДля этого мы создадим новую таблицу `Phones`:\n\n**users**\n\n| PhoneID | UserID | Phone |\n| --- | --- | --- |\n| 1 | 1 | +7 999 999 88 99 |\n| 2 | 2 | +7 909 111 88 00 |\n| 3 | 1 | +7 952 952 95 52 |\n| ... | ... | ... |\n\nВ этой таблице есть три поля:\n\n* PhoneID — ID номера телефона\n* UserID — ID покупателя, которому принадлежит телефон\n* Phone — номер телефона\n\nПри этом `PhoneID` должен быть в таблице уникален, но, как мы видим, `UserID` повторяется. Это значит, что у одного покупателя есть несколько номеров.\n\n### Многие ко многим\n\nЧтобы понять отношение **многие ко многим**, рассмотрим еще одну таблицу `Products`:\n\n**products**\n\n| ProductID | Product |\n| --- | --- |\n| 1 | Футболка черная женская |\n| 2 | Кеды белые мужские |\n| 3 | Платье в горошек детское |\n| ... | ... |\n\nВ таблице содержится список товаров магазина — ID товара и наименование. Мы хотим связать, какие товары какой покупатель приобрел. При этом один покупатель может купить много разных товаров, но и один и тот же товар могут купить разные люди. Таблица-отношение между таблицами `Users` и `Products` будет выглядеть так:\n\n**users_products**\n\n| UserProductID | UserID | ProductID |\n| --- | --- | --- |\n| 1 | 1 | 10 |\n| 2 | 1 | 5 |\n| 3 | 6 | 10 |\n| ... | ... | ... |\n\nВ этой таблице мы видим, что пользователь с `ID = 1` купил товары с ID 10 и 5, но товар 10 купили два пользователя: с `ID = 1` и `ID = 6`. Такие отношения и есть «многие ко многим».\n\nКроме того, что существуют разные виды отношений между таблицами, само проектирование баз данных и таблиц возможно несколькими способами.\n\n## Проектирование таблиц\n\nЕсть такие виды проектирования баз данных:\n\n* Концептуальное моделирование\n* Логическое моделирование\n* Физическое моделирование\n\nКонцептуальное проектирование заключается в том, что мы описываем, какие у нас будут сущности в базе данных и отношения. Сущности, как мы говорили выше — это отдельные таблицы. Отношения — какие сущности и как будут связаны друг с другом: «один к одному», «один ко многим» или «многие ко многим». То, о чем мы говорили выше, — это концептуальное проектирование.\n\nЛогическое проектирование дополняет концептуальное тем, что мы уже рассматриваем особенности модели данных, которые будут храниться в базе данных. Модели данных бывают реляционными, нереляционными и другими.\n\nФизическое моделирование уже учитывает проектирование на уровне особенностей каждой отдельной базы данных: PostgreSQL, MySQL. При физическом моделировании мы выбираем, какие запросы нам важны больше всего: чтение из таблицы или запись в таблицу, и производим тестирование на пропускную способность.\n\nКак правило, разные типы моделирования используются при проектировании одной и той же базы данных:\n\n1. Концептуально описываем схему базы данных и сущности, которые там содержатся\n2. Выбираем модель данных в логическом моделировании\n3. Рассматриваем более детально реализацию базы данных в конкретном хранилище с учетом конкретной базы, которую мы выбрали\n\nТаким образом, проектирование одной базы данных обычно проходит через все три этапа. Каждый следующий этап дополняет и уточняет предыдущий.\n\nМы знаем, что называется таблицей в базе данных, какие отношения между таблицами существуют и какие есть типы проектирования баз данных. Теперь перейдем к практике и агрегируем таблицу продаж.\n\n## Агрегация таблицы продаж\n\nРассмотрим базу данных [table](https://www.db-fiddle.com/f/digYryvuN7FFhXUzYjhbb9/4). В ней есть одна таблица `sales`, которая содержит информацию о покупках в магазине. Посмотрим на эту таблицу:\n\n**sales**\n\n| | | | | | | | | | | | | | | | | | | | |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n| order_id | order_date | ship_date | ship_mode | customer_id | customer_name | segment | country | city | state | postal_code | region | product_id | category | sub_category | product_name | sales | quantity | discount | profit |\n| CA-2014-103800 | 2014-01-03 00:00:00 | 2014-01-07 00:00:00 | Standard Class | DP-13000 | Darren Powers | Consumer | United States | Houston | Texas | 77095 | Central | OFF-PA-10000174 | Office Supplies | Paper | Message Book, Wirebound, Four 5 1/2\" X 4\" Forms/Pg., 200 Dupl. Sets/Book | 16.448 | 2 | 0.2 | 5.551199999999998 |\n| CA-2014-112326 | 2014-01-04 00:00:00 | 2014-01-08 00:00:00 | Standard Class | PO-19195 | Phillina Ober | Home Office | United States | Naperville | Illinois | 60540 | Central | OFF-LA-10003223 | Office Supplies | Labels | Avery 508 | 11.784 | 3 | 0.2 | 4.271699999999999 |\n| CA-2014-112326 | 2014-01-04 00:00:00 | 2014-01-08 00:00:00 | Standard Class | PO-19195 | Phillina Ober | Home Office | United States | Naperville | Illinois | 60540 | Central | OFF-ST-10002743 | Office Supplies | Storage | SAFCO Boltless Steel Shelving | 272.736 | 3 | 0.2 | -64.77480000000001 |\n| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |\n\nВ этой таблице мы видим информацию о покупателях и купленных товарах.\n\nМы хотим определить из таблицы топ-5 самых прибыльных товаров. Наименование товара указано в поле `product_name`, а прибыль — в `sales`. Напишем SQL-запрос:\n\n```SQL\nSELECT\n product_name,\n SUM(sales)\nFROM sales\nGROUP BY product_name\nORDER BY SUM(sales) DESC\nLIMIT 5;\n```\n\n[Ссылка на таблицу](https://www.db-fiddle.com/f/digYryvuN7FFhXUzYjhbb9/5)\n\nВ этом запросе мы произвели агрегацию по товарам и посчитали суммарную прибыль по всем товарам. Мы использовали `ORDER BY SUM(sales) DESC`, чтобы отсортировать товары по убыванию прибыли. `LIMIT 5` позволяет отобрать только пять верхних строк таблицы.\n\nТак мы нашли топ-5 самых прибыльных товаров. При этом самый удобный и наглядный способ решить нашу задачу — это просто таблица, без построения диаграмм.\n\n## Выводы\n\nВ этом уроке мы рассмотрели, что такое таблица, какие у нее есть компоненты, и какие существуют отношения между таблицами. Мы поговорили о том, как можно проектировать базы данных. Еще мы агрегировали таблицу продаж и нашли топ-5 самых прибыльных товаров в магазине.\n\nИспользование простой таблицы для этой задачи стало лучшим и более наглядным решением, чем построение диаграмм. С помощью знаний о таблицах вы станете лучше понимать, как работают базы данных, и сможете применить эти знания на практике.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":7100,"name":"theory","url":"/courses/analytics-with-sql/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":7100,"name":"theory","url":"/courses/analytics-with-sql/lessons/intro/theory_unit"}],"id":3144,"slug":"intro","state":"approved","name":"Введение","course_order":50,"goal":"Знакомимся с темой курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"В этом курсе мы изучим тему «Продвинутая аналитика на SQL». Полученные знания и навыки понадобятся, чтобы извлекать данные из больших наборов данных, превращать их в понятную и управляемую форму и создавать отчеты, которые помогут принимать более обоснованные решения.\n\nТакже в результате обучения вы сможете обеспечить безопасность и эффективность хранения и использования данных.\n\n## Какие темы изучим на курсе\n\nВ рамках курса мы изучим следующие темы:\n\n* Концепция витрины\n* Устройство графиков\n* Агрегация для визуализации\n* Агрегация для столбчатой диаграммы\n* Агрегация для candlestick-диаграм\n* Pie Chart\n* Scatter Chart\n* Funnel analysis\n* Устройство таблиц\n\nМы узнаем, что в основе любого графика лежит таблица с данными. В итоге научимся производить агрегацию для визуализации. Полученные навыки пригодятся, если нужно анализировать полученные данные с помощью разных диаграмм. Знания из этого курса помогут построить столбчатую, круговую, точечную и candlestick-диаграммы.\n"},"id":305,"slug":"analytics-with-sql","challenges_count":3,"name":"Продвинутая аналитика на SQL","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы научитесь строить графики на основе данных, полученных с помощью SQL-запросов. Вы узнаете, что в основе любого графика лежит таблица с данными. В итоге вы научитесь производить агрегацию для визуализации. Полученные навыки пригодятся, если вы решите анализировать полученные данные с помощью разных диаграмм. Знания из этого курса помогут построить столбчатую, круговую, точечную и candlestick-диаграммы.","kind":"advanced","updated_at":"2026-01-20T11:54:43.063Z","language":"sql","duration_cache":39420,"skills":["Объединять Google Sheets и SQL","Отображать результаты запросов в виде графиков и диаграмм","Строить и анализировать различные диаграммы","Производить агрегацию для визуализации"],"keywords":[],"lessons_count":10,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTA2OCwicHVyIjoiYmxvYl9pZCJ9fQ==--0d42690e9597873346e7166608b35971ac254007/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":55,"slug":"data-analytics","title":"Аналитик данных","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":80,"duration_in_months":7},"id":98,"slug":"data-analytics","title":"Аналитик данных","subtitle":"Изучите SQL, Python, Pandas, Tableau, Superset и методы A/B-тестов.","subtitle_for_lists":"Изучите SQL, Python, Pandas, Tableau, Superset и методы A/B-тестов.","locale":"ru","current":true,"duration_in_months_text":"7 месяцев","stack_slug":"data-analytics","price_text":"от 4 395 ₽","duration_text":"7 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzY1MywicHVyIjoiYmxvYl9pZCJ9fQ==--5107185de77b3481e0a836f9fc7326c4e1b77be4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Data%20extraction-pana.png"},{"stack":{"id":75,"slug":"sql-for-data-analysts","title":"SQL для анализа данных","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":1100,"duration_in_months":3},"id":133,"slug":"sql-for-data-analysts","title":"SQL для анализа данных","subtitle":"Навык работы с SQL, включая соединения, оконные функции и аналитику, для уверенного написания сложных запросов к БД","subtitle_for_lists":"Изучите SQL, соединения, оконные функции","locale":"ru","current":true,"duration_in_months_text":"3 месяца","stack_slug":"sql-for-data-analysts","price_text":"от 3 900 ₽","duration_text":"3 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk1MywicHVyIjoiYmxvYl9pZCJ9fQ==--963098414ddb264ba6c4deab9bd951f2d6778e4a/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Data%20analysis-amico.png"},{"stack":{"id":227,"slug":"bi-analyst","title":"BI-аналитик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":null,"duration_in_months":7},"id":359,"slug":"bi-analyst","title":"BI-аналитик","subtitle":"Изучите SQL, BI-инструменты и визуализацию данных","subtitle_for_lists":"Изучите SQL, BI-инструменты и визуализацию данных","locale":"ru","current":true,"duration_in_months_text":"7 месяцев","stack_slug":"bi-analyst","price_text":"от 4 395 ₽","duration_text":"7 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDg3MSwicHVyIjoiYmxvYl9pZCJ9fQ==--8175585f43b5401994e29b3ae73d76963d942512/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Browser%20stats-bro.png"},{"stack":{"id":462,"slug":"product-analyst","title":"Продуктовый аналитик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":500,"duration_in_months":7},"id":591,"slug":"product-analyst","title":"Продуктовый аналитик","subtitle":"Изучите продуктовые метрики, A/B-тесты и анализ пользовательских данных","subtitle_for_lists":"Изучите продуктовые метрики, A/B-тесты и анализ пользовательских данных","locale":"ru","current":true,"duration_in_months_text":"7 месяцев","stack_slug":"product-analyst","price_text":"от 4 395 ₽","duration_text":"7 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDg2MiwicHVyIjoiYmxvYl9pZCJ9fQ==--04a703ca18d7bf689064f1f3c2721058bd5564e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Statistics-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/analytics-with-sql/lessons/table/theory_unit","version":"0b0c6d4ebbd40fd58630a0dd89cc25544ccdf24e","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">Продвинутая аналитика на SQL</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":"Продвинутая аналитика на SQL"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Таблицы — необходимый компонент баз данных. Чтобы построить аналитику, мы часто агрегируем данные из таблиц и строим графики. Но иногда анализ самой таблицы без графиков оказывается удобнее.</p>
<p>В этом уроке мы рассмотрим, что такое таблица, какие существуют отношения между таблицами, и как проектировать таблицы. Мы агрегируем данные продаж по товарам и найдем самые прибыльные из них.</p>
<p>С более глубокими знаниями о таблицах можно проводить хорошую и разностороннюю аналитику и выбирать нужный метод анализа под каждую задачу.</p>
<h2 id="heading-2-1">Таблица</h2>
<p>Для начала рассмотрим, что такое таблица в базе данных. Посмотрим на пример ниже:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE0MSwicHVyIjoiYmxvYl9pZCJ9fQ==--ed0b4bd7e6ccd1fb9409371dcda6bead5d0bc54d/table.png" alt="table" loading="lazy"/></p>
<p>Таблица содержит информация о разных пиццериях. В ней есть следующие столбцы:</p>
<ul>
<li>Название — это название пиццерии</li>
<li>Доставка — есть у пиццерии доставка или нет</li>
<li>Начало работы — с какого времени открыта пиццерия</li>
</ul>
<p>Вся информация в таблице — это параметры разных пиццерий, то есть смысл этой таблицы. Смысл называется <strong>сущностью</strong> — это то, о чем хранится информация в таблице.</p>
<p>Для таблиц как сущностей есть такое правило: одна таблица — одна сущность. Например, в таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> хранится только информация о пользователях, в таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Payments</code> — о платежах, а в таблице выше — о пиццериях.</p>
<p>Таблицы в базах данных могут быть связаны друг с другом. Такие базы называются <strong>реляционными</strong>.</p>
<p>В реляционных базах содержатся предопределенные связи между таблицами. В каждой таблице хранится информация об отдельной сущности, но эти сущности могут быть связаны.</p>
<p>Например, таблица <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> хранит информацию о пользователе — его ФИО, адрес электронной почты и другие его параметры. Но каждый пользователь совершает какой-то платеж, и эти платежи хранятся в таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Payments</code>.</p>
<p>Таблицы <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Payments</code> связаны. Такие связи называются <strong>отношениями</strong> между сущностями. Язык <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">SQL</code> — это основной инструмент работы с реляционными базами данных.</p>
<p>У таблиц в реляционных базах данных есть разные компоненты. Их использование является одним из необходимых условий реляционной модели, потому что они позволяют связывать разные сущности. Рассмотрим их подробнее.</p>
<h3 id="heading-3-2">Компоненты таблицы</h3>
<p>Обязательные компоненты таблиц такие:</p>
<ul>
<li>Столбец</li>
<li>Строка</li>
<li>Домен</li>
<li>Ключ</li>
</ul>
<p>Столбец в таблицах еще называют <strong>колонкой</strong>*, <strong>полем</strong> или <strong>атрибутом</strong>. Атрибут таблицы выражает какой-то один тип информации о сущности. Например, в таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> может храниться поле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Full name</code>, и оно содержит информацию о ФИО пользователя. Поле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">User address</code> будет хранить адрес пользователя. В колонке <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Название</code> в таблице пиццерий мы видим название конкретной пиццерии.</p>
<p>Строки мы еще зовем <strong>записью</strong> или <strong>кортежем</strong>. Одна строка — это одна единица сущности. В таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> одна запись — это один пользователь, в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Payments</code> — это один платеж, в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Пиццерии</code> — одна пиццерия.</p>
<p>При создании таблицы в базе данных мы обязательно указываем <strong>тип данных</strong> информации в поле. <strong>Домен</strong> поля — это и есть тип данных столбца. К примеру, поле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Full name</code> в таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> может содержать строку и ничего кроме строки.</p>
<p>Вот примеры доменов полей:</p>
<ul>
<li>Строка</li>
<li>Целое число</li>
<li>Число с плавающей точкой и прочее</li>
</ul>
<p>Мы уже упомянули, что таблицы в реляционных базах данных связаны. Чтобы связать две таблицы, мы используем специальное поле, которое называется <strong>ключ</strong>. В таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Payments</code> такой ключ — это колонка <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UserID</code>. Мы связываем эти две таблицы с помощью оператора <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">join</code>.</p>
<p>Ключ, который является уникальным для всей таблицы, — это <strong>первичный ключ</strong>. В таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> каждый пользователь имеет свой уникальный <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UserID</code>, и два пользователя не могут иметь один <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UserID</code>. Но в таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Payments</code> один пользователь может совершить несколько платежей, поэтому бывают строки с одинаковыми <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UserID</code>.</p>
<p>В то же время первичный ключ <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">PaymentID</code> для этой таблицы бывает только уникальным, поскольку одна строка — это один платеж, и они не могут повторяться.</p>
<p>Таблицы в реляционных базах данных связаны друг с другом, и сейчас мы рассмотрим разные типы отношений между таблицами.</p>
<h2 id="heading-2-3">Отношения таблиц</h2>
<p>Мы уже сказали, что мы можем связывать разные таблицы в базах данных. Есть такие типы <strong>связей</strong> или <strong>отношений</strong>:</p>
<ul>
<li>Один к одному</li>
<li>Один ко многим</li>
<li>Многие ко многим</li>
</ul>
<p>Рассмотрим каждый из этих типов.</p>
<h3 id="heading-3-4">Один к одному</h3>
<p>Представим, что у нас есть база данных пользователей интернет-магазина. Посмотрим на таблицу <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code>, в которой есть информация о пользователях:</p>
<p><strong>users</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>UserID</th><th>Full name</th><th>Email</th></tr></thead><tbody><tr><td>1</td><td>Александ Попов</td><td><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="mailto:popov2763294783@mail.ru">popov2763294783@mail.ru</a></td></tr><tr><td>2</td><td>Людмила Астафьева</td><td><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="mailto:ludmila_ast_783242902@yandex.ru">ludmila_ast_783242902@yandex.ru</a></td></tr><tr><td>3</td><td>Екатерина Антонова</td><td><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="mailto:ekaterina_antonova_99113984302@gmail.com">ekaterina_antonova_99113984302@gmail.com</a></td></tr><tr><td>...</td><td>...</td><td>...</td></tr></tbody></table></div></div></div></div>
<p>В этой таблице мы видим три поля:</p>
<ul>
<li>UserID — ID покупателя</li>
<li>Full name — имя и фамилия покупателя</li>
<li>Email — адрес электронной почты</li>
</ul>
<p>Теперь топ-менеджер магазина сообщил нам, что мы должны внести новое поле: есть ли у покупателя скидочная карта или нет. В нашей таблице уже хранится 10 000 строк, поэтому вписывать в каждую наличие скидочной карты будет долго. При этом один покупатель мог быть вписан в таблицу несколько раз, если он менял адрес электронной почты.</p>
<p>Чтобы решить эту задачу просто, мы создадим новую таблицу <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users discount card</code>, в которой будет всего два поля:</p>
<ul>
<li>ID покупателя</li>
<li>Наличие у покупателя скидочной карты</li>
</ul>
<p>При этом, чтобы не вписывать наличие карты у одного и того же покупателя несколько раз, мы сделаем поле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UserID</code> уникальным.</p>
<p>Посмотрим на такую таблицу:</p>
<p><strong>users_discount_card</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>UserID</th><th>Discount card</th></tr></thead><tbody><tr><td>1</td><td>true</td></tr><tr><td>2</td><td>true</td></tr><tr><td>3</td><td>false</td></tr><tr><td>...</td><td>...</td></tr></tbody></table></div></div></div></div>
<p>Таблица содержит только <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UserID</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Discount card</code> — есть у пользователя скидочная карта или нет. Теперь эту таблицу легко связать с таблицей <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> по <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UserID</code> с помощью SQL-функции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">join</code> и получить для каждого пользователя наличие у него скидочной карты. При этом нам не нужно вносить в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> новое поле.</p>
<p>Такие отношения между таблицами называются <strong>один к одному</strong>. В таком типе отношений только одному пользователю соответствует только одна метка наличия карты.</p>
<h3 id="heading-3-5">Один ко многим</h3>
<p>Теперь предположим, что мы хотим хранить в таблице <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> номера телефонов покупателей. У одного покупателя может быть несколько номеров телефонов, но один номер телефона принадлежит только одному покупателю. Эта связь называется <strong>один ко многим</strong>.</p>
<p>Для этого мы создадим новую таблицу <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Phones</code>:</p>
<p><strong>users</strong></p>
<div style="--table-min-width:calc(50rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_a100c15 mantine-TableScrollContainer-scrollContainer m_d57069b5 mantine-ScrollArea-root"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-offset-scrollbars="x" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_62259741 mantine-TableScrollContainer-scrollContainerInner"><table><thead><tr><th>PhoneID</th><th>UserID</th><th>Phone</th></tr></thead><tbody><tr><td>1</td><td>1</td><td>+7 999 999 88 99</td></tr><tr><td>2</td><td>2</td><td>+7 909 111 88 00</td></tr><tr><td>3</td><td>1</td><td>+7 952 952 95 52</td></tr><tr><td>...</td><td>...</td><td>...</td></tr></tbody></table></div></div></div></div>
<p>В этой таблице есть три поля:</p>
<ul>
<li>PhoneID — ID номера телефона</li>
<li>UserID — ID покупателя, которому принадлежит телефон</li>
<li>Phone — номер телефона</li>
</ul>
<p>При этом <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">PhoneID</code> должен быть в таблице уникален, но, как мы видим, <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">UserID</code> повторяется. Это значит, что у одного покупателя есть несколько номеров.</p>
<h3 id="heading-3-6">Многие ко многим</h3>
<p>Чтобы понять отношение <strong>многие ко многим</strong>, рассмотрим еще одну таблицу <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Products</code>:</p>
<p><strong>products</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>ProductID</th><th>Product</th></tr></thead><tbody><tr><td>1</td><td>Футболка черная женская</td></tr><tr><td>2</td><td>Кеды белые мужские</td></tr><tr><td>3</td><td>Платье в горошек детское</td></tr><tr><td>...</td><td>...</td></tr></tbody></table></div></div></div></div>
<p>В таблице содержится список товаров магазина — ID товара и наименование. Мы хотим связать, какие товары какой покупатель приобрел. При этом один покупатель может купить много разных товаров, но и один и тот же товар могут купить разные люди. Таблица-отношение между таблицами <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Users</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">Products</code> будет выглядеть так:</p>
<p><strong>users_products</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>UserProductID</th><th>UserID</th><th>ProductID</th></tr></thead><tbody><tr><td>1</td><td>1</td><td>10</td></tr><tr><td>2</td><td>1</td><td>5</td></tr><tr><td>3</td><td>6</td><td>10</td></tr><tr><td>...</td><td>...</td><td>...</td></tr></tbody></table></div></div></div></div>
<p>В этой таблице мы видим, что пользователь с <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">ID = 1</code> купил товары с ID 10 и 5, но товар 10 купили два пользователя: с <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">ID = 1</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">ID = 6</code>. Такие отношения и есть «многие ко многим».</p>
<p>Кроме того, что существуют разные виды отношений между таблицами, само проектирование баз данных и таблиц возможно несколькими способами.</p>
<h2 id="heading-2-7">Проектирование таблиц</h2>
<p>Есть такие виды проектирования баз данных:</p>
<ul>
<li>Концептуальное моделирование</li>
<li>Логическое моделирование</li>
<li>Физическое моделирование</li>
</ul>
<p>Концептуальное проектирование заключается в том, что мы описываем, какие у нас будут сущности в базе данных и отношения. Сущности, как мы говорили выше — это отдельные таблицы. Отношения — какие сущности и как будут связаны друг с другом: «один к одному», «один ко многим» или «многие ко многим». То, о чем мы говорили выше, — это концептуальное проектирование.</p>
<p>Логическое проектирование дополняет концептуальное тем, что мы уже рассматриваем особенности модели данных, которые будут храниться в базе данных. Модели данных бывают реляционными, нереляционными и другими.</p>
<p>Физическое моделирование уже учитывает проектирование на уровне особенностей каждой отдельной базы данных: PostgreSQL, MySQL. При физическом моделировании мы выбираем, какие запросы нам важны больше всего: чтение из таблицы или запись в таблицу, и производим тестирование на пропускную способность.</p>
<p>Как правило, разные типы моделирования используются при проектировании одной и той же базы данных:</p>
<ol>
<li>Концептуально описываем схему базы данных и сущности, которые там содержатся</li>
<li>Выбираем модель данных в логическом моделировании</li>
<li>Рассматриваем более детально реализацию базы данных в конкретном хранилище с учетом конкретной базы, которую мы выбрали</li>
</ol>
<p>Таким образом, проектирование одной базы данных обычно проходит через все три этапа. Каждый следующий этап дополняет и уточняет предыдущий.</p>
<p>Мы знаем, что называется таблицей в базе данных, какие отношения между таблицами существуют и какие есть типы проектирования баз данных. Теперь перейдем к практике и агрегируем таблицу продаж.</p>
<h2 id="heading-2-8">Агрегация таблицы продаж</h2>
<p>Рассмотрим базу данных <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://www.db-fiddle.com/f/digYryvuN7FFhXUzYjhbb9/4" rel="noopener noreferrer" target="_blank">table</a>. В ней есть одна таблица <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">sales</code>, которая содержит информацию о покупках в магазине. Посмотрим на эту таблицу:</p>
<p><strong>sales</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></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody><tr><td>order_id</td><td>order_date</td><td>ship_date</td><td>ship_mode</td><td>customer_id</td><td>customer_name</td><td>segment</td><td>country</td><td>city</td><td>state</td><td>postal_code</td><td>region</td><td>product_id</td><td>category</td><td>sub_category</td><td>product_name</td><td>sales</td><td>quantity</td><td>discount</td><td>profit</td></tr><tr><td>CA-2014-103800</td><td>2014-01-03 00:00<div></div></td><td>2014-01-07 00:00<div></div></td><td>Standard Class</td><td>DP-13000</td><td>Darren Powers</td><td>Consumer</td><td>United States</td><td>Houston</td><td>Texas</td><td>77095</td><td>Central</td><td>OFF-PA-10000174</td><td>Office Supplies</td><td>Paper</td><td>Message Book, Wirebound, Four 5 1/2" X 4" Forms/Pg., 200 Dupl. Sets/Book</td><td>16.448</td><td>2</td><td>0.2</td><td>5.551199999999998</td></tr><tr><td>CA-2014-112326</td><td>2014-01-04 00:00<div></div></td><td>2014-01-08 00:00<div></div></td><td>Standard Class</td><td>PO-19195</td><td>Phillina Ober</td><td>Home Office</td><td>United States</td><td>Naperville</td><td>Illinois</td><td>60540</td><td>Central</td><td>OFF-LA-10003223</td><td>Office Supplies</td><td>Labels</td><td>Avery 508</td><td>11.784</td><td>3</td><td>0.2</td><td>4.271699999999999</td></tr><tr><td>CA-2014-112326</td><td>2014-01-04 00:00<div></div></td><td>2014-01-08 00:00<div></div></td><td>Standard Class</td><td>PO-19195</td><td>Phillina Ober</td><td>Home Office</td><td>United States</td><td>Naperville</td><td>Illinois</td><td>60540</td><td>Central</td><td>OFF-ST-10002743</td><td>Office Supplies</td><td>Storage</td><td>SAFCO Boltless Steel Shelving</td><td>272.736</td><td>3</td><td>0.2</td><td>-64.77480000000001</td></tr><tr><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td><td>...</td></tr></tbody></table></div></div></div></div>
<p>В этой таблице мы видим информацию о покупателях и купленных товарах.</p>
<p>Мы хотим определить из таблицы топ-5 самых прибыльных товаров. Наименование товара указано в поле <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">product_name</code>, а прибыль — в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">sales</code>. Напишем SQL-запрос:</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
product_name,
SUM(sales)
FROM sales
GROUP BY product_name
ORDER BY SUM(sales) DESC
LIMIT 5;</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><a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://www.db-fiddle.com/f/digYryvuN7FFhXUzYjhbb9/5" rel="noopener noreferrer" target="_blank">Ссылка на таблицу</a></p>
<p>В этом запросе мы произвели агрегацию по товарам и посчитали суммарную прибыль по всем товарам. Мы использовали <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">ORDER BY SUM(sales) DESC</code>, чтобы отсортировать товары по убыванию прибыли. <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">LIMIT 5</code> позволяет отобрать только пять верхних строк таблицы.</p>
<p>Так мы нашли топ-5 самых прибыльных товаров. При этом самый удобный и наглядный способ решить нашу задачу — это просто таблица, без построения диаграмм.</p>
<h2 id="heading-2-9">Выводы</h2>
<p>В этом уроке мы рассмотрели, что такое таблица, какие у нее есть компоненты, и какие существуют отношения между таблицами. Мы поговорили о том, как можно проектировать базы данных. Еще мы агрегировали таблицу продаж и нашли топ-5 самых прибыльных товаров в магазине.</p>
<p>Использование простой таблицы для этой задачи стало лучшим и более наглядным решением, чем построение диаграмм. С помощью знаний о таблицах вы станете лучше понимать, как работают базы данных, и сможете применить эти знания на практике.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/data-analytics?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">7 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Аналитик данных</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите SQL, Python, Pandas, Tableau, Superset и методы A/B-тестов.</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzY1MywicHVyIjoiYmxvYl9pZCJ9fQ==--5107185de77b3481e0a836f9fc7326c4e1b77be4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Data%20extraction-pana.png" alt="Аналитик данных" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 4 395 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/sql-for-data-analysts?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">3 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">SQL для анализа данных</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите SQL, соединения, оконные функции</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk1MywicHVyIjoiYmxvYl9pZCJ9fQ==--963098414ddb264ba6c4deab9bd951f2d6778e4a/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Data%20analysis-amico.png" alt="SQL для анализа данных" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/bi-analyst?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">7 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">BI-аналитик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите SQL, BI-инструменты и визуализацию данных</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDg3MSwicHVyIjoiYmxvYl9pZCJ9fQ==--8175585f43b5401994e29b3ae73d76963d942512/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Browser%20stats-bro.png" alt="BI-аналитик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 4 395 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/product-analyst?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">7 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Продуктовый аналитик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите продуктовые метрики, A/B-тесты и анализ пользовательских данных</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDg2MiwicHVyIjoiYmxvYl9pZCJ9fQ==--04a703ca18d7bf689064f1f3c2721058bd5564e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Statistics-bro.png" alt="Продуктовый аналитик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 4 395 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></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/analytics-with-sql/lessons/table/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 / 10</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/analytics-with-sql/lessons/table/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-CdBlNCiQ.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-nkZBEvfU.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>