Два самостоятельных задания в контексте MentorHub. Можно выполнять отдельно от основного проекта: первое — после знакомства с ограничениями и связями, второе — после JOIN и GROUP BY. Используйте PostgreSQL и обычный SQL, без ORM.
Ориентир — 25–40 минут на каждое задание. Сначала сформулируйте решение самостоятельно, затем откройте подсказку и проверьте граничные случаи.
1. Кто участвует в учебной цели
Навык: спроектировать связь many-to-many и защитить данные ограничениями БД.
У MentorHub есть пользователи и учебные цели. Несколько пользователей могут работать над одной целью; один пользователь может участвовать в нескольких целях.
Требования
- У пользователя есть числовой идентификатор и обязательный уникальный email. Для этого упражнения email сравниваются с учётом регистра; нормализация не требуется.
- У цели есть числовой идентификатор и обязательное название. Название должно содержать хотя бы один символ, отличный от обычного пробела.
- Участие связывает существующего пользователя с существующей целью. Для него хранятся момент вступления и роль:
ownerилиmember. - Один пользователь не может вступить в одну цель дважды, даже с другой ролью.
- Цель может пока не иметь участников. Владельцев может быть несколько: ограничение «ровно один owner» в это задание не входит.
- При удалении цели её записи об участии удаляются автоматически. Удалить пользователя, пока он участвует хотя бы в одной цели, нельзя.
Что сделать
- Нарисуйте связи и напишите
CREATE TABLEдляusers,goals,goal_members. Идентификаторы можно задавать вручную. - Добавьте двух пользователей и две цели. Один пользователь участвует в обеих целях, второй — только в первой. Роли назначьте самостоятельно.
- Напишите запрос: для переданного идентификатора пользователя вывести названия целей, роль и момент вступления. Отсортируйте по идентификатору цели.
- Отдельными запросами проверьте ограничения. Каждый ошибочный запрос запускайте отдельно либо используйте savepoint: после ошибки PostgreSQL не продолжает обычные команды внутри той же транзакции до её отката.
Самопроверка
| Попытка | Ожидаемое поведение |
|---|---|
| Создать второго пользователя с точно таким же email | Ошибка ограничения |
| Создать пользователя без email или цель с названием из пробелов | Ошибка ограничения |
| Добавить участие с несуществующим пользователем или целью | Ошибка ограничения |
| Добавить тому же пользователю ту же цель с другой ролью | Ошибка ограничения |
Записать роль admin или NULL |
Ошибка ограничения |
| Удалить пользователя, у которого есть участие | Удаление отклонено |
| Удалить первую цель | Её участия исчезают, оба пользователя и участие во второй цели сохраняются |
Что сдать: SQL создания таблиц, начальные данные, запрос списка целей и результаты проверок. Ограничения должны работать при прямом выполнении SQL, без проверок в Python.
Подсказка — после самостоятельной попытки
Связь many-to-many требует отдельной таблицы. Подумайте, какая пара столбцов должна быть уникальной и почему добавление роли в эту пару нарушит условие. Внешние ключи отвечают за существование связанных строк и поведение при удалении. CHECK не заменяет NOT NULL: проверьте оба ограничения.
Обсудить после решения: какие индексы создаются первичным и уникальным ключами, а какой дополнительный индекс может помочь находить участников по цели?
2. Кто выполнил недельный план
Навык: соединить таблицы, отфильтровать исходные строки и отобрать группы по агрегату.
В MentorHub записываются учебные сессии пользователей. Нужно найти тех, кто за неделю завершил не меньше двух сессий общей длительностью не меньше 120 минут.
Правила отчёта
- Неделя: с
2026-09-14 00:00:00+00включительно до2026-09-21 00:00:00+00исключительно. - Сессия относится к неделе по моменту начала
started_at. Её длительность учитывается целиком. - Учитываются только сессии со статусом
completed. - Выведите
user_id,display_name, число подходящих сессийsession_countи сумму минутtotal_minutes. - Сортировка: сначала сумма минут по убыванию, при равенстве — идентификатор пользователя по возрастанию.
- Одинаковые имена принадлежат разным пользователям. В отчёте они не должны объединяться.
Данные для запуска
Этот набор независим от первого задания. Временные таблицы существуют только в текущем соединении; выполните подготовку и своё решение в одной сессии. Для повторного запуска подготовки откройте новое соединение.
CREATE TEMP TABLE practice_users (
id integer PRIMARY KEY,
display_name text NOT NULL
);
CREATE TEMP TABLE practice_sessions (
id integer PRIMARY KEY,
user_id integer NOT NULL REFERENCES practice_users(id),
started_at timestamptz NOT NULL,
duration_minutes integer NOT NULL CHECK (duration_minutes > 0),
status text NOT NULL CHECK (status IN ('completed', 'cancelled'))
);
INSERT INTO practice_users (id, display_name) VALUES
(1, 'Алекс'), (2, 'Мира'), (3, 'Алекс'),
(4, 'Лев'), (5, 'Ника'), (6, 'Олег');
INSERT INTO practice_sessions
(id, user_id, started_at, duration_minutes, status)
VALUES
(1, 1, '2026-09-14 00:00:00+00', 60, 'completed'),
(2, 1, '2026-09-16 18:00:00+00', 60, 'completed'),
(3, 1, '2026-09-17 18:00:00+00', 90, 'cancelled'),
(4, 1, '2026-09-21 00:00:00+00', 200, 'completed'),
(5, 2, '2026-09-15 10:00:00+00', 80, 'completed'),
(6, 2, '2026-09-20 23:59:59+00', 70, 'completed'),
(7, 3, '2026-09-14 12:00:00+00', 30, 'completed'),
(8, 3, '2026-09-18 12:00:00+00', 45, 'completed'),
(9, 4, '2026-09-19 12:00:00+00', 180, 'completed'),
(10, 5, '2026-09-13 23:59:59+00', 100, 'completed'),
(11, 5, '2026-09-15 12:00:00+00', 60, 'completed'),
(12, 5, '2026-09-19 12:00:00+00', 60, 'completed');
Ожидаемый результат
Прокрутите таблицу по горизонтали →
| user_id | display_name | session_count | total_minutes |
|---|---|---|---|
| 2 | Мира | 2 | 150 |
| 1 | Алекс | 2 | 120 |
| 5 | Ника | 2 | 120 |
Что сдать: один SELECT-запрос и короткое объяснение, почему в отчёте нет пользователей 3, 4 и 6.
Самопроверка
- Начало недели входит в интервал, начало следующей недели — нет.
- Отменённая сессия не увеличивает ни количество, ни сумму.
- 120 минут достаточно, но одной длинной сессии недостаточно.
- Пользователи с одинаковыми именами остаются отдельными группами.
- Результат не зависит от текущей даты и часового пояса SQL-клиента.
Подсказка — после самостоятельной попытки
Сначала отберите строки по времени и статусу. Затем сгруппируйте по пользователю, посчитайте количество и сумму. WHERE проверяет отдельные строки, HAVING — полученные группы. Для границ используйте явные значения timestamptz с UTC-смещением, а не преобразование времени сессии в date.
Усложнение: верните всех пользователей, включая тех, у кого за неделю нет завершённых сессий. Для них покажите нулевые количество и сумму; отбор по недельному плану уберите. Объясните, как расположение условий по сессиям влияет на LEFT JOIN.
После выполнения
Объясните решение другому ученику без чтения SQL вслух по строкам: какие данные отбираются, что гарантируют ограничения, где проверяются границы. Через несколько дней повторите запрос с другим периодом и порогом.