Финальный проект — SQL-дашборд менеджера социальной сети

Применяем весь курс на практике: от простых SELECT до оконных функций и CTE

Прогресс курса Финальный проект

Задача

00

Схема базы данных

Таблицы социальной сети

ТаблицаКлючевые поляОписание
usersid, name, age, gender, cityПользователи
communitiesid, name, ratingСообщества
postsid, community_id, title, scoreПубликации
likesid, user_id, post_id, liked_atЛайки к постам
viewsid, user_id, community_id, viewed_atПросмотры сообществ
01

Общие метрики активности

Первый блок дашборда — сводные цифры: сколько пользователей, лайков, просмотров и каков средний score постов.

SELECT
    (SELECT COUNT(*) FROM users)         AS total_users,
    (SELECT COUNT(*) FROM communities)    AS total_communities,
    (SELECT COUNT(*) FROM posts)          AS total_posts,
    (SELECT COUNT(*) FROM likes)          AS total_likes,
    (SELECT COUNT(*) FROM views)          AS total_views,
    (SELECT ROUND(AVG(score), 1) FROM posts) AS avg_score;
Результат
total_userstotal_communitiestotal_poststotal_likestotal_viewsavg_score
106131818934.6
02

Топ-5 постов по количеству лайков

Какие публикации вызвали наибольший отклик аудитории? Объединяем posts и likes.

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, p.title
LIMIT 5;
Результат
titlecommunityscorelikes_count
review Elden Ring DLCGamersHub8003
tutorial homemade pastaFoodLovers10002
tutorial portrait lightingPhotoWorld10002
review dev tools 2026TechTalks9002
review Lightroom vs CapturePhotoWorld8002
По два лайка у пяти постов, а мест после лидера осталось четыре. Без последнего ключа сортировки p.title база вправе каждый раз отбрасывать разные посты с равными лайками и score. С LIMIT порядок должен быть однозначным — иначе дашборд «мигает» между обновлениями.
03

Топ-5 пользователей по активности

Самые активные пользователи — те, кто поставил больше всего лайков. Считаем и добавляем их город.

SELECT
    u.name,
    u.city,
    COUNT(l.id)  AS likes_count
FROM   users u
LEFT JOIN likes l ON l.user_id = u.id
GROUP BY u.id, u.name, u.city
ORDER BY likes_count DESC, u.name
LIMIT 5;
Результат
namecitylikes_count
AndreyMoscow2
AnnaMoscow2
DenisKazan2
DmitriyKazan2
ElviraKazan2

По два лайка у восьми пользователей из десяти — топ-5 здесь мало что говорит, это особенность маленькой учебной базы. На живых данных распределение неравномерное, и такой отчёт показывает ядро аудитории.

04

Метрики по сообществам

Для каждого сообщества: рейтинг, количество постов, средний score постов, количество лайков и просмотров.

Соблазнительно присоединить к сообществам посты, лайки и просмотры одним рядом LEFT JOIN и посчитать COUNT(DISTINCT …). Счётчики так получатся верными, а AVG(p.score) — нет: каждый пост повторится столько раз, сколько у него лайков, умноженных на просмотры сообщества, и популярные посты перевесят. У GamersHub вышло бы 875 вместо 950. Поэтому каждую метрику считаем отдельно и присоединяем уже готовые итоги.
SELECT
    c.name,
    c.rating,
    p.posts_count,
    p.avg_score,
    COALESCE(l.likes_count, 0) AS likes_count,
    COALESCE(v.views_count, 0) AS views_count
FROM      communities c
LEFT JOIN (SELECT community_id, COUNT(*) AS posts_count, ROUND(AVG(score), 1) AS avg_score
           FROM posts GROUP BY community_id) p ON p.community_id = c.id
LEFT JOIN (SELECT p.community_id, COUNT(*) AS likes_count
           FROM likes l JOIN posts p ON p.id = l.post_id
           GROUP BY p.community_id) l ON l.community_id = c.id
LEFT JOIN (SELECT community_id, COUNT(*) AS views_count
           FROM views GROUP BY community_id) v ON v.community_id = c.id
ORDER BY likes_count DESC, c.name;
Результат
nameratingposts_countavg_scorelikes_countviews_count
GamersHub4.32950.043
PhotoWorld3.82900.045
TechTalks4.62750.045
FoodLovers4.121050.032
SportLife3.531066.721
TravelDiaries4.22825.012
05

Новые активные пользователи по неделям

«Новый» пользователь — тот, кто поставил первый лайк на данной неделе. Смотрим, как растёт аудитория. В учебной базе все лайки поставлены за 12 дней января, поэтому группируем по неделям: по месяцам получилась бы одна строка.

WITH first_like AS (
    SELECT
        user_id,
        MIN(liked_at)                           AS first_at,
        DATE_TRUNC('week', MIN(liked_at))::date AS cohort_week
    FROM   likes
    GROUP BY user_id
)
SELECT
    cohort_week,
    COUNT(*) AS new_users
FROM   first_like
GROUP BY cohort_week
ORDER BY cohort_week;
Результат
cohort_weeknew_users
2025-12-293
2026-01-056
2026-01-121

DATE_TRUNC('week', …) возвращает понедельник недели, поэтому первая когорта датирована 29 декабря 2025, хотя лайки в ней — 1–4 января. За первую неделю пришли 3 пользователя, за вторую — 6, за третью — 1.

06

Бонус: ранжирование через оконные функции

Оконные функции (RANK, DENSE_RANK, ROW_NUMBER) позволяют ранжировать строки внутри групп, не теряя детализацию.

-- Ранг пользователей по лайкам внутри каждого города
SELECT
    u.name,
    u.city,
    COUNT(l.id)                                     AS likes_count,
    RANK()       OVER (PARTITION BY u.city ORDER BY COUNT(l.id) DESC) AS rnk,
    DENSE_RANK() OVER (PARTITION BY u.city ORDER BY COUNT(l.id) DESC) AS dense_rnk
FROM   users u
LEFT JOIN likes l ON l.user_id = u.id
GROUP BY u.id, u.name, u.city
ORDER BY u.city, rnk, u.name;
Результат
namecitylikes_countrnkdense_rnk
DenisKazan211
DmitriyKazan211
ElviraKazan211
AndreyMoscow211
AnnaMoscow211
PeterMoscow211
MariaMoscow142
KateNovosibirsk211
NatalieNovosibirsk211
IrinaSaint-Petersburg111

Разница между функциями видна на Москве: три пользователя делят первое место, и RANK отдаёт Maria четвёртое — пропуская 2 и 3, а DENSE_RANK — второе, без пропусков.

-- Накопительный итог лайков по датам
SELECT
    liked_at,
    COUNT(*)                              AS daily_likes,
    SUM(COUNT(*)) OVER (ORDER BY liked_at) AS cumulative_likes
FROM   likes
GROUP BY liked_at
ORDER BY liked_at;
Результат
liked_atdaily_likescumulative_likes
2026-01-0111
2026-01-0212
2026-01-0324
2026-01-0415
2026-01-0516
2026-01-0617
2026-01-0718
2026-01-08210
2026-01-09212
2026-01-10214
2026-01-11216
2026-01-12218
SUM(COUNT(*)) OVER (ORDER BY liked_at) — оконная функция поверх агрегата. COUNT(*) агрегирует строки в группе, а SUM … OVER считает накопительный итог по всем группам в порядке даты.
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.

Вы прошли курс!

За эти девять модулей вы освоили:

Следующий шаг — практика на реальных данных. Попробуйте Kaggle, public datasets PostgreSQL или откройте свою базу данных в pgAdmin и задайте ей правильные вопросы.

Модуль 08: Функции Все модули На главную