SQL Academy: всё о реляционных БД и SQL
По всем вопросам и коммерческим предложениям писать @LadanovNick Купить рекламу: https://telega.in/c/sqlacademyofficial Чат студентов SQL Academy https://t.me/sqlacademyorg
Показати більше📈 Аналітичний огляд Telegram-каналу SQL Academy: всё о реляционных БД и SQL
Канал SQL Academy: всё о реляционных БД и SQL (@sqlacademyofficial) у мовному сегменті Російська є активним учасником. На даний момент спільнота об'єднує 11 365 підписників, посідаючи 10 911 місце в категорії Технології та додатки та 57 339 місце у регіоні Росія.
📊 Показники аудиторії та динаміка
З моменту свого створення невідомо, проект продемонстрував стрімке зростання, зібравши аудиторію у 11 365 підписників.
За останніми даними від 28 червня, 2026, канал демонструє стабільну активність. Хоча за останні 30 днів спостерігається зміна кількості учасників на 180, а за останні 24 години на 9, загальне охоплення залишається високим.
- Статус верифікації: Не верифікований
- Рівень залученості (ER): Середній показник залученості аудиторії становить 17.77%. Протягом перших 24 годин після публікації контент зазвичай збирає 11.66% реакцій від загальної кількості підписників.
- Охоплення публікацій: В середньому кожен допис отримує 2 020 переглядів. Протягом першої доби публікація в середньому набирає 1 325 переглядів.
- Реакції та взаємодія: Аудиторія активно підтримує контент: середня кількість реакцій на один пост – 16.
- Тематичні інтереси: Контент зосереджений навколо ключових тем, таких як sql, строка, индекс, auto_increment, created_at.
📝 Опис та контентна політика
Автор описує ресурс як майданчик для висловлення суб'єктивної думки:
“По всем вопросам и коммерческим предложениям писать @LadanovNick
Купить рекламу: https://telega.in/c/sqlacademyofficial
Чат студентов SQL Academy
https://t.me/sqlacademyorg”
Завдяки високій частоті оновлень (останні дані отримано 29 червня, 2026), канал підтримує актуальність та високий рівень охоплення публікацій. Аналітика показує, що аудиторія активно взаємодіє з контентом, що робить його важливою точкою впливу в категорії Технології та додатки.
-- создаём последовательность с шага 1, стартуем с 10 000
CREATE SEQUENCE ticket_seq
START WITH 10000
INCREMENT BY 1;
2️⃣ Вставка чисел в таблицу
INSERT INTO tickets (id, user_id, created_at)
VALUES (NEXT VALUE FOR ticket_seq, 42, NOW());
Получаем уникальный id без AUTO_INCREMENT в самой таблице.
3️⃣ Один счётчик — много таблиц
Нужно, чтобы tickets и invoices шли в общей нумерации? Просто используйте ту же последовательность:
INSERT INTO invoices (id, total)
VALUES (NEXT VALUE FOR ticket_seq, 199.00);
4️⃣ Дополнительные возможности
-- увеличить шаг
ALTER SEQUENCE ticket_seq
INCREMENT BY 10;
-- обнулить (осторожно!)
ALTER SEQUENCE ticket_seq
RESTART WITH 50000;
-- посмотреть текущее значение
SELECT LAST_VALUE
FROM INFORMATION_SCHEMA.SEQUENCES
WHERE SEQUENCE_NAME = 'ticket_seq';
5️⃣ Сравнение с AUTO_INCREMENT 🤔
🔹 Объект:
– SEQUENCE — отдельный объект в схеме
– AUTO_INCREMENT — жёстко привязан к таблице
🔹Один счётчик для нескольких таблиц:
– SEQUENCE — да, можно использовать в любой таблице
– AUTO_INCREMENT — нет, только внутри своей таблицы
🔹Задать старт / шаг:
– SEQUENCE — гибко: START WITH, INCREMENT BY для каждой последовательности
– AUTO_INCREMENT — глобальные AI_OFFSET / AI_INCREMENT, меньше контроля
🔹Сброс счётчика:
– SEQUENCE — ALTER SEQUENCE … RESTART WITH n
– AUTO_INCREMENT — ALTER TABLE … AUTO_INCREMENT = n (только вверх от MAX(id))
✅ Итог
SEQUENCE = гибкий, «шарящийся» счётчик, который:
🔹Позволяет одной командой нумеровать разные таблицы.
🔹Даёт точный контроль над шагом и стартом.
NTILE(N) OVER (PARTITION BY … ORDER BY …)
🔹 N — число групп (4 = квартели).
🔹 PARTITION BY (необязательно) — создаёт независимые группы (например, по стране).
🔹 ORDER BY — признак, по которому сортируем и считаем ранг.
2️⃣ Классический пример: 4 квартиля по количеству заказов
SELECT
user_id,
orders_count,
NTILE(4) OVER
(ORDER BY orders_count DESC) AS quartile
FROM user_stats
ORDER BY orders_count DESC;
🔹 Пользователи с наибольшим orders_count попадают в quartile = 1, а с наименьшим — в quartile = 4.
🔹 Если строк не делится поровну, верхние группы получат на 1 запись больше (MySQL делит «сверху»).
3️⃣ Квартели внутри каждой страны
SELECT
country,
user_id,
orders_count,
NTILE(4) OVER (
PARTITION BY country
ORDER BY orders_count DESC
) AS quartile
FROM user_stats;
Теперь каждый блок «страна» делится на свои 4 части — удобно для локальных лидеров.
4️⃣ Как использовать результат
🔹Маркетинг: таргетировать акции только quartile = 4 (самые неактивные).
🔹Аналитика: сравнить средний чек между квартилями.
🔍 Потренируйтесь
Откройте готовый плейграунд, запустите NTILE(4) и поэкспериментируйте с разным числом групп.
👉 https://sqlplayground.app/sandbox/683ef210cb9fcbe8d3db3a9e
✅ Итог
🔹NTILE(N) быстро делит данные на равные сегменты.
🔹Работает вместе с PARTITION BY и любой сортировкой.
🔹Незаменим при сегментации пользователей, товаров или чеков.
-- страница 3, по 20 записей
SELECT *
FROM posts
ORDER BY created_at DESC
LIMIT 20 OFFSET 40;
Работает просто, но есть три проблемы:
🔹 OFFSET N сначала пропускает N строк → чем дальше, тем медленнее.
🔹 Если за время прокрутки добавятся новые посты, пользователь увидит дубликаты или пропуски.
🔹При больших значениях OFFSET сервер читает тысячи строк зря.
2️⃣ Key-set pagination (a.k.a. «лента без дыр»)
Идея: вместо «пропустить N» передаём последний id/дату уже загруженной записи и берём следующие сверху.
-- первый запрос (стартовая лента)
SELECT *
FROM posts
ORDER BY created_at DESC
LIMIT 20;
-- фронт получает последнюю дату
→ next_cursor = '2025-05-26 10:15:00'
-- второй запрос (следующая порция)
SELECT *
FROM posts
WHERE created_at < '2025-05-26 10:15:00'
ORDER BY created_at DESC
LIMIT 20;
Плюсы:
✅ Нет медленного OFFSET — используется индекс по created_at.
✅ Новые посты не «вставляются» в уже просмотренную часть, дубликатов нет.
✅ Маркер-курсор можно прятать в URL или JSON‐ответе.
3️⃣ Что передавать в качестве курсора?
🔹 Авто-инкремент id — легко, но если бывают «дыры» (удаления) — используйте < id.
🔹Дата/время — удобно, но в редких случаях возможны совпадения секунд. Можно добавить второй критерий:
WHERE (created_at < :cursor_date)
OR (created_at = :cursor_date AND id < :cursor_id)
4️⃣ Советы и грабли ⚠️
🔹Создайте индекс (created_at DESC) или (created_at, id) — иначе даже key-set будет сканировать всю таблицу.
🔹Никогда не показывайте номер страницы пользователю, если используете key-set: курсор — это дата/ID, а не «страница 5».
🔹Для поиска по разным фильтрам (например, user_id) добавляйте их в индекс (user_id, created_at).
✅ Итог
🔹LIMIT + OFFSET подходит для админ-таблиц или маленьких данных.
🔹Для «бесконечных» лент используйте key-set pagination: быстрее, без дубликатов, идеально для мобильных фидов в духе Instagram/ТикТок.
Потренируйтесь: создайте таблицу posts, сгенерируйте 100 000 строк, сравните скорость OFFSET 90000 и key-set. Результат удивит! 🚀
SELECT name, is_vip
FROM customers
ORDER BY
CASE WHEN is_vip = 1 THEN 0 ELSE 1 END,
name;
-- внутри групп сортируем по алфавиту
▶️ CASE возвращает 0 для VIP, 1 — для всех остальных.
▶️ В порядке сортировки 0 идёт раньше 1, поэтому VIP‑ы будут первыми
🔹Сложнее: приоритеты A > B > C → остальные
SELECT name, status
FROM orders
ORDER BY CASE status
WHEN 'A' THEN 0
WHEN 'B' THEN 1
WHEN 'C' THEN 2
ELSE 3
END,
created_at DESC;
▶️ Сначала все со статусом A, потом B, затем C, затем остальные.
▶️ Внутри каждой группы — свежие заказы выше (по дате).
Типичные ошибки ⚠️
1️⃣ NULL‑ы: если столбец может быть NULL, добавьте явную проверку
CASE WHEN col IS NULL THEN …
2️⃣ Числа вместо строк: не забывайте, что CASE должен возвращать однотипные значения (в примерах — всегда INT).
3️⃣ Производительность: выражение в ORDER BY отключает использование индекса.
Для больших таблиц можно создать сгенерированный столбец с тем же CASE и проиндексировать его.
ALTER TABLE customers
ADD vip_sort TINYINT AS (CASE WHEN is_vip = 1 THEN 0 ELSE 1 END) STORED,
ADD INDEX (vip_sort);
Итоги ✅
🔸CASE в ORDER BY даёт гибкую условную сортировку без подзапросов.
🔸Легко расставить приоритеты: VIP, скидки, статусы…
🔸Следите за NULL‑ами и индексацией при больших объёмах.
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100)
);
🔹 Как задать стартовое значение
Нужно указать AUTO_INCREMENT = n при создании или изменить уже существующую таблицу:
-- сразу при создании
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
product VARCHAR(100)
) AUTO_INCREMENT = 1000;
-- позднее
ALTER TABLE orders AUTO_INCREMENT = 5000;
💡 Полезно, если хотите «красивые» ID или переносите старые данные.
🔹 Почему появляются «дырки» в нумерации
1. DELETE. Удалили строку с id = 5 → число 5 уже не вернётся.
2. ROLLBACK. Транзакция вставила id = 6 и откатилась — 6 пропадает.
3. Сбой сервера. MySQL резервирует диапазон ID в буфере; при крэше часть чисел «теряется».
🔹 Можно ли сбросить счётчик?
Да, но осторожно.
ALTER TABLE users AUTO_INCREMENT = 1;
Условие: новое значение должно быть больше любого существующего id. Иначе получите ошибку или конфликт PK.
🛑 На проде почти никогда не делают — потеря сквозной уникальности мешает логам и внешним ссылкам.
🔹 Грабли и советы
1️⃣ Не используйте AUTO_INCREMENT как «количество строк» — после DELETE счётчик не отражает реальное число записей.
2️⃣ Храните ссылки на записи только по уникальным id, а не позициям.
3️⃣ Если важно отсутствие дырок (к примеру, номера чеков) — создавайте отдельную таблицу «генератор последовательностей» или используйте сериализацию в приложении.
📝 Итог
1️⃣ Начать с нужного числа — AUTO_INCREMENT = N.
2️⃣ Сбросить можно, но не ниже текущего максимума.
3️⃣ Пропуски после DELETE и ROLLBACK — нормальное поведение.
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).
🔹Когда хотите, чтобы в таблице «по умолчанию» всегда были только корректные данные.
🔹Чтобы упростить валидацию и сделать БД самодостаточной (например, при работе с внешними источниками данных).= NULL всегда возвращает FALSE → условия «теряют» строки.
🔹Агрегаты (`SUM`, `AVG`) игнорируют NULL → итоги оказываются заниженными.
🔹Деление на 0 вызывает ошибку, если предварительно не обработать значение.
В MySQL есть три базовые функции, которые помогают держать ситуацию под контролем.
1️⃣ IFNULL(expr, alt) — поставить значение по умолчанию
Возвращает alt, если expr равно NULL, иначе возвращает expr.
SELECT name,
IFNULL(email, 'no-email@example.com') AS contact
FROM users;
Результат: вместо NULL-email выводится строка-заглушка.
2️⃣ COALESCE(expr1, expr2, …) — взять первое ненулевое
Последовательно проверяет аргументы слева направо и возвращает первый, который не NULL.
SELECT name,
COALESCE(email, phone, 'нет контактов') AS main_contact
FROM users;
Сценарий: если email отсутствует, берём phone; если и он NULL — выводим фразу «нет контактов».
3️⃣ NULLIF(a, b) — обнулить при равенстве
Возвращает NULL, когда a = b, иначе возвращает a. Полезно, чтобы избежать деления на ноль.
SELECT revenue / NULLIF(orders_count, 0) AS avg_check
FROM shop_stats;
Если orders_count равен 0, деление не выполняется — результатом будет NULL вместо ошибки.
Памятка
🔹Для проверок используйте IS NULL и IS NOT NULL, а не = NULL.
🔹Проверяйте, как агрегатные функции ведут себя с пропущенными данными.
🔹В формулах страхуйтесь NULLIF, чтобы не получить «division by zero».
Запомните IFNULL, COALESCE и NULLIF — с ними работа с NULL становится предсказуемой и безопасной. 🚀
INSERT INTO messages (sent_at)
VALUES (UTC_TIMESTAMP());
✅ Читаем данные и переводим в нужный пояс (например, для Москвы, +03:00)
SELECT sent_at,
CONVERT_TZ(sent_at, '+00:00', '+03:00') AS sent_at_moscow
FROM messages;
🔹 Итоговые рекомендации:
1️⃣ Используйте TIMESTAMP для автоматического хранения данных в UTC.
2️⃣ Для ручного управления используйте тип DATETIME и всегда сохраняйте в UTC.
3️⃣ Переводите данные в локальное время только при отображении.
Вже доступно! Дослідження Telegram за 2025 — головні інсайти року 
