Раздел 13 — Итоговый мини-проект разработчика

Собрать весь курс воедино: от спроектированной схемы до работающей аналитики

Прогресс курса Раздел 13 из 13

Что вы делаете в этом разделе

4 академических часа практики. Теории в разделе нет. Работа выполняется на схеме, которую вы вели через весь курс: спроектировали в разделе 3, нормализовали в 4, создали в 7, наполнили в 8.
01

Постановка задачи

Вы — разработчик, которому поставили задачу: «сделай нам дашборд, чтобы видеть, что происходит». Дашборд — набор показателей, который руководитель открывает утром и понимает состояние дел.

Работа состоит из четырёх частей:

Состав итоговой работы

  • Витрина показателей. Набор запросов, отвечающих на вопросы бизнеса вашей предметной области.
  • Оформление. Показатели вынесены в представления, чтобы ими могли пользоваться другие.
  • Производительность. Индексы под запросы дашборда, подтверждённые планами выполнения.
  • Доказательство корректности. Каждый ключевой показатель сверен независимым способом.
Главный риск итоговой работы — красивый отчёт с неверными числами. Вспомните раздел 10: два соединения «ко многим» перемножают строки, и все агрегаты завышаются, не вызывая никакой ошибки. Отчёт, который никто не сверял с исходными данными, следует считать неверным, пока не доказано обратное.
02

Образец: дашборд менеджера социальной сети

Разбор эталонной работы на учебной базе. Ваша задача — сделать аналогичное на своей предметной области, а не повторить эти запросы.

Показатель 1. Общие метрики

-- Сводка одной строкой: сколько всего чего в системе
SELECT (SELECT count(*) FROM users)       AS users_total,
       (SELECT count(*) FROM communities) AS communities_total,
       (SELECT count(*) FROM posts)       AS posts_total,
       (SELECT count(*) FROM likes)       AS likes_total,
       (SELECT count(DISTINCT user_id) FROM likes) AS users_active;
Независимые подзапросы вместо соединений — здесь это единственный корректный способ. Попытка получить те же числа одним запросом с соединениями даст перемножение строк.

Показатель 2. Топ постов по вовлечённости

SELECT p.title,
       c.name AS community,
       p.score,
       count(l.id) AS likes_count
FROM   posts p
JOIN   communities c ON c.id = p.community_id
LEFT JOIN likes l ON l.post_id = p.id
GROUP BY p.id, p.title, c.name, p.score
ORDER BY likes_count DESC, p.score DESC
LIMIT 5;

Здесь соединение с likes одно, поэтому агрегат корректен. Группировка идёт по p.id — первичному ключу, что позволяет выводить остальные столбцы таблицы (раздел 10).

Показатель 3. Активность пользователей

-- Два независимых счётчика — агрегируем ДО соединения
WITH likes_agg AS (
    SELECT user_id, count(*) AS cnt FROM likes GROUP BY user_id
),
views_agg AS (
    SELECT user_id, count(*) AS cnt,
           count(DISTINCT community_id) AS communities
    FROM   views GROUP BY user_id
)
SELECT u.name,
       u.city,
       coalesce(l.cnt, 0)         AS likes_count,
       coalesce(v.cnt, 0)         AS views_count,
       coalesce(v.communities, 0) AS communities_visited,
       CASE
           WHEN coalesce(l.cnt, 0) = 0 THEN 'пассивный'
           WHEN l.cnt <= 2             THEN 'обычный'
           ELSE                          'активный'
       END AS segment
FROM   users u
LEFT JOIN likes_agg l ON l.user_id = u.id
LEFT JOIN views_agg v ON v.user_id = u.id
ORDER BY likes_count DESC, u.name;

Показатель 4. Динамика по месяцам

-- Помесячная активность; generate_series даёт месяцы без пропусков
WITH months AS (
    SELECT generate_series(
        date_trunc('month', (SELECT min(liked_at) FROM likes)),
        date_trunc('month', (SELECT max(liked_at) FROM likes)),
        interval '1 month'
    )::date AS month
),
monthly AS (
    SELECT date_trunc('month', liked_at)::date AS month,
           count(*) AS likes_count,
           count(DISTINCT user_id) AS active_users
    FROM   likes
    GROUP BY 1
)
SELECT m.month,
       coalesce(x.likes_count, 0)  AS likes_count,
       coalesce(x.active_users, 0) AS active_users
FROM   months m
LEFT JOIN monthly x ON x.month = m.month
ORDER BY m.month;
Приём с generate_series решает частую проблему отчётов: месяц без данных просто исчезает из результата, и на графике образуется разрыв, который читается как «данных нет», а не как «активности не было». Сетка периодов строится отдельно, факты подтягиваются к ней через LEFT JOIN.

Показатель 5. Ранжирование оконными функциями

-- Топ-2 поста в каждом сообществе
WITH ranked AS (
    SELECT c.name AS community,
           p.title,
           p.score,
           row_number() OVER (PARTITION BY c.id ORDER BY p.score DESC, p.id) AS rn,
           round(avg(p.score) OVER (PARTITION BY c.id)) AS community_avg
    FROM   posts p
    JOIN   communities c ON c.id = p.community_id
)
SELECT community, title, score, community_avg
FROM   ranked
WHERE  rn <= 2
ORDER BY community, rn;
Оконная функция считает по группе строк, но не схлопывает их — в отличие от GROUP BY. Это позволяет в одной строке показать и значение самой записи, и показатель по её группе. Задача «топ-N внутри каждой категории» без оконных функций решается заметно тяжелее. В программе курса эта тема не значится, поэтому здесь она даётся как дополнительная возможность.

Оформление витрины

-- Каждый показатель — представление с говорящим именем
CREATE VIEW v_dashboard_totals AS ...;
CREATE VIEW v_dashboard_top_posts AS ...;
CREATE VIEW v_dashboard_user_activity AS ...;

-- Тяжёлый показатель — материализованное представление с обновлением по расписанию
CREATE MATERIALIZED VIEW mv_dashboard_monthly AS ...;
CREATE UNIQUE INDEX uq_mv_dashboard_monthly ON mv_dashboard_monthly (month);

-- Права: аналитик читает витрину, но не исходные таблицы
GRANT SELECT ON v_dashboard_totals TO app_readonly;
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.

Что вы прошли

Полный цикл работы с базой данных

  • Разделы 1–2. Зачем нужна СУБД, реляционная модель, ключи и связи
  • Разделы 3–4. Проектирование от предметной области до нормализованной схемы
  • Разделы 5–6. Развёртывание, настройка, права, резервное копирование, мониторинг
  • Разделы 7–8. Создание структуры, миграции, работа с данными
  • Разделы 9–11. Запросы, соединения, агрегация, индексы, транзакции
  • Раздел 12. Ориентация на рынке СУБД и обоснование выбора
  • Раздел 13. Собственная система от идеи до работающей аналитики

Куда двигаться дальше: оконные функции и аналитический SQL, PL/pgSQL и триггеры (приложение А1), репликация и отказоустойчивость, партиционирование больших таблиц, углублённая оптимизация запросов, полнотекстовый поиск и работа с jsonb.

Самое ценное из курса — не синтаксис, а привычки: сначала спроектировать, потом создавать; проверять число затронутых строк перед изменением; сверять отчёт с исходными данными; хранить структуру в миграциях; давать приложению минимальные права. Синтаксис ищется в документации за минуту, а привычки нарабатываются годами и отличают разработчика от того, кто «умеет писать SELECT».
Раздел 12: Другие СУБД Все разделы курса