SQL Academy: всё о реляционных БД и SQL
8.65K subscribers
142 photos
1 video
1 file
25 links
По всем вопросам и коммерческим предложениям писать @LadanovNick

Купить рекламу: https://telega.in/c/sqlacademyofficial

Чат студентов SQL Academy
https://t.me/sqlacademyorg
Download Telegram
Как устроены хранилища MySQL: InnoDB vs MyISAM vs MEMORY 🏗️
Когда вы создаёте таблицу в MySQL, у неё есть так называемый движок хранения (storage engine). Это то, как MySQL управляет данными внутри таблицы: где их хранить, как их обновлять и как быстро к ним обращаться.

В MySQL есть несколько движков, но самые популярные:
🔹InnoDB — стандартный и самый надёжный вариант 🔥
🔹MyISAM — старый, быстрый, но без транзакций
🔹MEMORY — для супербыстрых временных таблиц 🧠

Давайте разберёмся, в чём их различия и когда какой использовать.

🔥 InnoDB — современный стандарт
InnoDB — это основной и рекомендуемый движок в MySQL. Он поддерживает транзакции, обеспечивает целостность данных и отлично подходит для больших нагрузок.

Основные особенности:
Поддержка транзакций — если что-то пошло не так, можно откатить изменения (ROLLBACK).
Поддержка внешних ключей — можно строить связи между таблицами (FOREIGN KEY).
Блокировка на уровне строк — если обновляется одна строка, другие остаются доступными.
Хранит данные в кластеризованном индексе — быстрее работает с PRIMARY KEY.

🛠 Где используется?
🔹В интернет-магазинах, CRM-системах, SaaS-проектах.
🔹Там, где важна целостность данных (например, банковские операции).
🔹Если нужна высокая скорость чтения и записи одновременно.

MyISAM — быстрый, но устаревший
MyISAM был основным движком в MySQL до версии 5.5. Он работает быстрее, но не поддерживает транзакции.

Основные особенности:
Очень быстрое чтение данных — идеален для аналитики.
Легче в администрировании — структура проще, чем у InnoDB.

Минусы:
🔹 Нет транзакций — если сервер упадёт во время записи, можно потерять данные.
🔹 Блокировка на уровне таблицы — если обновляется одна строка, вся таблица заблокирована.
🔹 Нет внешних ключей — нельзя строить сложные связи между таблицами.

🛠 Где используется?
🔹В системах, где важнее скорость чтения, а не записи (например, блоги, статистика).
🔹В проектах, где потеря данных не критична.
🔹Сейчас почти не используется, потому что InnoDB во многом его заменил.

🧠 MEMORY — супербыстрые временные таблицы
MEMORY хранит данные не на диске, а в оперативной памяти (RAM). Это делает его невероятно быстрым, но с одной важной особенностью: при перезапуске MySQL все данные исчезают!

Основные особенности:
Молниеносная скорость — идеально для временных данных.
Нет нагрузки на диск — всё работает в оперативке.
Отлично подходит для кэша — можно временно хранить результаты сложных запросов.

Минусы:
🔹Данные исчезают при рестарте MySQL.
🔹Не поддерживает TEXT и BLOB (нельзя хранить большие строки и файлы).
🔹Ограничение на размер — зависит от объёма оперативной памяти.

🛠 Где используется?
🔹Временные таблицы для отчётов.
🔹Кэширование данных, которые часто запрашиваются.
🔹Для промежуточных вычислений.

🏆 Какой движок выбрать?
🔹Если не знаете, что выбрать — берите InnoDB. Он безопасный, поддерживает транзакции и гибкий.
🔹MyISAM хорош только для устаревших проектов, но сейчас почти не используется.
🔹MEMORY подходит для временных данных, если они не должны сохраняться после перезапуска сервера.

Теперь вы знаете, как устроены хранилища MySQL и какой движок выбрать для своего проекта! 🚀
1👍153🔥3🤩1
Event Scheduler в MySQL: что это и как его использовать?
Event Scheduler — это механизм MySQL, который позволяет автоматически выполнять SQL-запросы в заданное время или с определенной периодичностью. Это аналог CRON, но работает внутри MySQL.

📌 Применение Event Scheduler:
Очистка старых данных (логов, временных записей) 🗑️
Автоматическое обновление данных 📊
Генерация отчетов и статистики 📈
Автоматическое создание резервных копий 💾

🔹 Как включить Event Scheduler?
По умолчанию Event Scheduler может быть выключен. Проверить его статус можно так:

SHOW VARIABLES LIKE 'event_scheduler';


Если он OFF, включаем его:

SET GLOBAL event_scheduler = ON;


🔹 Создание событий в MySQL
Простейший пример однократного события:

CREATE EVENT delete_old_logs
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR
DO
DELETE FROM logs WHERE created_at < NOW() - INTERVAL 30 DAY;

💡 Это событие запустится один раз через 1 час и удалит старые логи.

🔹 Периодическое выполнение событий
Если нужно выполнять задачу регулярно, используем EVERY:

CREATE EVENT update_stats
ON SCHEDULE EVERY 1 DAY
DO
UPDATE statistics SET last_updated = NOW();

Это событие будет обновлять таблицу каждый день.

🔹 Управление событиями
📌 Посмотреть все события в базе:

SHOW EVENTS;

📌 Остановить событие:

ALTER EVENT update_stats DISABLE;

📌 Запустить снова:

ALTER EVENT update_stats ENABLE;

📌 Удалить событие:

DROP EVENT delete_old_logs;


🔹 Что важно помнить?
1️⃣ События привязаны к БД, где они созданы.
Если сделать USE другая_БД, событие не будет видно.
2️⃣ События выполняются от имени пользователя, который их создал, с его правами.
3️⃣ Если сервер MySQL перезапустится, события сохраняются, но Event Scheduler может выключиться — проверяйте его статус!
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍184🔥2
Как хранить бинарные данные (BLOB) в MySQL? 🔒
BLOB (Binary Large Object) — это способ хранить двоичные данные (файлы, изображения, документы и т.д.) прямо в базе MySQL. Однако есть нюансы, которые важно учитывать.

🔹 Виды BLOB:
1️⃣ TINYBLOB — до 255 байт
2️⃣ BLOB — до ~65 КБ
3️⃣ MEDIUMBLOB — до ~16 МБ
4️⃣ LONGBLOB — до ~4 ГБ

🔹 Когда стоит использовать BLOB?
Для небольших файлов (например, аватары пользователей)
Когда важно хранить всё в одной базе (цельность и транзакции)
При отсутствии внешнего хранилища

⚠️ Большие файлы (видео, большие документы) лучше хранить вне БД, а в базе — только пути к ним.

🔹 Пример таблицы:

CREATE TABLE user_avatars (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
avatar MEDIUMBLOB NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

🔹 Вставка данных:

INSERT INTO user_avatars (user_id, avatar)
VALUES (123, LOAD_FILE('/path/to/avatar.png'));


Или передача через параметризованный запрос из приложения (PHP/Python), где двоичные данные вставляются напрямую.

🔹 Ограничения и подводные камни:
1️⃣ Большие BLOB замедляют запросы и увеличивают размер бэкапа
2️⃣ Нужны корректные настройки (max_allowed_packet, innodb_log_file_size и др.)
3️⃣ При блокировке таблицы с большим BLOB транзакции могут зависать дольше
4️⃣ Бэкапы и восстановление займут больше времени

🔹 Рекомендации:
1️⃣ Для больших файлов — отдельное хранилище (например, S3), а в БД только ссылки
2️⃣ При использовании BLOB — ограничивать максимальный размер файлов
3️⃣ Следить за правами (функция LOAD_FILE требует соответствующих привилегий)
4️⃣ Использовать кэширование (CDN), если часто выгружаете большие файлы
👍153🔥3
Часовые пояса в MySQL: как не запутаться и правильно хранить время? 🕒
Представьте: вы сохранили в базе данных время, когда пользователь отправил сообщение, а позже оказалось, что для одного пользователя это "11:00", а для другого – "14:00". Почему так происходит? Дело в часовых поясах.

🌍 Что такое UTC?
UTC (Coordinated Universal Time) – это универсальное мировое время, от которого отсчитываются все часовые пояса планеты. Это удобная точка отсчёта, которая не зависит от часового пояса вашего сервера или пользователей.

Почему это важно?
Если вы будете хранить данные в разных часовых поясах (например, местное время каждого пользователя), в базе данных появится хаос. Станет сложно сравнивать даты и время, а также понять, когда именно произошло событие.

🔹 Как правильно хранить время в MySQL?
Правило простое:
1️⃣ Храните всё время в базе данных в формате UTC.
2️⃣ Конвертируйте его в местное время только тогда, когда показываете пользователю.

Для хранения времени в MySQL можно использовать разные типы данных. Наиболее распространены TIMESTAMP и DATETIME. Давайте разберёмся, какой тип лучше выбрать.

🔹 Разница между типами TIMESTAMP и DATETIME
🔘 TIMESTAMP автоматически сохраняет данные в UTC и при чтении может автоматически конвертировать в текущий часовой пояс сервера.
🔘 DATETIME не имеет привязки к часовому поясу и хранит время ровно так, как вы его сохранили. Поэтому при использовании DATETIME нужно всегда явно указывать UTC-время.

🔹 Пример работы с UTC:
Сохраняем текущее время в UTC:

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️⃣ Переводите данные в локальное время только при отображении.
1👍205🔥5
NULL в SQL: зачем знать и как работать

Что такое NULL?
Это специальное значение «неизвестно / отсутствует». Оно не равно нулю, пустой строке или 0 — это «ничего».

Почему NULL может создать проблемы
🔹 Сравнение = 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 становится предсказуемой и безопасной. 🚀
132👍23🔥9
CHECK-ограничения в MySQL: валидация прямо в таблице
Когда вы хотите убедиться, что в таблицу попадают только корректные данные — не всегда нужно писать сложную логику в приложении. Иногда достаточно встроенного механизма MySQL: ограничения CHECK.

🔹 Что такое CHECK?
Это правило, которое автоматически проверяет значение в колонке при вставке (INSERT) или обновлении (UPDATE). Если правило нарушено — операция отменяется с ошибкой.
Пример:

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).
🔹Когда хотите, чтобы в таблице «по умолчанию» всегда были только корректные данные.
🔹Чтобы упростить валидацию и сделать БД самодостаточной (например, при работе с внешними источниками данных).
👍242🔥2
AUTO_INCREMENT в MySQL: как работает, можно ли переопределить и сбросить счётчик? 🔢

💬 Почему важно
Вопрос про AUTO_INCREMENT регулярно всплывает на собеседованиях: «почему ID прыгают через 5?», «как начать нумерацию с 1000?», «можно вернуть счётчик обратно?». Разберёмся на практике.

🔹 Что делает AUTO_INCREMENT
При каждой вставке (`INSERT`) MySQL берёт текущее значение счётчика, увеличивает его на 1 и записывает в колонку.

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 — нормальное поведение.
🔥154👍2🗿1
🚀 Мы запускаем Python Academy! 🚀
Python — это ключ к аналитике, автоматизации и быстрой разработке. Мы собрали материал, который поможет заложить прочный фундамент и сразу перейти к практике. 🐍💼

Что внутри:
1️⃣ стартуем в формате «Ранний доступ» — уроки уже открыты и будут регулярно пополняться;
2️⃣ практические задания после каждого модуля, чтобы теория превращалась в навык;
3️⃣ приятные сюрпризы и новые фичи впереди! 🎁

💡 Основной контент и задания — бесплатно.
Учитесь без ограничений и прокачивайте навыки вместе с нами.

Хотите ещё глубже?
Подключайте Премиум, чтобы решать задачи с реальных собеседований в топ-IT-компаниях и получать расширенные возможности курса.

🔥 В честь запуска действует скидка 70 % по промокоду IAMFIRST.

Перейти к курсу Python Academy → https://python-academy.org/

Чтобы не пропустить обновления и бонусы, присоединяйтесь к нашему Telegram-каналу 👉 https://t.me/pythonacademyofficial

Спасибо, что растёте вместе с SQL Academy — и теперь с Python Academy. Ваши успехи — наша радость! 💙
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5914👍5
CASE в ORDER BY: сортируем по условию 🔄
Иногда нужно показать записи не просто «по алфавиту» или «по дате», а сначала важные, потом второстепенные. В MySQL (и в SQL вообще) это делается через выражение CASE прямо в ORDER BY.

💡 Зачем знать?
Вопрос часто звучит на собеседованиях: «Как вывести VIP‑клиентов сверху, а остальных — по имени?» Умение написать условную сортировку показывает, что вы понимаете SQL‑движок, а не только копируете примеры.

🔹 Базовый пример: VIP‑клиенты выше остальных

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‑ами и индексацией при больших объёмах.
🔥25👍159
LIMIT + OFFSET и «вечная прокрутка»: как делают Instagram-ленты 📜➡️

Зачем знать?
На собеседованиях часто просят: «Сделай API, чтобы лента грузилась порциями». Понимание пагинации важно и для новичков — вы сможете писать быстрые, масштабируемые запросы.

────────────────────────
1️⃣ Классический способ — LIMIT + OFFSET

-- страница 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. Результат удивит! 🚀
👍26🔥1815
WINDOW-функция NTILE(): делим пользователей на квартели 📊
На собеседованиях любят задачу «раскидай пользователей по квартилям по количеству покупок».
NTILE() решает её одной строкой, без подзапросов и циклов в приложении.

1️⃣ Что такое NTILE()
Оконная функция, которая разбивает отсортированный набор строк на N равных «коробок» (tiles).

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 и любой сортировкой.
🔹Незаменим при сегментации пользователей, товаров или чеков.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥276👍5
SEQUENCE (MySQL 8.0.29+) — современная альтернатива AUTO_INCREMENT

💡 Зачем знать
🔹 На собеседованиях всё чаще спрашивают: «Чем SEQUENCE отличается от AUTO_INCREMENT и зачем он нужен?»
🔹 В проде помогает, когда один источник чисел требуется сразу нескольким таблицам или когда нельзя допускать длинной блокировки PRIMARY KEY.

1️⃣ Что такое SEQUENCE?
Это отдельный объект в схеме, хранящий текущее значение счётчика.
🔹 Не привязан к конкретной таблице.
🔹 Умеет шаг ( INCREMENT ), начальное значение (START).
🔹 Получить число можно в любом запросе: NEXT VALUE FOR seq_name.


-- создаём последовательность с шага 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 = гибкий, «шарящийся» счётчик, который:
🔹Позволяет одной командой нумеровать разные таблицы.
🔹Даёт точный контроль над шагом и стартом.
🔥125👍3
SUBSTRING() / LEFT() / RIGHT() — извлекаем части строки ✂️

Зачем нужно
Чистка и разбор данных — частая задача аналитика. Извлечь код страны из телефона, отделить год из даты в тексте, взять первые 3 буквы артикулы — всё это делается прямо в SQL, без кода на стороне приложения.

1️⃣ SUBSTRING() — получить подстроку

-- с 3-й позиции длиной 4 символа
SELECT SUBSTRING('ABCDEF', 3, 4);
-- результат: 'CDEF'

-- начиная с 2-го символа до конца
SELECT SUBSTRING('123456', 2);
-- результат: '23456'

-- отрицательный индекс = отсчёт от конца
SELECT SUBSTRING('hello', -2);
-- результат: 'lo’


2️⃣ LEFT(str, N) — «первые N символов»

SELECT LEFT('8901234567', 2);
-- '89' (код страны)


3️⃣ RIGHT(str, N) — «последние N символов»

SELECT RIGHT('2025-04-30', 2);
-- '30' (день)


💡 Практический пример: разбираем телефон

SELECT
phone,
LEFT(phone, 2) AS country_code,
-- первые 2 цифры
SUBSTRING(phone, 3, 3) AS city_code,
-- далее 3 цифры
RIGHT(phone, 4) AS last_digits
-- последние 4
FROM contacts;


⚠️ Подводные камни
🔹 Нумерация с 1: первый символ — позиция 1, а не 0.
🔹 NULL → NULL: любое из функций вернёт NULL, если аргумент NULL — учитывайте в отчётах.

Итог
🔹SUBSTRING — универсальный нож: позиция + длина.
🔹LEFT / RIGHT — быстрый способ взять начало или конец строки.
👍2010🔥1👌1
DATE_ADD / DATE_SUB — прибавляем дни, месяцы, годы 📅

Зачем знать
Практически каждый отчёт или запрос «за последние N дней» использует именно эти функции. Умение правильно прибавлять/вычитать интервалы избавит от ошибок с високосными годами и разной длиной месяцев.

1️⃣ Синтаксис

DATE_ADD(date, INTERVAL N unit)
DATE_SUB(date, INTERVAL N unit)

unit бывает: SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR и их комбинации (например, DAY_MICROSECOND).

2️⃣ Простые примеры


-- дата через 7 дней
SELECT DATE_ADD('2025-05-01', INTERVAL 7 DAY); -- 2025-05-08

-- месяц назад
SELECT DATE_SUB(NOW(), INTERVAL 1 MONTH);

-- +2 часа к текущему времени
SELECT DATE_ADD(NOW(), INTERVAL 2 HOUR);


3️⃣ Кейс: отчёт «за последние 30 дней»

SELECT *
FROM sales
WHERE sale_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY);

👉 CURDATE() = 00:00:00 текущего дня, поэтому попадут 30 «полных» дней.

4️⃣ Прибавляем месяцы правильно
DATE_ADD учитывает разную длину месяцев и високосные годы:

-- 31 января + 1 месяц → 28 февраля (или 29 в високосный год)
SELECT DATE_ADD('2024-01-31', INTERVAL 1 MONTH); -- 2024-02-29


⚠️ Подводные камни
1. Точность NOW(): если нужен только день без времени — используйте CURDATE().
2. Интервалы с недельной гранулярностью: INTERVAL 1 WEEK = 7 дней, не календарная неделя.
Please open Telegram to view this post
VIEW IN TELEGRAM
18🔥9👍4
ELT() и FIELD() — превращаем числовой код в текст без JOIN 🏷️

Зачем нужно
Частый кейс: в таблице хранится числовой статус (1 = “pending”, 2 = “done”, 3 = “failed”).
Обычно делают JOIN с маленьким справочником, но MySQL умеет отдать текст прямо в SELECT — скомпилируем «карманный словарь» функциями FIELD() + ELT().

1️⃣ FIELD(needle, list…) — возвращает позицию элемента

SELECT FIELD(3, 1,2,3,4);
-- Результат: 3
SELECT FIELD('b', 'a','b','c');
-- Результат: 2


2️⃣ ELT(index, list…) — берёт элемент по номеру

SELECT ELT(3, 'pending','done','failed','unknown');
-- Результат: 'failed'


💡 Объединяем: число → позиция → текст

SELECT
order_id,
status_code,
ELT(
FIELD(status_code, 1,2,3,4),
'pending','in_progress','done','failed'
) AS status_text
FROM orders;

🔹 FIELD находит позицию status_code в списке кодов,
🔹 ELT вытаскивает строку из второго списка той же длины.

3️⃣ Удобный вариант через CASE?
Да, но ELT/FIELD короче, особенно для 5-10 значений.

-- аналог на CASE (длиннее)
CASE status_code
WHEN 1 THEN 'pending'
WHEN 2 THEN 'in_progress'
WHEN 3 THEN 'done'
WHEN 4 THEN 'failed'
ELSE 'unknown'
END


4️⃣ Подводные камни ⚠️
🔹 Если FIELD вернёт 0 (код не найден) — ELT() даст NULL.
🔹Порядок важен: списки в FIELD и ELT должны совпадать по длине и позиции.

Итог
🔹FIELD() находит позицию кода, ELT() возвращает строку по этой позиции.
🔹Для 3-5 статусов — самый быстрый способ «раскрасить» отчёт без JOIN и CASE.
🔥205👍3🥰1🙏1
🎉 PostgreSQL теперь с нами! 🐘🎉

Мы расширили движок курса — добавили полную поддержку PostgreSQL.
Теперь все уроки, задачи и автотесты работают как на MySQL, так и на PostgreSQL. Выбирайте любимый диалект — знание SQL теперь прокачивается вдвойне! 🚀

Что нового:
1️⃣ Двойной режим — переключайтесь между MySQL PostgreSQL в один клик.
2️⃣ Все практические задачи проверяются в обоих СУБД, а значит больше гибкости и реальной пользы.
3️⃣ Обновлённые подсказки и примеры с учётом синтаксиса Postgres.

💡 Базовый контент и задания по-прежнему бесплатны.

Спасибо, что растёте вместе с SQL Academy. Теперь на MySQL и PostgreSQL! 💙
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥119👍3118
UUID() и UUID_SHORT() — генерируем уникальный ID без сервера приложений 🎲

Зачем знать
🔹Иногда нужно создать уникальный первичный ключ на стороне БД, а не приложения.

1️⃣ UUID() — стандартный 128-битный идентификатор

SELECT UUID();
-- '550e8400-e29b-41d4-a716-446655440000'

🔹36 символов (32 шестн. + 4 дефисa).
🔹Практически нулевая вероятность коллизии.
🔹Минусы: длинная строка → индекс ~4× крупнее, хуже кэшируется.

2️⃣ UUID_SHORT() — компактный 64-битный вариант

SELECT UUID_SHORT();
-- 281474976710661

🔹Возвращает BIGINT UNSIGNED (до 18 цифр).
🔹Формируется как: (server_id << 48) + (unix_ts << 24) + increment.
🔹Удобен для числовых PK 👉 индекс меньше, быстрее сортировка.
🔹Риск коллизии есть при одинаковом server_id (на реплике проверьте server_id!).

3️⃣ Практический пример: таблица заказов

CREATE TABLE orders (
id CHAR(36) DEFAULT (UUID()) PRIMARY KEY,
user_id INT,
total DECIMAL(10,2)
);


Итог
🔹UUID() — гарантированная уникальность, но тяжёлый индекс.
🔹UUID_SHORT() — компактный числовой ID, если контролируете server_id.
🔹Оба способа позволяют генерировать ключ прямо в MySQL без внешнего сервиса или кода приложения.
20👍5
Задача из собеседования (Cian, middle): считаем, сколько раз издавалось произведение 📚

Схема:
🔹Books(id, title) — произведение
🔹BookEditions(id, book_id, publish_year, pages) — издание произведения
🔹BookCopies(id, edition_id, inventory_num) — экземпляр издания

🎯 Формулировка
Найти произведения, которые издавались более 5 раз. Вывести только название произведения (title).

🧪 Сначала решите сами!
Откройте задачу и попробуйте написать запрос без подсказок:
👉 https://sql-academy.org/ru/trainer/tasks/125

Разбор ——
Как подойти шаг за шагом:
1️⃣ Что нужно посчитать?
Количество изданий на каждое произведение → считаем строки в BookEditions по book_id.

2️⃣ Где лежит название?
В Books. Значит, к агрегату нужно присоединить Books по Books.id = BookEditions.book_id.

3️⃣ По чему группировать?
По уникальному идентификатору книги (и для вывода — по названию): GROUP BY b.id, b.title.

4️⃣ Как отфильтровать только те, у кого >5 изданий?
Через HAVING, потому что фильтруем по агрегату (COUNT).

Правильный ответ

SELECT b.title
FROM BookEditions e
JOIN Books b ON b.id = e.book_id
GROUP BY b.id, b.title
HAVING COUNT(e.id) > 5;


На что обратить внимание ❗️
🔹Группируем по b.id, b.title, а не только по b.title: два разных произведения могут теоретически иметь одинаковое название; по id безопаснее.

А если в условии скрыт нюанс «издавалось в разные годы»?
Иногда собеседующий подразумевает уникальные годы издания. Тогда используйте COUNT(DISTINCT e.publish_year)

SELECT b.title
FROM BookEditions e
JOIN Books b ON b.id = e.book_id
GROUP BY b.id, b.title
HAVING COUNT(DISTINCT e.publish_year) > 5;
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32🔥109👏2👌1
🌟 Считаем возраст человека правильно 🌟

Вы когда-нибудь задумывались, как правильно вычислить возраст человека в SQL? 🤔 Может показаться, что всё просто: возьмём текущий год, отнимем год рождения, и вуаля! Но ой, подстерегает соблазн ошибки! 😈

🚫 Неправильный подход:

SELECT YEAR(NOW()) - YEAR('2003-07-03 14:10:26');

Проблема такого подхода в том, что он не учитывает был ли день рождения у данного человека в этом году или ещё нет. То есть, если на момент запроса уже наступило 3-е июля (07-03), то человек отпраздновал свой день рождения и ему уже 21 лет, иначе ему по-прежнему 20 года. Разница функций YEAR тут будет бесполезна — в обоих случаях она даст 21 лет.

🤔 Что же делать? Мы могли бы пойти по пути вычисления разницы в днях и деления на 365:

SELECT FLOOR(DATEDIFF(NOW(), '2003-07-03 14:10:26') / 365);

Это лучше, но всё же не совсем точно из-за наличия високосных годов. 📆

Правильный подход с TIMESTAMPDIFF:

TIMESTAMPDIFF(YEAR, '2003-07-03 14:10:26', NOW());

Используя функцию TIMESTAMPDIFF, мы получаем точную разницу в годах между двумя датами, учитывая все нюансы календаря! 🎯

Так что, когда дело доходит до вычисления возраста, доверяйте TIMESTAMPDIFF — и ваши расчёты будут точны как никогда! 🚀💡
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥43👍154
ClickHouse vs Greenplum: что выбрать для аналитики?
📅 10 сентября | 20:00 мск | бесплатно

На вебинаре разберёмся, какая СУБД лучше подойдёт для ваших задач:
•Архитектура, производительность и масштабируемость CH и Greenplum
•Различия в хранении и обработке данных
•Как базы показывают себя в OLAP, ETL и ad-hoc аналитике
•Реальные кейсы внедрения и оптимизации

После урока вы сможете:
•Выбирать оптимальное решение под разные сценарии
•Оценивать производительность и удобство работы
•Понимать архитектурные плюсы и минусы каждой СУБД

💥 Участие бесплатное — регистрируйтесь и приходите: [https://otus.pw/SoTpx/?erid=2W5zFJEjnp8

Бесплатное занятие приурочено к старту курса ClickHouse. После обучения вы научитесь быстро и эффективно настраивать БД, работать с ее продвинутыми функциями, интегрировать с другими системами и выбирать оптимальные решения для ваших данных.

Реклама. ООО "ОТУС ОНЛАЙН-ОБРАЗОВАНИЕ". ИНН 9705100963.
4👍1🔥1