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

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

Чат студентов SQL Academy
https://t.me/sqlacademyorg
Download Telegram
Подзапросы (Subqueries) в SQL ⭐️
Подзапрос (Subquery) — это запрос внутри другого запроса. Он выполняется первым и передает результат основному запросу.

🔍 Некоторые виды подзапросов:

🔹 В SELECT — используется для получения дополнительных данных:

SELECT name,
(SELECT COUNT(*) FROM orders WHERE customer_id = customers.id) AS order_count
FROM customers;


🔹 В WHERE — используется для фильтрации:

SELECT name FROM products
WHERE price > (SELECT AVG(price) FROM products);


🔹 В FROM — подзапрос как временная таблица:

SELECT avg_price FROM
(SELECT AVG(price) AS avg_price FROM products) AS temp;
Please open Telegram to view this post
VIEW IN TELEGRAM
👍50❤14🔥7
🌟 Считаем возраст человека правильно 🌟

Вы когда-нибудь задумывались, как правильно вычислить возраст человека в 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
👍78❤20🔥14👎2
CASE WHEN: Условные выражения в SELECT ✨
Иногда нужно «разделить» данные по условию прямо внутри запроса, не меняя структуру таблицы и не создавая дополнительные поля. И тут на помощь приходит CASE WHEN!

Что это такое?
CASE WHEN — это оператор, который позволяет писать логику «если… то… иначе…» в SELECT, WHERE, ORDER BY и других частях SQL-запроса.

Пример использования
Допустим, у нас есть таблица products с колонками: name (название товара) и price (цена). Нужно вывести для каждого товара метку, говорящую о ценовом уровне:

SELECT
name,
price,
CASE
WHEN price < 50 THEN 'Дешево'
WHEN price BETWEEN 50 AND 100 THEN 'Средне'
ELSE 'Дорого'
END AS price_label
FROM products;


Что тут происходит?
1. CASE проверяет цену в каждой строке:
🔹Если цена < 50, то подставляем строку 'Дешево'.
🔹Если цена между 50 и 100, получаем 'Средне'.
🔹И во всех остальных случаях — 'Дорого'.
2. Результат помещаем в новый столбец — price_label.

Зачем это нужно?
🔹Быстрая категоризация данных без изменений в самой таблице.
🔹Условная логика в запросах: выводим разные значения в зависимости от условий.
🔹Упрощает отчёты и помогает строить «человеко-понятные» выводы данных.

Попрактиковаться в написание запроса с использование CASE оператора можно в заданиях для самопроверки по ссылке
https://sql-academy.org/ru/guide/case-expression
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥40👍11❤5💘1
Генерируемые столбцы (Generated Columns) в MySQL — это столбцы, значение которых вычисляется автоматически на основе других столбцов в таблице. Это очень удобно, когда нужно всегда иметь «актуальную» производную величину (например, итоговую стоимость, индекс массы тела, коэффициент, результат арифметических операций и т.д.), при этом не писать лишний код или триггеры. 🔮

В чём суть? ⚙️
Вместо того чтобы хранить формулу в приложении, мы «встраиваем» логику прямо в таблицу. При обновлении данных в базовых полях вычисляемый столбец тоже меняется.

Два вида генерируемых столбцов:
🔹VIRTUAL (виртуальный)
Значение вычисляется «на лету» при выборе из таблицы и не хранится физически.
🔹STORED (сохранённый)
Значение сохраняется в таблице и обновляется при изменении данных. Имеет свой физический размер, но и доступ к нему быстрее (особенно при частых запросах).

Пример создания таблицы с генерируемым столбцом
Предположим, у нас есть таблица для хранения товаров. В ней мы хотим автоматически высчитывать итоговую стоимость (total_cost) как произведение цены price на количество quantity.

CREATE TABLE items (
id INT AUTO_INCREMENT PRIMARY KEY,
price DECIMAL(10,2),
quantity INT,
-- Генерируемый столбец, который будет хранить итоговую стоимость
total_cost DECIMAL(10,2) AS (price * quantity) STORED
);

🔹AS (price * quantity) — формула, по которой будет вычисляться столбец total_cost.
🔹STORED означает, что итоговая стоимость будет записываться в таблицу и быстренько выдаваться из физического хранения.
🔹При вставке или обновлении записи MySQL сама позаботится о том, чтобы total_cost пересчитался.

А если сделать виртуальный столбец? 💡
Чтобы не занимать место на диске, используем VIRTUAL:

CREATE TABLE items_virtual (
id INT AUTO_INCREMENT PRIMARY KEY,
price DECIMAL(10,2),
quantity INT,
total_cost DECIMAL(10,2)
AS (price * quantity) VIRTUAL
);

🔹 Поле total_cost не хранится физически. Оно вычисляется при каждом чтении строки.
🔹При частом чтении большого количества строк запросы могут работать чуть медленнее, ведь каждый раз идёт пересчёт.
🔹Зато при редких выборках и частых обновлениях виртуальный столбец может быть выгоднее, так как не используется дополнительное место на хранение.

Индексация и ограничения 🔑
🔹STORED столбцы можно индексировать, а значит, быстрее искать по ним.
🔹VIRTUAL столбцы индексировать нельзя

Подводим итоги 🏆
🔹Генерируемые столбцы избавляют от необходимости самостоятельно следить за обновлением вычисляемых значений.
🔹Выбор между VIRTUAL и STORED зависит от частоты чтения/записи, а также необходимости индексации.
🔹Такой подход упрощает архитектуру БД и код приложения: формула одна, лежит «рядом с данными», все операции прозрачны и понятны.

Если вам нужно ускорить расчёт «сложной формулы» или хранить результат для аналитических задач — выбирайте STORED. Если поле лишь вспомогательное, используйте VIRTUAL.
👍21❤10🔥8
📊 Медиана в SQL: почему она важнее среднего?
Среднее арифметическое (AVG()) часто используется для анализа данных, но иногда оно даёт искажённые результаты.

Например, представьте, что у нас есть 11 сотрудников:
🔹10 человек получают 40 000 💰
🔹1 человек получает 5 000 000 💰

Если посчитать среднюю зарплату, получится:

SELECT AVG(salary) FROM salaries;

👉 Средняя зарплата: 490 000

Но разве это отражает реальность? Большинство сотрудников получают 40 000, а один выбивает статистику.

✅ Медиана (MEDIAN) — это значение, которое делит отсортированные данные пополам: половина значений меньше, половина больше. В данном случае медиана = 40 000, что лучше отражает ситуацию.

📌 Как посчитать медиану в MySQL?
В отличие от AVG(), в MySQL нет встроенной функции MEDIAN(), но её можно посчитать разными способами.

1️⃣ Метод через ORDER BY LIMIT (для нечётного количества записей)
Если записей нечётное количество, медиана — это значение в центре списка:

SELECT salary FROM salaries
ORDER BY salary
LIMIT 1 OFFSET (SELECT COUNT(*) FROM salaries) / 2;

⚠️ Минус: Работает только для нечётного количества записей.

2️⃣ Метод через ROW_NUMBER() (универсальный способ)
Этот метод работает и для чётного, и для нечётного количества записей:

WITH ordered AS (
SELECT salary, ROW_NUMBER() OVER (ORDER BY salary) AS rn,
COUNT(*) OVER () AS total
FROM salaries
)
SELECT AVG(salary) FROM ordered
WHERE rn IN (FLOOR((total + 1) / 2), CEIL((total + 1) / 2));

🔹 Мы нумеруем строки с ROW_NUMBER().
🔹 Выбираем одно или два центральных значения.
🔹 Берём их среднее (AVG()) — для чётного числа записей берётся среднее двух центральных значений.

🏆 Итог
✅ Медиана полезнее среднего арифметического, когда есть выбросы в данных.
✅ В MySQL нет встроенной MEDIAN(), но её можно вычислить с ROW_NUMBER().
✅ Используйте медиану, если хотите честную аналитику! 🚀
Please open Telegram to view this post
VIEW IN TELEGRAM
👍42❤22🔥4🤩2
🔐 Как хранить пароли в базе данных? 🤔
Если вы храните пароли в базе как обычный текст — вы в опасности! 🚨 Это одна из самых больших ошибок в безопасности. Давайте разберёмся, как правильно хранить пароли в MySQL, чтобы ваши пользователи были защищены! 🔒

🚫 Плохие способы хранения паролей
1. Хранение пароля в открытом виде:

INSERT INTO users (username, password) VALUES ('kolya', 'mypassword123');

👉 Это ужасная практика! Если база данных утечёт, злоумышленники сразу получат доступ ко всем аккаунтам! 😱

2. Использование MD5/SHA1:

INSERT INTO users (username, password) VALUES ('kolya', 'bff149a0b87f5b0e00d9dd364e9ddaa0');

👉 MD5 и SHA1 устарели и больше не считаются безопасными! Их можно взломать с помощью радужных таблиц (rainbow tables) 🌈 или перебора (brute force). 🛑

✅ Хороший способ: хеширование паролей 🔒
Лучший способ защитить пароли — использовать bcrypt или Argon2. В MySQL есть встроенная функция PASSWORD(), но её не рекомендуют использовать, так как она небезопасна!
😕 Вместо этого используем bcrypt. 🎉

🔹 Как хешировать пароль в Python (рекомендуется)

import bcrypt

password = b'mypassword123'
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
print(hashed_password)

🔹 Как хранить хеш в базе:

CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(255) UNIQUE,
password_hash VARCHAR(255)
);

INSERT INTO users (username, password_hash) VALUES ('kolya', '$2y$10$abcdefghijABCDEFGHIJ1234567890');

👉 Здесь password_hash хранит уже захешированный пароль, а не сам пароль! 🔐

🔓 Как проверить пароль при входе?
В Python:

if bcrypt.checkpw(b'mypassword123', stored_hash.encode()):
print('Пароль верный! ✅')
else:
print('Неправильный пароль! ❌')


🛡 Итог
❌ НЕ храните пароли в открытом виде! 🚫
❌ НЕ используйте MD5 или SHA1! ❗️
✅ ИСПОЛЬЗУЙТЕ bcrypt или Argon2! 🔥
✅ ИСПОЛЬЗУЙТЕ bcrypt.hashpw() и bcrypt.checkpw() в Python! 🏆

Следуя этим правилам, вы защитите данные пользователей и сделаете свой сервис безопаснее! 🔒💪
Please open Telegram to view this post
VIEW IN TELEGRAM
👍36❤12🔥8
Как устроены хранилища 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👍15❤3🔥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👍18❤4🔥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), если часто выгружаете большие файлы
👍15❤3🔥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👍20❤5🔥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 становится предсказуемой и безопасной. 🚀
1❤35👍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).
🔹Когда хотите, чтобы в таблице «по умолчанию» всегда были только корректные данные.
🔹Чтобы упростить валидацию и сделать БД самодостаточной (например, при работе с внешними источниками данных).
👍24❤2🔥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 — нормальное поведение.
🔥15❤4👍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
🔥59❤14👍6
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‑ами и индексацией при больших объёмах.
🔥26👍15❤9
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🔥19❤16
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
🔥27❤6👍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 = гибкий, «шарящийся» счётчик, который:
🔹Позволяет одной командой нумеровать разные таблицы.
🔹Даёт точный контроль над шагом и стартом.
🔥12❤5👍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 — быстрый способ взять начало или конец строки.
👍21❤10🔥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🔥10👍4