Мини-квиз по дате и времени в PostgreSQL
1️⃣ Вопрос 1: Парсинг даты одной функцией
a) 16/08/2024
b) 2024-16-08
c) 2024-08-16
d) Ошибка парсинга
Правильный ответ:c) 2024-08-16
2️⃣ Вопрос 2: Разница в днях
a) 13
b) 14
c) 15
d) 16
Правильный ответ:b) 14
3️⃣ Вопрос 3: Номер недели (ISO) для актуальной даты
a) 0
b) 1
c) 52
d) 53
Правильный ответ:b) 1
4️⃣ Вопрос 4: Переход через полночь
a) 2024-08-16 25:00:00
b) 2024-08-17 00:00:00
c) 2024-08-17 01:00:00
d) 2024-08-16 23:02:00
Правильный ответ:c) 2024-08-17 01:00:00
1️⃣ Вопрос 1: Парсинг даты одной функцией
SELECT to_date('16-08-2024', 'DD-MM-YYYY');
a) 16/08/2024
b) 2024-16-08
c) 2024-08-16
d) Ошибка парсинга
Правильный ответ:
2️⃣ Вопрос 2: Разница в днях
SELECT DATE '2024-03-15' - DATE '2024-03-01';
a) 13
b) 14
c) 15
d) 16
Правильный ответ:
3️⃣ Вопрос 3: Номер недели (ISO) для актуальной даты
SELECT EXTRACT(WEEK FROM DATE '2026-01-01');
a) 0
b) 1
c) 52
d) 53
Правильный ответ:
4️⃣ Вопрос 4: Переход через полночь
SELECT TIMESTAMP '2024-08-16 23:00:00' + INTERVAL '2 hours';
a) 2024-08-16 25:00:00
b) 2024-08-17 00:00:00
c) 2024-08-17 01:00:00
d) 2024-08-16 23:02:00
Правильный ответ:
🔥32❤12👍9👨💻7
Всем привет! На канале Data analysis | Анализ данных | DA разбираются темы и вопросы, которые должен знать аналитик данных, имеющий опыт 3-6 лет. Все темы взяты из реальных вакансий, опубликованных на hh.ru.
Будет полезно, если вы являетесь аналитиком данных (начинающим или опытным) или работаете по смежной профессии, либо просто интересуетесь базами данных, Python, SQL, экономикой и финансами и всеми производными от этих тем.
Список разобранных вопросов:
🐍 Python:
📖 SQL:
📖 Базы данных:
⚙️ Инструменты:
🐞 А/Б тестирование:
📊 Работа с данными:
В ближайшем будущем будем разбирать👇
— Больше про SQL и базы данных: архитектуру и т.п.
— Больше питоновских библиотек и кейсов
— Про банковские данные
— Актуальные инструменты, в частности BI-инструменты и ETL-инструменты
Будет полезно, если вы являетесь аналитиком данных (начинающим или опытным) или работаете по смежной профессии, либо просто интересуетесь базами данных, Python, SQL, экономикой и финансами и всеми производными от этих тем.
Список разобранных вопросов:
— Эмбеддинги предложений
— Алгоритм кластеризации
— Кластеризация текстовой информации
— Визуализация: Matplotlib
— Визуализация: Seaborn
— Python в Tableau
— Python + SQL: Cx_oracle
— Большие данные в Python: Dask
— Массовая загрузка файлов в БД
— Продвинутая запись в Excel: XlsxWriter
— Аномалии в данных
— Аномалии в данных: применение скользящих средних
— Автоматизация подбора чисел
— Анализ динамики
— PARTITION (оконные функции)
— PARTITION (партиционирование)
— Процедуры: разбор IN | OUT | IN OUT
— Процедуры: объявления и исключения
— PACKAGE (пакеты)
— Циклы LOOP, WHILE, FOR
— CURSOR
— Индексы
— Представления (Views)
— Материализованные и нематериализованные views
— Hints (хинты)
— EXPLAIN PLAN
— TRIGGER (триггеры)
— Какие бывают базы данных
— Виды БД наглядно
— ACID и BASE
— OLAP-кубы
— Проектирование баз данных
— Разница между БД и DWH
— Витрины данных
— ETL и ELT процессы
— Звездочка, снежинка, Data Vault
— Слои данных в DWH
— Нормализация
⚙️ Инструменты:
— Обзор Hadoop
— Обзор Hive
— Обзор Impala
— Обзор Airflow
— Обзор ClickHouse
— Tableau
— Arenadata Catalog
— Qlik Sense
— Informatica PowerCenter
🐞 А/Б тестирование:
— Основы А/Б тестов
— А/Б тесты на практике
— Математические методы проверки результатов
— Инструменты А/Б тестирования
📊 Работа с данными:
— Парадокс Симпсона
— Банковские клиенты
— Клиентская информация в банковском DWH
— Банковские продукты
— Продуктовая информация в банковском DWH
— Счета, баланс и фин рез в банковском DWH
— Качество данных
— Метаданные
— Source-to-Target Mapping
В ближайшем будущем будем разбирать
— Больше про SQL и базы данных: архитектуру и т.п.
— Больше питоновских библиотек и кейсов
— Про банковские данные
— Актуальные инструменты, в частности BI-инструменты и ETL-инструменты
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Data analysis | Анализ данных | DA
Про анализ данных для аналитиков (Data analysis): базы данных (БД), SQL, Python и IT
По всем вопросам - @connection_07
Мой курс по анализу данных - https://stepik.org/a/269469
Реклама на бирже - https://telega.in/channels/business_stats/card
По всем вопросам - @connection_07
Мой курс по анализу данных - https://stepik.org/a/269469
Реклама на бирже - https://telega.in/channels/business_stats/card
🔥8❤5👍2
Soft Delete, Hard Delete или Архив? Как правильно удалять данные 🗑️
Задача: Пользователь нажал «Удалить аккаунт». Что делать базе? Стереть навсегда? Скрыть? Или перенести в чулан? Разбираем три стратегии.
1️⃣ Hard Delete (Физическое удаление) 🧨
Классический DELETE. Данные исчезают, место освобождается.
Плюсы:
🔹 Максимальная скорость работы базы (таблицы компактные).
🔹 Соблюдение GDPR (право на забвение).
🔹 Нет проблем с уникальностью (email можно использовать повторно сразу).
Минусы: Данные не восстановить без бэкапа. Опасность «битых ссылок», если забыли настроить каскадное удаление (Foreign Keys).
2️⃣ Soft Delete (Мягкое удаление) 👻
Мы не удаляем строку, а ставим метку deleted_at = NOW(). Данные лежат на месте, но приложение делает вид, что их нет.
Плюсы:
🔹 Легко восстановить («Ой, я случайно!»).
🔹 Сохраняется история и связи.
Минусы:
🔸 Раздувание таблицы: База хранит тонны «мертвых» данных, индексы тормозят.
🔸 Сложность запросов: Везде нужно писать WHERE deleted_at IS NULL.
🔸 Проблема уникальности: Если bob@site.com удален «мягко», создать нового Боба с тем же email не даст уникальный индекс (нужен частичный индекс).
3️⃣ Archiving (Архивирование / Cold Storage) 📦
Гибридный подход. Данные удаляются из основной («горячей») таблицы, но перед этим переносятся в отдельную таблицу-архив (users_archive).
Как это работает:
🔹Стартуем транзакцию.
🔹INSERT данные в таблицу архива.
🔹DELETE данные из основной таблицы.
🔹Коммит.
Плюсы:
✅ Основная таблица летает (в ней только активные данные).
✅ История сохранена (в архиве).
✅ Нет проблем с уникальными индексами в основной таблице.
Элегантный перенос одной командой (PostgreSQL) :
Задача: Пользователь нажал «Удалить аккаунт». Что делать базе? Стереть навсегда? Скрыть? Или перенести в чулан? Разбираем три стратегии.
1️⃣ Hard Delete (Физическое удаление) 🧨
Классический DELETE. Данные исчезают, место освобождается.
Плюсы:
🔹 Максимальная скорость работы базы (таблицы компактные).
🔹 Соблюдение GDPR (право на забвение).
🔹 Нет проблем с уникальностью (email можно использовать повторно сразу).
Минусы: Данные не восстановить без бэкапа. Опасность «битых ссылок», если забыли настроить каскадное удаление (Foreign Keys).
2️⃣ Soft Delete (Мягкое удаление) 👻
Мы не удаляем строку, а ставим метку deleted_at = NOW(). Данные лежат на месте, но приложение делает вид, что их нет.
Плюсы:
🔹 Легко восстановить («Ой, я случайно!»).
🔹 Сохраняется история и связи.
Минусы:
🔸 Раздувание таблицы: База хранит тонны «мертвых» данных, индексы тормозят.
🔸 Сложность запросов: Везде нужно писать WHERE deleted_at IS NULL.
🔸 Проблема уникальности: Если bob@site.com удален «мягко», создать нового Боба с тем же email не даст уникальный индекс (нужен частичный индекс).
3️⃣ Archiving (Архивирование / Cold Storage) 📦
Гибридный подход. Данные удаляются из основной («горячей») таблицы, но перед этим переносятся в отдельную таблицу-архив (users_archive).
Как это работает:
🔹Стартуем транзакцию.
🔹INSERT данные в таблицу архива.
🔹DELETE данные из основной таблицы.
🔹Коммит.
Плюсы:
✅ Основная таблица летает (в ней только активные данные).
✅ История сохранена (в архиве).
✅ Нет проблем с уникальными индексами в основной таблице.
Элегантный перенос одной командой (PostgreSQL) :
WITH moved_rows AS (
DELETE FROM users
WHERE id = 101
RETURNING *
)
INSERT INTO users_archive
SELECT * FROM moved_rows;
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥28👍9❤4👎1👌1💯1🆒1
SQL & AI: Друзья или враги?
Еще пару лет назад мы обсуждали, заменит ли нейросеть разработчика. В 2026 году ответ стал очевиден: AI не заменил SQL-специалиста, но он радикально изменил правила игры.
Сегодня разбираемся, как превратить AI в мощного союзника и где расставлены ловушки, в которые попадают даже мидлы.
🤝 Почему AI - ваш лучший друг
🔹Прощай, шаблонный код
Написать 10 однотипных JOIN или сложную структуру CASE WHEN нейросеть может за секунды. Это экономит до 40% времени на рутине.
🔹Объяснение чужого кода
Получили в наследство legacy-запрос на 200 строк без единого комментария? AI отлично справляется с декомпозицией и объяснением логики «человеческим» языком.
🔹Генерация синтетических данных
Нужно быстро наполнить таблицу для тестов, соблюдая типы данных и связи? AI сделает это лучше любого скрипта-заполнителя.
⚠️ Почему AI - коварный враг
Главная проблема в том, что AI галлюцинирует уверенно.
🔹Каша из диалектов
Модель может предложить изящное решение на функциях PostgreSQL (например, DISTINCT ON), которые просто не существуют или работают иначе в вашей версии MySQL или ClickHouse.
🔹Производительность — не приоритет по умолчанию
Если не попросить специально оптимизировать запрос, AI выдаст код, который просто «работает». Он часто предлагает вложенные подзапросы там, где эффективнее CTE или оконные функции, и редко думает об использовании индексов.
🔹Отсутствие контекста БД
Нейросеть не знает объема ваших данных, наличия индексов и специфики «железа». Запрос, который AI посчитал идеальным, может «положить» ваш прод в пиковую нагрузку.
💡 Как выжить в эпоху AI-SQL?
Чтобы оставаться востребованным профи, фокус должен сместиться с «написания кода» на его аудит и архитектуру.
🔹Валидация — это закон
Никогда не копируйте код из чата в консоль без проверки EXPLAIN ANALYZE.
🔹AI как ревьюер
Попробуйте обратный подход — скормите нейросети свой рабочий запрос и спросите: «Как это оптимизировать?». Иногда она подсвечивает неочевидные узкие места.
🔹Изучайте теорию глубже
Чем лучше вы понимаете работу планировщика запросов и индексов, тем проще вам будет увидеть «галлюцинацию» в ответе нейросети.
AI - это не замена мозга, а мощный инструмент. Он делает вас быстрее, но очень важно валидировать самостоятельно любой ее ответ.
Еще пару лет назад мы обсуждали, заменит ли нейросеть разработчика. В 2026 году ответ стал очевиден: AI не заменил SQL-специалиста, но он радикально изменил правила игры.
Сегодня разбираемся, как превратить AI в мощного союзника и где расставлены ловушки, в которые попадают даже мидлы.
🤝 Почему AI - ваш лучший друг
🔹Прощай, шаблонный код
Написать 10 однотипных JOIN или сложную структуру CASE WHEN нейросеть может за секунды. Это экономит до 40% времени на рутине.
🔹Объяснение чужого кода
Получили в наследство legacy-запрос на 200 строк без единого комментария? AI отлично справляется с декомпозицией и объяснением логики «человеческим» языком.
🔹Генерация синтетических данных
Нужно быстро наполнить таблицу для тестов, соблюдая типы данных и связи? AI сделает это лучше любого скрипта-заполнителя.
⚠️ Почему AI - коварный враг
Главная проблема в том, что AI галлюцинирует уверенно.
🔹Каша из диалектов
Модель может предложить изящное решение на функциях PostgreSQL (например, DISTINCT ON), которые просто не существуют или работают иначе в вашей версии MySQL или ClickHouse.
🔹Производительность — не приоритет по умолчанию
Если не попросить специально оптимизировать запрос, AI выдаст код, который просто «работает». Он часто предлагает вложенные подзапросы там, где эффективнее CTE или оконные функции, и редко думает об использовании индексов.
🔹Отсутствие контекста БД
Нейросеть не знает объема ваших данных, наличия индексов и специфики «железа». Запрос, который AI посчитал идеальным, может «положить» ваш прод в пиковую нагрузку.
💡 Как выжить в эпоху AI-SQL?
Чтобы оставаться востребованным профи, фокус должен сместиться с «написания кода» на его аудит и архитектуру.
🔹Валидация — это закон
Никогда не копируйте код из чата в консоль без проверки EXPLAIN ANALYZE.
🔹AI как ревьюер
Попробуйте обратный подход — скормите нейросети свой рабочий запрос и спросите: «Как это оптимизировать?». Иногда она подсвечивает неочевидные узкие места.
🔹Изучайте теорию глубже
Чем лучше вы понимаете работу планировщика запросов и индексов, тем проще вам будет увидеть «галлюцинацию» в ответе нейросети.
AI - это не замена мозга, а мощный инструмент. Он делает вас быстрее, но очень важно валидировать самостоятельно любой ее ответ.
❤25👍23🔥8💯2
🚀 Вопросы с собеседования на позицию intern аналитика в Тинькофф: разбор SQL 🚀
Сегодня мы разберем некоторые интересные вопросы по SQL, которые могут встретиться на собеседовании в Тинькофф.📊 🔍
1️⃣ Может ли измениться результат запроса, если в LEFT JOIN поменять местами таблицы ?
Да, если поменять местами таблицы в LEFT JOIN, результат запроса кардинально изменится. Все потому, что LEFT JOIN берет все строки из "левой" таблицы, дополняя их данными из "правой". Смена местами меняет логику: теперь "правая" становится "левой" и наоборот. Это влияет на то, какие строки и как будут включены в результат. 🔄
2️⃣ 5 + NULL это сколько?
В SQL, когда вы выполняете арифметическую операцию с NULL, результатом всегда будет NULL. Это связано с тем, что NULL представляет собой неопределенное значение, и любая операция с неопределенным значением также является неопределенной. Таким образом, 5 + NULL будет равно NULL.
3️⃣ Какие функции умеют возвращать значения из предыдущих/последующих строк для заданной строки таблицы ?
В SQL, чтобы работать с данными из строк до и после текущей, используются оконные функции. Эти функции обеспечивают доступ к значениям предыдущих/последующих строк:
ℹ️ LEAD(): Получает данные из строки после текущей, позволяя смотреть вперед на заданное количество строк.
ℹ️ LAG(): Доступ к данным из строки перед текущей, предоставляя возможность анализировать предыдущие значения.
ℹ️ FIRST_VALUE() и LAST_VALUE(): Возвращают первое и последнее значение в наборе строк соответственно, идеально для сравнения текущих значений с крайними в диапазоне.
ℹ️ NTH_VALUE(): Дает значение из конкретной позиции в окне, полезно для нахождения конкретных точек данных в последовательности.
Для тех, кто хочет углубиться в тему оконных функций:
https://sql-academy.org/ru/guide/windows-functions
#задание_из_собеседования #tinkoff #intern #analytic
Сегодня мы разберем некоторые интересные вопросы по SQL, которые могут встретиться на собеседовании в Тинькофф.
1️⃣ Может ли измениться результат запроса, если в LEFT JOIN поменять местами таблицы ?
Да, если поменять местами таблицы в LEFT JOIN, результат запроса кардинально изменится. Все потому, что LEFT JOIN берет все строки из "левой" таблицы, дополняя их данными из "правой". Смена местами меняет логику: теперь "правая" становится "левой" и наоборот. Это влияет на то, какие строки и как будут включены в результат. 🔄
2️⃣ 5 + NULL это сколько?
В SQL, когда вы выполняете арифметическую операцию с NULL, результатом всегда будет NULL. Это связано с тем, что NULL представляет собой неопределенное значение, и любая операция с неопределенным значением также является неопределенной. Таким образом, 5 + NULL будет равно NULL.
3️⃣ Какие функции умеют возвращать значения из предыдущих/последующих строк для заданной строки таблицы ?
В SQL, чтобы работать с данными из строк до и после текущей, используются оконные функции. Эти функции обеспечивают доступ к значениям предыдущих/последующих строк:
Для тех, кто хочет углубиться в тему оконных функций:
https://sql-academy.org/ru/guide/windows-functions
#задание_из_собеседования #tinkoff #intern #analytic
Please open Telegram to view this post
VIEW IN TELEGRAM
❤52👍24🔥14
Views vs Materialized Views: в чем разница?
Когда вы только начинаете работать с SQL, вам быстро надоедает писать один и тот же запрос на 20 строк с кучей джоинов. Чтобы не копипастить код, его можно сохранить прямо в базе. Для этого есть два способа: обычные Views (вьюхи) и материализованные.
Что такое View?
Это просто запрос, которому дали имя. База не создает новую таблицу и не копирует данные. Она просто запоминает ваш SELECT.
Когда вы пишете SELECT * FROM my_view, база «под капотом» подставляет ваш исходный код и выполняет его заново.
Представьте, что вам постоянно нужно видеть комнаты вместе с именами владельцев. Чтобы не джойнить таблицы каждый раз руками, делаем так:
Теперь можно просто писать SELECT * FROM room_owners. База будет каждый раз пересчитывать джойн.
Плюс в том, что данные всегда актуальные.
Минус: если в таблицах миллионы строк, такая View начнет подтормаживать.
Что такое Materialized View
Здесь база выполняет запрос один раз и сохраняет результат в отдельную скрытую таблицу. Это спасение для тяжелых расчетов. Например, если мы хотим посчитать общую выручку по каждой комнате за всё время:
Результат читается мгновенно, потому что сумма уже посчитана и лежит на диске. Но есть нюанс: данные в ней «застывают». Если пришла новая бронь, сумма не изменится, пока вы не обновите View командой:
Что выбрать?
Все зависит от того, что вам важнее: скорость или актуальность. Если данные нужны «прямо сейчас» — делайте обычную View. Если вы строите отчет за прошлый месяц, который не меняется каждую секунду — Materialized View сэкономит кучу ресурсов.
Когда вы только начинаете работать с SQL, вам быстро надоедает писать один и тот же запрос на 20 строк с кучей джоинов. Чтобы не копипастить код, его можно сохранить прямо в базе. Для этого есть два способа: обычные Views (вьюхи) и материализованные.
Что такое View?
Это просто запрос, которому дали имя. База не создает новую таблицу и не копирует данные. Она просто запоминает ваш SELECT.
Когда вы пишете SELECT * FROM my_view, база «под капотом» подставляет ваш исходный код и выполняет его заново.
Представьте, что вам постоянно нужно видеть комнаты вместе с именами владельцев. Чтобы не джойнить таблицы каждый раз руками, делаем так:
CREATE VIEW room_owners AS
SELECT r.id, r.home_type, u.name as owner_name
FROM Rooms r
JOIN Users u ON r.owner_id = u.id;
Теперь можно просто писать SELECT * FROM room_owners. База будет каждый раз пересчитывать джойн.
Плюс в том, что данные всегда актуальные.
Минус: если в таблицах миллионы строк, такая View начнет подтормаживать.
Что такое Materialized View
Здесь база выполняет запрос один раз и сохраняет результат в отдельную скрытую таблицу. Это спасение для тяжелых расчетов. Например, если мы хотим посчитать общую выручку по каждой комнате за всё время:
CREATE MATERIALIZED VIEW room_stats AS
SELECT room_id, SUM(total) as total_earned
FROM Reservations
GROUP BY room_id;
Результат читается мгновенно, потому что сумма уже посчитана и лежит на диске. Но есть нюанс: данные в ней «застывают». Если пришла новая бронь, сумма не изменится, пока вы не обновите View командой:
REFRESH MATERIALIZED VIEW room_stats;
Что выбрать?
Все зависит от того, что вам важнее: скорость или актуальность. Если данные нужны «прямо сейчас» — делайте обычную View. Если вы строите отчет за прошлый месяц, который не меняется каждую секунду — Materialized View сэкономит кучу ресурсов.
❤36👍25🔥6👎1
DELETE — это навсегда. Почему в разработке его избегают?
Удаление данных из базы в реальном проекте — это почти всегда плохая идея. Ошибка пользователя или случайный клик могут стоить часов работы по восстановлению данных из бэкапов. Чтобы не доводить до этого, используют Soft delete.
Вместо реального удаления мы просто помечаем строку как неактивную в колонке deleted_at. Данные остаются на месте, но приложение их «не видит».
Где начинаются проблемы
Самое неприятное — это необходимость фильтровать удаленные записи в каждом запросе. Стоит один раз забыть про WHERE deleted_at IS NULL, и в аналитике по выручке или количеству живых юзеров появятся «мертвые души».
Еще одна ловушка — уникальные индексы. Если на email стоит UNIQUE, база не даст создать новый аккаунт с почтой удаленного юзера. Для индекса эта запись всё еще существует, и ему неважно, какая дата там проставлена.
Как сделать архитектуру чище
Чтобы не загромождать код постоянными проверками на NULL, лучше вынести логику во View. Мы создаем виртуальную таблицу, которая сразу отсекает всё лишнее
Для решения конфликтов уникальности идеально подходят частичные индексы.
В Postgres это делается одной командой:
Так индекс будет игнорировать удаленные строки. Это позволит пользователям регистрироваться заново на ту же почту и сэкономит место на диске.
Итог прост: если данные представляют хоть какую-то ценность, не удаляйте их. Используйте флаги удаления и View.
Это в разы безопаснее, чем чистить базу физически.
Удаление данных из базы в реальном проекте — это почти всегда плохая идея. Ошибка пользователя или случайный клик могут стоить часов работы по восстановлению данных из бэкапов. Чтобы не доводить до этого, используют Soft delete.
Вместо реального удаления мы просто помечаем строку как неактивную в колонке deleted_at. Данные остаются на месте, но приложение их «не видит».
Где начинаются проблемы
Самое неприятное — это необходимость фильтровать удаленные записи в каждом запросе. Стоит один раз забыть про WHERE deleted_at IS NULL, и в аналитике по выручке или количеству живых юзеров появятся «мертвые души».
Еще одна ловушка — уникальные индексы. Если на email стоит UNIQUE, база не даст создать новый аккаунт с почтой удаленного юзера. Для индекса эта запись всё еще существует, и ему неважно, какая дата там проставлена.
Как сделать архитектуру чище
Чтобы не загромождать код постоянными проверками на NULL, лучше вынести логику во View. Мы создаем виртуальную таблицу, которая сразу отсекает всё лишнее
CREATE VIEW active_users AS
SELECT * FROM Users WHERE deleted_at IS NULL;
Для решения конфликтов уникальности идеально подходят частичные индексы.
В Postgres это делается одной командой:
CREATE UNIQUE INDEX idx_email ON Users (email) WHERE deleted_at IS NULL;
Так индекс будет игнорировать удаленные строки. Это позволит пользователям регистрироваться заново на ту же почту и сэкономит место на диске.
Итог прост: если данные представляют хоть какую-то ценность, не удаляйте их. Используйте флаги удаления и View.
Это в разы безопаснее, чем чистить базу физически.
❤47👍23🔥11💘1
Один UPDATE — и данных больше нет. Как это починить?
Представьте: прибегает продакт — «Почему у клиента цена 50к? Мы же вчера ставили 40к!» А в базе уже новое значение. Старое затёрто командой UPDATE. Восстановить нельзя. Никак.
Если в проекте есть данные, которые меняются и за которыми нужно следить — цены, статусы, настройки — эта проблема рано или поздно прилетит. Это как редактировать документ без Ctrl+Z — каждое изменение уничтожает предыдущую версию. Для финансовой отчётности и аналитики — катастрофа.
Разберём три способа этого избежать — от простого к мощному.
1️⃣ Вариант 1: Таблица-клон
Самый понятный способ. Рядом с основной таблицей (например, Rooms) создаём её копию — Rooms_History. Каждый раз, когда кто-то меняет строку в основной таблице, автоматический механизм (триггер) сначала сохраняет старую версию строки в копию, и только потом применяет изменение.
Главная боль — Schema Drift. Допустим, разработчик добавил в основную таблицу новую колонку «площадь». Если он забыл добавить эту же колонку в таблицу-клон и обновить триггер — всё ломается. Подходит только там, где структура таблиц не менялась годами и не планирует.
2️⃣ Вариант 2: SYSTEM_TIME — встроенный видеорегистратор
Некоторые современные базы данных умеют вести историю сами, без всяких таблиц-клонов и триггеров. Достаточно один раз включить функцию — и база начнёт запоминать, когда каждая строка появилась и когда изменилась.
После этого можно буквально «заглянуть в прошлое» одной командой:
Эта строка говорит: «Покажи мне все комнаты в том виде, в каком они были 1 января 2026 года». Идеально для отладки: можно увидеть точное состояние данных в ту секунду, когда пользователь словил баг. Если нужна серьёзная аналитика и «путешествия во времени» — это лучший выбор.
Но есть нюанс: SYSTEM_TIME поддерживают не все базы данных. В PostgreSQL и MariaDB это работает, а вот в MySQL — нет.
3️⃣ Вариант 3: История через JSONB-слепки
Идея простая: каждый раз, когда строка меняется, мы берём её текущую версию, упаковываем целиком в один JSON-объект и сохраняем в отдельную таблицу. Если комнату не трогали полгода, ни одной новой записи за это время не появится.
Допустим, у комнаты №42 три раза менялась цена. В таблице истории будет:
Хотим узнать цену в феврале? Берём последнюю запись до февраля — 300.
Почему JSON, а не обычная таблица-клон? Потому что JSON — это «резиновый» контейнер. Неважно, сколько новых колонок добавили в основную таблицу — JSON примет их все без единого изменения в таблице истории. Та самая проблема Schema Drift из первого варианта здесь просто не существует.
Ложка дёгтя: история раздувает базу
Об этом забывают чаще всего. Если данные меняются часто, таблица истории за полгода легко вырастает до сотен гигабайт.
Проверенная стратегия — разделять данные по «температуре»:
🔹«Горячая» история за последние 3 месяца — живёт в основной базе (PostgreSQL), к ней обращаются постоянно.
🔹 Всё, что старше — уезжает в «холодное» хранилище (ClickHouse, S3). Там место дешевле, а аналитика по миллионам старых строк работает даже быстрее.
Итого, что выбрать:
🔹 Простая система, структура не меняется → Таблица-клон с триггером
🔹 Нужна точная аналитика и «путешествие во времени» → SYSTEM_TIME
🔹 Структура часто меняется, средний масштаб → JSONB-слепки
И в любом случае — заранее решите, куда архивировать историю через год. Потом будет поздно.
Представьте: прибегает продакт — «Почему у клиента цена 50к? Мы же вчера ставили 40к!» А в базе уже новое значение. Старое затёрто командой UPDATE. Восстановить нельзя. Никак.
Если в проекте есть данные, которые меняются и за которыми нужно следить — цены, статусы, настройки — эта проблема рано или поздно прилетит. Это как редактировать документ без Ctrl+Z — каждое изменение уничтожает предыдущую версию. Для финансовой отчётности и аналитики — катастрофа.
Разберём три способа этого избежать — от простого к мощному.
Как это работает — коротко:
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-слепки
И в любом случае — заранее решите, куда архивировать историю через год. Потом будет поздно.
❤20👍14🔥7
Задача из собеседования (VK, intern-аналитик): медиана зарплат по отделам без MEDIAN 📊
Любимый вопрос на собесах: проверяет понимание оконных функций и аккуратность с чётным/нечётным числом строк.
📋 Схема БД
🔹 employees (id, name, department, salary)
🎯 Формулировка
Посчитать медианную зарплату для каждого отдела. Использовать MEDIAN / PERCENTILE_CONT нельзя — нужно реализовать руками.
📊 Пример данных
Ожидаемый результат:
Разбор
🧠 Что такое медиана на пальцах
🔹 Сортируем зарплаты по возрастанию
🔹 Если строк нечётно — берём центральную
🔹 Если чётно — среднее двух центральных
Для отдела с зарплатами [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️⃣ Решение через оконные функции
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 задача решается в одну строку:
Но на собесе обычно просят именно «руками» — чтобы проверить, понимаете ли вы, что происходит под капотом 🔍
🎯 Что запомнить
🔹 Медиана руками = ROW_NUMBER + COUNT в одном CTE
🔹 Центральные позиции — FLOOR и CEIL от (cnt+1)/2
🔹 Делим на 2.0, а не на 2 — иначе целочисленное деление всё сломает
🔹 Если в БД есть PERCENTILE_CONT — используйте его; ручная реализация нужна только для собесов и старых MySQL (до 8.0 там вообще нет оконных функций — придётся через переменные)
Любимый вопрос на собесах: проверяет понимание оконных функций и аккуратность с чётным/нечётным числом строк.
📋 Схема БД
🔹 employees (id, name, department, salary)
🎯 Формулировка
Посчитать медианную зарплату для каждого отдела. Использовать MEDIAN / PERCENTILE_CONT нельзя — нужно реализовать руками.
📊 Пример данных
id 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 там вообще нет оконных функций — придётся через переменные)
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥19👍11❤6🤯6
Больше об CTE по ссылке
https://sql-academy.org/ru/guide/operator-with
https://sql-academy.org/ru/guide/operator-with
🔥17👍8❤6👎1
CHECK-ограничения в MySQL: валидация прямо в таблице ✅
Когда вы хотите убедиться, что в таблицу попадают только корректные данные — не всегда нужно писать сложную логику в приложении. Иногда достаточно встроенного механизма MySQL: ограничения CHECK.
🔹 Что такое CHECK?
Это правило, которое автоматически проверяет значение в колонке при вставке (INSERT) или обновлении (UPDATE). Если правило нарушено — операция отменяется с ошибкой.
Пример:
Теперь нельзя вставить пользователя с возрастом -5 или 999 — MySQL просто не даст это сделать.
Синтаксис CHECK
🔸 Можно добавлять при создании таблицы (CREATE TABLE) или позже (ALTER TABLE).
🔸 Можно использовать AND, OR, арифметику, сравнения, функции (с ограничениями).
🔸 Поддерживается в MySQL 8.0+. В старых версиях CHECK игнорировался (!).
Пример с условием на строку:
Здесь мы запрещаем отрицательные цены.
Добавление ограничения в существующую таблицу:
Что произойдёт при нарушении?
⚠️ Важно помнить:
🔹CHECK срабатывает только для новых данных — старые строки не проверяются.
🔹Если значение NULL, правило обычно не нарушается (NULL считается «неизвестным» и не сравнивается напрямую).
🔹Ошибку можно перехватывать в приложении, чтобы сообщить пользователю.
✅ Когда стоит использовать CHECK:
🔹 Для ограничений: возраст, положительная цена, длина строки, формат (через LIKE или REGEXP).
🔹Когда хотите, чтобы в таблице «по умолчанию» всегда были только корректные данные.
🔹Чтобы упростить валидацию и сделать БД самодостаточной (например, при работе с внешними источниками данных).
Когда вы хотите убедиться, что в таблицу попадают только корректные данные — не всегда нужно писать сложную логику в приложении. Иногда достаточно встроенного механизма 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).
🔹Когда хотите, чтобы в таблице «по умолчанию» всегда были только корректные данные.
🔹Чтобы упростить валидацию и сделать БД самодостаточной (например, при работе с внешними источниками данных).
❤16🔥12👍5
🔑 UUIDv4 тихо убивает твою базу
UUID удобен: генерируешь на бэке, ключи уникальны, и никто не подсмотрит число заказов, поменяв цифру в URL. Кажется, идеальный Primary Key.
Но обычный UUIDv4 — полностью случайный. А базе это очень не нравится.
⚙️ Почему так
Индексы живут в B-Tree, а дереву нужен порядок.
🔹 BigInt с автоинкрементом → новая запись дописывается в конец. Мгновенно.
🔹UUIDv4 → случайный ID лезет в середину дерева. База рвёт заполненную страницу пополам и перетасовывает данные. Это и есть Page Split.
📉 Что происходит в продакшене
Пока строк пара сотен тысяч — тишина.
На миллионах начинается боль:
🔹 тормозят INSERT — CPU занят не записью, а перебалансировкой
🔹 фрагментация — страницы полупустые, индекс пухнет в разы
🔹 вымывание кэша — раздутый индекс не лезет в RAM, и база уходит на диск
🚀 Решение — UUIDv7
Отказываться от UUID не нужно. Просто бери седьмую версию.
В начале строки — timestamp (время до миллисекунды), и только потом случайные биты.
🔹 Ключи всегда растут
🔹 Для базы это почти автоинкремент
🔹Записи ложатся в конец, фрагментация исчезает
Скорость вставок остаётся ровной даже на таблицах в десятки гигабайт.
💡 Стартуешь проект? Закладывай UUIDv7 сразу.
UUID удобен: генерируешь на бэке, ключи уникальны, и никто не подсмотрит число заказов, поменяв цифру в URL. Кажется, идеальный Primary Key.
Но обычный UUIDv4 — полностью случайный. А базе это очень не нравится.
⚙️ Почему так
Индексы живут в B-Tree, а дереву нужен порядок.
🔹 BigInt с автоинкрементом → новая запись дописывается в конец. Мгновенно.
🔹UUIDv4 → случайный ID лезет в середину дерева. База рвёт заполненную страницу пополам и перетасовывает данные. Это и есть Page Split.
📉 Что происходит в продакшене
Пока строк пара сотен тысяч — тишина.
На миллионах начинается боль:
🔹 тормозят INSERT — CPU занят не записью, а перебалансировкой
🔹 фрагментация — страницы полупустые, индекс пухнет в разы
🔹 вымывание кэша — раздутый индекс не лезет в RAM, и база уходит на диск
🚀 Решение — UUIDv7
Отказываться от UUID не нужно. Просто бери седьмую версию.
В начале строки — timestamp (время до миллисекунды), и только потом случайные биты.
🔹 Ключи всегда растут
🔹 Для базы это почти автоинкремент
🔹Записи ложатся в конец, фрагментация исчезает
Скорость вставок остаётся ровной даже на таблицах в десятки гигабайт.
💡 Стартуешь проект? Закладывай UUIDv7 сразу.
👍24🔥10👌3❤2
🚀 Поздний JOIN: как ускорить OFFSET на больших данных в 10 раз
Классическая пагинация через
📉 В чём проблема
🔹 Представь поиск 100-й страницы в толстой энциклопедии. Вместо оглавления ты читаешь весь текст с первой страницы, пока не дойдёшь до сотой.
🔹 Так же делает база при
🚀 Решение — Поздний JOIN (Deferred Join)
Сначала мы быстро находим нужные идентификаторы, а уже к ним присоединяем полные данные:
⚙️ Как это работает
🔸 Внутренний запрос работает как оглавление. Он бежит только по легкому индексу (цене и
🔸 Внешний
💡 Этот контринтуитивный трюк спасет твою пагинацию, когда таблица огромная, а отказаться от навигации по номерам страниц нельзя.
Классическая пагинация через
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 финальных строк.💡 Этот контринтуитивный трюк спасет твою пагинацию, когда таблица огромная, а отказаться от навигации по номерам страниц нельзя.
🔥28👍9❤7
🥷 Подстава от LIKE: почему 'user_1' находит чужие данные
Все знают, что знак процента (
⚙️ В чём ловушка
В SQL символ
Если ты попытаешься найти конкретного пользователя:
База радостно вернёт не только user_1, но и user-1, userA1 или user91. Подчёркивание сработает как джокер в колоде карт.
🚀 Как починить
Чтобы база искала именно сам символ подчёркивания, его нужно экранировать с помощью оператора
🔹 Мы сами выбираем символ для экранирования (здесь это
🔹 Теперь комбинация
💡 Всегда экранируй спецсимволы в LIKE, чтобы точный поиск не превращался в непредсказуемую лотерею.
Все знают, что знак процента (
%) в поиске заменяет любой кусок текста. Но многие забывают про скрытую угрозу — нижнее подчёркивание.⚙️ В чём ловушка
В SQL символ
_ (underscore) — это тоже спецсимвол. Он означает «ровно один любой знак». Если ты попытаешься найти конкретного пользователя:
SELECT * FROM users WHERE login LIKE 'user_1';
База радостно вернёт не только user_1, но и user-1, userA1 или user91. Подчёркивание сработает как джокер в колоде карт.
🚀 Как починить
Чтобы база искала именно сам символ подчёркивания, его нужно экранировать с помощью оператора
ESCAPE.SELECT * FROM users
WHERE login LIKE 'user!_1' ESCAPE '!';
🔹 Мы сами выбираем символ для экранирования (здесь это
!).🔹 Теперь комбинация
!_ воспринимается базой как обычный текст, а не спецсимвол поиска.💡 Всегда экранируй спецсимволы в LIKE, чтобы точный поиск не превращался в непредсказуемую лотерею.
👍42❤10🔥9🤯3
🧟♂️ Подстава Soft Delete: почему удалённый юзер ломает регистрацию
Ты внедрил мягкое удаление (
⚙️ В чём проблема
🔹 Обычно на колонке
🔹 Но для базы «удалённый» юзер всё ещё существует. Строка физически никуда не делась, поэтому уникальный индекс блокирует новую регистрацию с этим же адресом.
🚀 Идеальное решение
Можно усложнять код бэкенда, но лучше поручить эту задачу самой базе с помощью частичного индекса (Partial Index). Он будет следить за уникальностью только среди активных пользователей.
📈 Почему это круто
🔸 Вся логика защиты от дублей остаётся на уровне БД — никаких костылей в коде.
🔸 Индекс работает быстрее и занимает меньше места, так как просто игнорирует удалённые записи.
💡 Частичный индекс — самое изящное решение для Soft Delete, которое бережёт нервы и дисковое пространство.
Ты внедрил мягкое удаление (
is_deleted = true). Пользователь удаляет аккаунт, а через месяц решает вернуться с тем же email. И тут регистрация падает с ошибкой!⚙️ В чём проблема
🔹 Обычно на колонке
email висит уникальный индекс, чтобы в базе не было клонов.🔹 Но для базы «удалённый» юзер всё ещё существует. Строка физически никуда не делась, поэтому уникальный индекс блокирует новую регистрацию с этим же адресом.
🚀 Идеальное решение
Можно усложнять код бэкенда, но лучше поручить эту задачу самой базе с помощью частичного индекса (Partial Index). Он будет следить за уникальностью только среди активных пользователей.
CREATE UNIQUE INDEX active_users_email_idx
ON users (email)
WHERE is_deleted = false;
📈 Почему это круто
🔸 Вся логика защиты от дублей остаётся на уровне БД — никаких костылей в коде.
🔸 Индекс работает быстрее и занимает меньше места, так как просто игнорирует удалённые записи.
💡 Частичный индекс — самое изящное решение для Soft Delete, которое бережёт нервы и дисковое пространство.
🔥17👍8❤6