SQL Academy: всё о реляционных БД и SQL
По всем вопросам и коммерческим предложениям писать @LadanovNick Купить рекламу: https://telega.in/c/sqlacademyofficial Чат студентов SQL Academy https://t.me/sqlacademyorg
Больше📈 Аналитический обзор Telegram-канала SQL Academy: всё о реляционных БД и SQL
Канал SQL Academy: всё о реляционных БД и SQL (@sqlacademyofficial) языкового сегмента Русский является активным участником. Сейчас сообщество объединяет 11 356 подписчиков, занимая 10 920 место в категории Технологии и приложения и 57 450 место в регионе Россия.
📊 Показатели аудитории и динамика
С момента создания невідомо проект демонстрирует стремительный рост, собрав аудиторию из 11 356 подписчиков.
Согласно последним данным от 26 июня, 2026, канал показывает стабильную активность. За последние 30 дней изменение числа участников составило 170, а за последние 24 часа — 6, при этом общий охват остаётся высоким.
- Статус верификации: Не верифицирован
- Уровень вовлечённости (ER): Средний показатель вовлечённости аудитории составляет 15.08%. В первые 24 часа после публикации контент обычно набирает 11.53% реакций от общего числа подписчиков.
- Охват публикаций: В среднем каждый пост получает 1 712 просмотров. В течение первых суток публикация набирает 1 309 просмотров.
- Реакции и взаимодействия: Аудитория активно поддерживает контент: среднее количество реакций на один пост — 16.
- Тематические интересы: Контент сосредоточен на ключевых темах, таких как sql, строка, индекс, auto_increment, created_at.
📝 Описание и контентная политика
Автор описывает ресурс как площадку для выражения субъективного мнения:
“По всем вопросам и коммерческим предложениям писать @LadanovNick
Купить рекламу: https://telega.in/c/sqlacademyofficial
Чат студентов SQL Academy
https://t.me/sqlacademyorg”
Благодаря высокой частоте обновлений (последние данные получены 27 июня, 2026) канал поддерживает актуальность и высокий уровень охвата публикаций. Аналитика показывает, что аудитория активно взаимодействует с контентом, что делает его важной точкой влияния в категории Технологии и приложения.
OFFSET сильно замедляет базу. Она читает тысячи тяжелых строк целиком только для того, чтобы их отбросить.
📉 В чём проблема
🔹 Представь поиск 100-й страницы в толстой энциклопедии. Вместо оглавления ты читаешь весь текст с первой страницы, пока не дойдёшь до сотой.
🔹 Так же делает база при OFFSET 100000: она собирает с диска все данные (длинные тексты, даты), отсчитывает ненужные сто тысяч строк и просто выбрасывает их. Это огромная трата ресурсов.
🚀 Решение — Поздний JOIN (Deferred Join)
Сначала мы быстро находим нужные идентификаторы, а уже к ним присоединяем полные данные:
SELECT p.*
FROM (
SELECT id FROM products
ORDER BY price
LIMIT 50 OFFSET 100000
) AS sub
JOIN products p ON p.id = sub.id;
⚙️ Как это работает
🔸 Внутренний запрос работает как оглавление. Он бежит только по легкому индексу (цене и id), мгновенно пропуская 100 тысяч записей, и выдает 50 нужных id.
🔸 Внешний JOIN обращается к самой таблице и загружает тяжелые данные только для этих 50 финальных строк.
💡 Этот контринтуитивный трюк спасет твою пагинацию, когда таблица огромная, а отказаться от навигации по номерам страниц нельзя.
CREATE TABLE users (
id INT PRIMARY KEY,
age INT,
CHECK (age >= 0 AND age <= 120)
);
Теперь нельзя вставить пользователя с возрастом -5 или 999 — MySQL просто не даст это сделать.
Синтаксис CHECK
🔸 Можно добавлять при создании таблицы (CREATE TABLE) или позже (ALTER TABLE).
🔸 Можно использовать AND, OR, арифметику, сравнения, функции (с ограничениями).
🔸 Поддерживается в MySQL 8.0+. В старых версиях CHECK игнорировался (!).
Пример с условием на строку:
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
category ENUM('book', 'game', 'toy'),
price DECIMAL(10,2),
CHECK (price >= 0)
);
Здесь мы запрещаем отрицательные цены.
Добавление ограничения в существующую таблицу:
ALTER TABLE users
ADD CONSTRAINT chk_age CHECK (age >= 0 AND age <= 120);
Что произойдёт при нарушении?
INSERT INTO users (id, age) VALUES (1, -10);
-- ERROR 3819 (HY000): Check constraint 'chk_age' is violated.
⚠️ Важно помнить:
🔹CHECK срабатывает только для новых данных — старые строки не проверяются.
🔹Если значение NULL, правило обычно не нарушается (NULL считается «неизвестным» и не сравнивается напрямую).
🔹Ошибку можно перехватывать в приложении, чтобы сообщить пользователю.
✅ Когда стоит использовать CHECK:
🔹 Для ограничений: возраст, положительная цена, длина строки, формат (через LIKE или REGEXP).
🔹Когда хотите, чтобы в таблице «по умолчанию» всегда были только корректные данные.
🔹Чтобы упростить валидацию и сделать БД самодостаточной (например, при работе с внешними источниками данных).А при мыслях об автоматизации задач и усилении своих скиллов, осекаешься - повышение квалифкации стоит десятки тысяч.
Больше 60% работодателей уже заложили бюджет на развитие команды в этом году. Просто большинство аналитиков никогда не просят. Потому что не знают, как.
Мы собрали полный гайд:
→ 8 шагов от «хочу прокачаться» до «компания всё оплатила»
→ Готовые обоснования для руководителя
→ Скрипты ответов на возражения
→ Чек-лист перед разговором с HR
Здесь - твоя грамотная заявка на корпоративный грант.
📄 Забирай методичку бесплатно → https://tglink.io/7516d0cb076c3f
Реклама. ООО "АЙТИ РЕЗЮМЕ". ИНН 4025460134. erid: 2W5zFHRhK4Did name dept salary 1 Анна IT 80 2 Борис IT 120 3 Вера IT 150 4 Глеб HR 60 5 Дина HR 90Ожидаемый результат:
dept median IT 120 ← центр из 3 HR 75 ← (60+90)/2Разбор 🧠 Что такое медиана на пальцах 🔹 Сортируем зарплаты по возрастанию 🔹 Если строк нечётно — берём центральную 🔹 Если чётно — среднее двух центральных Для отдела с зарплатами [40, 50, 70, 100] медиана = (50 + 70) / 2 = 60. 1️⃣ Что нам нужно знать про каждую строку Чтобы найти «центральную» строку в отделе, нужны две вещи: 🔹 её позиция в отсортированном списке внутри отдела 🔹 общее число строк в отделе Обе задачи решают оконные функции — ROW_NUMBER() и COUNT(*) с PARTITION BY department. 2️⃣ Как найти центральные позиции Формула для центра: 🔹 FLOOR((cnt + 1) / 2) — нижняя центральная 🔹 CEIL((cnt + 1) / 2) — верхняя центральная Магия в том, что для нечётного cnt обе формулы дают одну и ту же строку — это и есть единственная центральная. Для чётного — две соседние, которые мы потом усредним. Проверим на наших данных: 🔹 IT (cnt=3): FLOOR(4/2)=2, CEIL(4/2)=2 → берём строку №2 → зарплата 120 ✅ 🔹 HR (cnt=2): FLOOR(3/2)=1, CEIL(3/2)=2 → строки №1 и №2 → (60+90)/2 = 75 ✅ 3️⃣ Решение через оконные функции
WITH ranked AS (
SELECT
department,
salary,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY salary
) AS rn,
COUNT(*) OVER (PARTITION BY department) AS cnt
FROM employees
)
SELECT
department,
AVG(salary * 1.0) AS median_salary
FROM ranked
WHERE rn IN (FLOOR((cnt + 1) / 2.0), CEIL((cnt + 1) / 2.0))
GROUP BY department;
CTE ranked внутри каждого отдела нумерует сотрудников от самой низкой зарплаты к самой высокой и параллельно считает общее число сотрудников. Внешний запрос оставляет только «центральные» строки и усредняет их.
На что обратить внимание❗️
🔹 Деление на 2.0, а не на 2 — иначе в некоторых БД получим целочисленное деление и логика для чётных отделов поедет
🔹 AVG(salary * 1.0) — та же история, если зарплаты хранятся в INT, без умножения на 1.0 результат округлится до целого
🔹 COUNT(*) посчитает и NULL-зарплаты как строки. Если такое возможно — используйте COUNT(salary) и заранее отфильтруйте WHERE salary IS NOT NULL, иначе ROW_NUMBER поставит NULL в начало и сломает порядок
🎁 Бонус: если PERCENTILE_CONT всё-таки доступен
В PostgreSQL, Oracle, SQL Server задача решается в одну строку:
SELECT
department,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median
FROM employees
GROUP BY department;
Но на собесе обычно просят именно «руками» — чтобы проверить, понимаете ли вы, что происходит под капотом 🔍
🎯 Что запомнить
🔹 Медиана руками = ROW_NUMBER + COUNT в одном CTE
🔹 Центральные позиции — FLOOR и CEIL от (cnt+1)/2
🔹 Делим на 2.0, а не на 2 — иначе целочисленное деление всё сломает
🔹 Если в БД есть PERCENTILE_CONT — используйте его; ручная реализация нужна только для собесов и старых MySQL (до 8.0 там вообще нет оконных функций — придётся через переменные)Как это работает — коротко:
1. Таблица-клон
Rooms → триггер → Rooms_History
2. SYSTEM_TIME
Rooms → база сама ведёт историю
3. JSONB-слепки
Rooms → триггер → JSON-снимок в отдельную таблицу
1️⃣ Вариант 1: Таблица-клон
Самый понятный способ. Рядом с основной таблицей (например, Rooms) создаём её копию — Rooms_History. Каждый раз, когда кто-то меняет строку в основной таблице, автоматический механизм (триггер) сначала сохраняет старую версию строки в копию, и только потом применяет изменение.
Главная боль — Schema Drift. Допустим, разработчик добавил в основную таблицу новую колонку «площадь». Если он забыл добавить эту же колонку в таблицу-клон и обновить триггер — всё ломается. Подходит только там, где структура таблиц не менялась годами и не планирует.
2️⃣ Вариант 2: SYSTEM_TIME — встроенный видеорегистратор
Некоторые современные базы данных умеют вести историю сами, без всяких таблиц-клонов и триггеров. Достаточно один раз включить функцию — и база начнёт запоминать, когда каждая строка появилась и когда изменилась.
После этого можно буквально «заглянуть в прошлое» одной командой:
SELECT * FROM Rooms FOR SYSTEM_TIME AS OF '2026-01-01';
Эта строка говорит: «Покажи мне все комнаты в том виде, в каком они были 1 января 2026 года». Идеально для отладки: можно увидеть точное состояние данных в ту секунду, когда пользователь словил баг. Если нужна серьёзная аналитика и «путешествия во времени» — это лучший выбор.
Но есть нюанс: SYSTEM_TIME поддерживают не все базы данных. В PostgreSQL и MariaDB это работает, а вот в MySQL — нет.
3️⃣ Вариант 3: История через JSONB-слепки
Идея простая: каждый раз, когда строка меняется, мы берём её текущую версию, упаковываем целиком в один JSON-объект и сохраняем в отдельную таблицу. Если комнату не трогали полгода, ни одной новой записи за это время не появится.
Допустим, у комнаты №42 три раза менялась цена. В таблице истории будет:
room snapshot changed_at
42 {"price": 300, "status": "active"} 1 января
42 {"price": 400, "status": "active"} 15 марта
42 {"price": 500, "status": "paused"} 10 апреля
Хотим узнать цену в феврале? Берём последнюю запись до февраля — 300.
Почему JSON, а не обычная таблица-клон? Потому что JSON — это «резиновый» контейнер. Неважно, сколько новых колонок добавили в основную таблицу — JSON примет их все без единого изменения в таблице истории. Та самая проблема Schema Drift из первого варианта здесь просто не существует.
Ложка дёгтя: история раздувает базу
Об этом забывают чаще всего. Если данные меняются часто, таблица истории за полгода легко вырастает до сотен гигабайт.
Проверенная стратегия — разделять данные по «температуре»:
🔹«Горячая» история за последние 3 месяца — живёт в основной базе (PostgreSQL), к ней обращаются постоянно.
🔹 Всё, что старше — уезжает в «холодное» хранилище (ClickHouse, S3). Там место дешевле, а аналитика по миллионам старых строк работает даже быстрее.
Итого, что выбрать:
🔹 Простая система, структура не меняется → Таблица-клон с триггером
🔹 Нужна точная аналитика и «путешествие во времени» → SYSTEM_TIME
🔹 Структура часто меняется, средний масштаб → JSONB-слепки
И в любом случае — заранее решите, куда архивировать историю через год. Потом будет поздно.
Уже доступно! Исследование Telegram 2025 — ключевые инсайты года 
