This media is not supported in your browser
VIEW IN TELEGRAM
Это подборка материалов по PostgreSQL: настройка и администрирование баз данных, оптимизация запросов, репликация, резервное копирование, индексы, мониторинг и др. темы. Помимо теории, здесь много практических статей и кейсов, которые помогают лучше понять работу PostgreSQL и применять полученные знания.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍6🔥6
N+1 проблема в SQL: почему приложение внезапно начинает делать тысячи запросов!
Одна из самых частых проблем backend-приложений — N+1 queries. Особенно часто это появляется при работе через ORM, потому что код выглядит нормально, а реальные SQL-запросы скрыты внутри слоя абстракции.
Например, есть таблицы:
Сначала приложение получает пользователей:
Допустим, запрос вернул 1000 пользователей. Дальше приложение начинает отдельно загружать заказы для каждого пользователя:
И этот запрос выполняется уже 1000 раз. То есть итоговая схема выглядит так: 1 запрос на получение
В ORM это обычно выглядит примерно так:
Внешне код выглядит абсолютно нормально, но внутри ORM может выполнять отдельный
На маленьких объемах данных проблема почти незаметна. Но на продакшене начинают быстро расти: latency, network overhead, нагрузка на connection pool, время ответа API, нагрузка на БД.
Особенно неприятно это проявляется при pagination, background jobs и high-load API. Обычно данные эффективнее загружать набором.
Например, через
Либо через batch loading:
Во многих случаях
Поэтому современные ORM обычно уже имеют встроенные механизмы борьбы с N+1.
Например, в Django:
В SQLAlchemy:
Отдельная проблема — nested N+1:
И приложение внезапно начинает выполнять уже сотни, тысячи или даже десятки тысяч SQL-запросов. Самое опасное — проблема часто долго остается незаметной, пока объем данных не вырастает.
🔥 Если внутри цикла потенциально выполняется SQL-запрос — почти всегда стоит проверить код на N+1. Именно поэтому profiling SQL-запросов и понимание того, как ORM реально работает с базой, критично для продакшн backend-разработки.
➡️ SQL Ready | #практика
Одна из самых частых проблем backend-приложений — N+1 queries. Особенно часто это появляется при работе через ORM, потому что код выглядит нормально, а реальные SQL-запросы скрыты внутри слоя абстракции.
Например, есть таблицы:
users(id, name)
orders(id, user_id, amount)
Сначала приложение получает пользователей:
SELECT
id,
name
FROM users;
Допустим, запрос вернул 1000 пользователей. Дальше приложение начинает отдельно загружать заказы для каждого пользователя:
SELECT
id,
user_id,
amount
FROM orders
WHERE user_id = ?;
И этот запрос выполняется уже 1000 раз. То есть итоговая схема выглядит так: 1 запрос на получение
users, N запросов на получение orders. Это и есть классическая N+1 problem.В ORM это обычно выглядит примерно так:
users = User.objects.all()
for user in users:
orders = list(user.order_set.all())
print(orders)
Внешне код выглядит абсолютно нормально, но внутри ORM может выполнять отдельный
SELECT для каждого user.order_set.all().На маленьких объемах данных проблема почти незаметна. Но на продакшене начинают быстро расти: latency, network overhead, нагрузка на connection pool, время ответа API, нагрузка на БД.
Особенно неприятно это проявляется при pagination, background jobs и high-load API. Обычно данные эффективнее загружать набором.
Например, через
JOIN:SELECT
u.id,
u.name,
o.id,
o.amount
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id;
Либо через batch loading:
SELECT
id,
user_id,
amount
FROM orders
WHERE user_id IN (?, ?, ?, ...);
Во многих случаях
batch loading даже эффективнее огромного JOIN, потому что JOIN может раздувать result set и создавать большое количество дублирующихся строк при one-to-many связях.Поэтому современные ORM обычно уже имеют встроенные механизмы борьбы с N+1.
Например, в Django:
User.objects.prefetch_related("order_set")
User.objects.select_related("profile")
prefetch_related() обычно используется для reverse FK и many-to-many; select_related() — для FK и OneToOne.В SQLAlchemy:
select(User).options(
selectinload(User.orders)
)
Отдельная проблема — nested N+1:
users
→ orders
→ payments
→ items
И приложение внезапно начинает выполнять уже сотни, тысячи или даже десятки тысяч SQL-запросов. Самое опасное — проблема часто долго остается незаметной, пока объем данных не вырастает.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍8❤6
This media is not supported in your browser
VIEW IN TELEGRAM
Репозиторий представляет собой структурированную базу знаний по MySQL, где собраны как основы работы с базами данных, так и более сложные темы. Материал подан в формате конспекта, поэтому его удобно использовать и для изучения, и для быстрого повторения перед собеседованием или рабочими задачами.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15👍6🤝5
Например, Query Router (mongos) принимает запросы от приложения и распределяет их по нужным шардам, а Replica Set внутри каждого shard обеспечивает отказоустойчивость и репликацию данных.
На картинке — базовая архитектура MongoDB Cluster: Client Application, Driver, Query Router, Config Server и Shards с Primary/Secondary-нодами.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤16👍6🔥4🤝3
This media is not supported in your browser
VIEW IN TELEGRAM
Отличный ресурс для тех, кто хочет разобраться в SQL и освоить оконные функции. На сайте подробно объясняются инструменты для аналитики и сложной обработки данных. Всё сопровождается наглядными примерами и практическими кейсами.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍7🔥4
Сидеть и работать в корпорации — страшно, жизнь-то мимо проходит. Уходить строить бизнес — страшно, а вдруг прогорит. Один из вариантов — разрабатывать свой пет-проект по вечерам. Многие успешные компании, например, Twitter, создавались именно так. Это не значит, что ваш проект обязательно заработает миллиарды, но заработать больше, чем в найме, и получить ценный опыт — вполне реально.
Перед началом разработки появляется множество вопросов, например:
• Как выбрать идею для пет-проекта?
• Что нужно знать про маркетинг
• Как запуститься и довести до первых продаж не имея бюджета на рекламу?
В телеграм-канале «Твой пет проект», Михаил Табунов делится своим опытом с разработчиками и менеджерами.
Он рассказывает, где искать идею для нового проекта, что нужно знать о маркетинге, как запустить стартап и привлечь первых 10 клиентов, а также о многих других важных вещах.
Подписывайтесь на «Твой пет проект», получайте пользу от практиков рынка!
https://t.me/+8Frwa03ciVlhNTky
Перед началом разработки появляется множество вопросов, например:
• Как выбрать идею для пет-проекта?
• Что нужно знать про маркетинг
• Как запуститься и довести до первых продаж не имея бюджета на рекламу?
В телеграм-канале «Твой пет проект», Михаил Табунов делится своим опытом с разработчиками и менеджерами.
Он рассказывает, где искать идею для нового проекта, что нужно знать о маркетинге, как запустить стартап и привлечь первых 10 клиентов, а также о многих других важных вещах.
Подписывайтесь на «Твой пет проект», получайте пользу от практиков рынка!
https://t.me/+8Frwa03ciVlhNTky
Telegram
Твой пет проект
Канал про то, как создать свой маленький свечной заводик
Пишу про:
- Запуски и как сделать первые 10 продаж
- Прожарка идей
- Кейсы роста и ведения проекта параллельно с работой
Автор - Михаил Табунов - @bossofyourboss
Связь @to_baza_education
Пишу про:
- Запуски и как сделать первые 10 продаж
- Прожарка идей
- Кейсы роста и ведения проекта параллельно с работой
Автор - Михаил Табунов - @bossofyourboss
Связь @to_baza_education
Транзакционная блокировка без блокировки строк!
Иногда нужно запретить параллельную обработку одного объекта, но подходящей строки для
Пока транзакция не завершится, другой процесс с тем же ключом будет ждать. После
Если ждать нельзя, используй
🔥 Полезно для идемпотентных операций, генерации документов, биллинга, обработки вебхуков и любых мест, где один и тот же объект нельзя обрабатывать параллельно.
➡️ SQL Ready | #совет
Иногда нужно запретить параллельную обработку одного объекта, но подходящей строки для
FOR UPDATE ещё может не быть.SELECT pg_advisory_xact_lock(10, 42);
pg_advisory_xact_lock создаёт транзакционную пользовательскую блокировку. Первый аргумент удобно использовать как namespace, второй — как id объекта.SELECT pg_advisory_xact_lock(20, user_id);
Пока транзакция не завершится, другой процесс с тем же ключом будет ждать. После
COMMIT или ROLLBACK блокировка снимается автоматически.SELECT pg_try_advisory_xact_lock(20, user_id);
Если ждать нельзя, используй
pg_try_advisory_xact_lock: он сразу вернёт true или false, и приложение сможет аккуратно пропустить задачу.BEGIN;
SELECT pg_advisory_xact_lock(30, 123);
UPDATE invoices SET status = 'paid' WHERE id = 123;
COMMIT;
advisory lock не блокирует таблицу и не заменяет ограничения БД. Это кооперативная блокировка, поэтому все участники должны использовать один и тот же ключ.Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤5🔥4
This media is not supported in your browser
VIEW IN TELEGRAM
Этот репозиторий особенно полезен тем, кто уже работает с PostgreSQL и хочет больше разобраться в производительности базы данных. Здесь хорошо показано, как анализировать запросы, понимать execution plan и находить узкие места, которые замедляют работу приложения.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍7🤝5
Lost Update: почему транзакций недостаточно без правильной модели конкурентного доступа!
Очень распространённое заблуждение: если запросы выполняются внутри транзакции — значит данные уже защищены от гонок, но это не так.
Одна из классических проблем конкурентного доступа —
Текущий баланс:
Два запроса одновременно читают баланс:
Оба получают: 1000. Дальше: первый процесс хочет списать 100; второй — 200. Первый считает: 1000 - 100 = 900. Второй: 1000 - 200 = 800.
После этого выполняются:
а также:
В итоге финальный баланс: 800, хотя математически должен быть: 700, одно обновление потерялось. Это и есть
И самое неприятное — такое может происходить даже внутри транзакций. Всё зависит от уровня изоляции, паттерна обновления и механики блокировок конкретной СУБД.
Очень частая ошибка выглядит так:
Проблема в том, что между
Один из самых надёжных вариантов — атомарное обновление:
Теперь вычисление происходит внутри самого
СУБД выполняет обновление на основе актуального значения строки и использует необходимые механизмы блокировок для корректной синхронизации конкурентных изменений. Это намного безопаснее.
Ещё один вариант — pessimistic locking:
Но здесь есть trade-off, чем больше блокировок: тем выше contention; тем ниже concurrency; тем выше риск deadlock.
Ещё один подход — optimistic locking. Например:
Чтение:
Обновление:
Если другая транзакция уже изменила строку —
Например, PostgreSQL и MySQL (InnoDB) используют разные механизмы MVCC и имеют различия в поведении блокировок и уровней изоляции.
🔥 Главное правило, если логика выглядит как: прочитал значение — изменил в приложении — записал обратно, то всегда стоит проверять, не появляется ли
➡️ SQL Ready | #практика
Очень распространённое заблуждение: если запросы выполняются внутри транзакции — значит данные уже защищены от гонок, но это не так.
Одна из классических проблем конкурентного доступа —
lost update. Например, есть таблица:accounts(id, balance)
Текущий баланс:
id | balance
1 | 1000
Два запроса одновременно читают баланс:
SELECT balance
FROM accounts
WHERE id = 1;
Оба получают: 1000. Дальше: первый процесс хочет списать 100; второй — 200. Первый считает: 1000 - 100 = 900. Второй: 1000 - 200 = 800.
После этого выполняются:
UPDATE accounts
SET balance = 900
WHERE id = 1;
а также:
UPDATE accounts
SET balance = 800
WHERE id = 1;
В итоге финальный баланс: 800, хотя математически должен быть: 700, одно обновление потерялось. Это и есть
lost update.И самое неприятное — такое может происходить даже внутри транзакций. Всё зависит от уровня изоляции, паттерна обновления и механики блокировок конкретной СУБД.
Очень частая ошибка выглядит так:
BEGIN;
SELECT balance
FROM accounts
WHERE id = 1;
-- вычисления в приложении
UPDATE accounts
SET balance = :new_balance
WHERE id = 1;
COMMIT;
Проблема в том, что между
SELECT и UPDATE другая транзакция может изменить строку.Один из самых надёжных вариантов — атомарное обновление:
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
Теперь вычисление происходит внутри самого
UPDATE.СУБД выполняет обновление на основе актуального значения строки и использует необходимые механизмы блокировок для корректной синхронизации конкурентных изменений. Это намного безопаснее.
Ещё один вариант — pessimistic locking:
SELECT balance
FROM accounts
WHERE id = 1
FOR UPDATE;
FOR UPDATE ставит row-level lock. Пока транзакция не завершится, другая транзакция не сможет изменить эту строку или получить несовместимую блокировку на неё.Но здесь есть trade-off, чем больше блокировок: тем выше contention; тем ниже concurrency; тем выше риск deadlock.
Ещё один подход — optimistic locking. Например:
accounts(id, balance, version)
Чтение:
SELECT balance, version
FROM accounts
WHERE id = 1;
Обновление:
UPDATE accounts
SET
balance = :new_balance,
version = version + 1
WHERE
id = 1
AND version = 5;
Если другая транзакция уже изменила строку —
UPDATE затронет 0 строк. Приложение понимает: данные устарели, нужно перечитать и повторить операцию. Ещё важно понимать: READ COMMITTED, REPEATABLE READ, SERIALIZABLE ведут себя по-разному в разных СУБД.Например, PostgreSQL и MySQL (InnoDB) используют разные механизмы MVCC и имеют различия в поведении блокировок и уровней изоляции.
lost update при параллельных запросах.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍6🤝6
This media is not supported in your browser
VIEW IN TELEGRAM
На странице собрана официальная документация ClickHouse по работе с PostgreSQL. Здесь подробно разбираются импорт данных, репликация таблиц, синхронизация и выполнение запросов к PostgreSQL напрямую из ClickHouse. Также есть примеры настройки и практические сценарии использования.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13🔥8👍7👎2
Каждый найдет что-то по душе:
1202 ГБ — Python
1811 ГБ — Frontend
1100 ГБ — C / C++ / C#
804 ГБ — Java
411 ГБ — SQL & БД
309 ГБ — DevOps
998 ГБ — ИБ & Хакинг
773 ГБ — Kotlin / Swift
189 ГБ — PHP
201 ГБ — GoLang
170 ГБ — Rust
167 ГБ — QA / Тестирование
310 ГБ — 1C + Лицензии
495 ГБ — Машинное обучение
704 ГБ — Аналитика Данных
991 ГБ — Дизайн
Материалы в закрепе, постоянно пополняются👆🏻
Please open Telegram to view this post
VIEW IN TELEGRAM
👎7❤1
Разбор JSON в строки на стороне PostgreSQL!
Когда из API прилетает массив объектов, не обязательно разбирать его в коде и делать много отдельных
На вход можно передать
Это особенно удобно для пакетных загрузок, импорта данных, обработки вебхуков и синхронизации с внешними сервисами.
Главный плюс в том, что приложение передаёт один JSON-параметр, а вся пакетная обработка происходит внутри базы одним запросом.
🔥 Такой приём убирает циклы в коде, уменьшает количество запросов к базе и делает массовые операции намного чище.
➡️ SQL Ready | #совет
Когда из API прилетает массив объектов, не обязательно разбирать его в коде и делать много отдельных
INSERT или UPDATE. PostgreSQL умеет превратить JSON-массив в обычную табличную выборку.SELECT *
FROM jsonb_to_recordset($1::jsonb)
AS x(id bigint, amount numeric);
На вход можно передать
JSON вида [{"id":1,"amount":500},{"id":2,"amount":900}], а на выходе получить нормальные типизированные строки.INSERT INTO payments (id, amount)
SELECT id, amount
FROM jsonb_to_recordset($1::jsonb)
AS x(id bigint, amount numeric);
Это особенно удобно для пакетных загрузок, импорта данных, обработки вебхуков и синхронизации с внешними сервисами.
UPDATE payments p
SET amount = x.amount
FROM jsonb_to_recordset($1::jsonb)
AS x(id bigint, amount numeric)
WHERE p.id = x.id;
Главный плюс в том, что приложение передаёт один JSON-параметр, а вся пакетная обработка происходит внутри базы одним запросом.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍8🤝5
This media is not supported in your browser
VIEW IN TELEGRAM
Так сказал мой друг из Африки, когда увидел этот стол)
Не знаю уж че за сетап, меня зовут Саша и качественную необычную мебель из натурального дерева я делаю уже больше 12-ти лет, столько же занимаюсь и темой здоровья.
Когда я услышал, что до 10% смертности связано с сидячим образом жизни, меня это поразило и я задался целью делать максимально функциональные и полезные рабочие пространства, ибо геморрой в 30 это конечно довольно нишево, но все же сомнительно 😂
Ну а собрать такой комплект под свои задачи и при этом сразу прикинуть цены вы можете в удобном Mini App конструкторе
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍2👎2🔥2
Разбираем почему ORDER BY RANDOM() на больших таблицах не лучшая идея!
Если нужно вытащить случайные строки из таблицы, многие пишут так, например в PostgreSQL:
На первый взгляд всё отлично: перемешали строки и взяли первые 10. Но под капотом всё не так красиво. Обычно база читает много строк, а часто вообще всю таблицу, затем вычисляет случайное значение для каждой строки, сортирует результат и только после этого применяет
То есть даже если нужна всего одна строка:
на большой таблице это всё равно может оказаться тяжёлым запросом. Если строк миллионы, начинаются проблемы с CPU, памятью, сортировками и latency. Индексы здесь обычно не помогают.
Один из более дешёвых вариантов — случайный выбор через id:
Этот вариант уже может использовать индекс по id. Но есть нюанс: если после удалений в id много дырок, распределение будет неидеальным.
Например:
В таком случае чаще будет выпадать первая существующая строка после большого разрыва — здесь это 10000. То есть выборка получается смещённой.
Ещё один вариант — случайный
Звучит неплохо. Но у
В PostgreSQL есть ещё
Или:
На больших таблицах это часто работает заметно быстрее. Но важно понимать:
Если нужно, например, 10 строк, обычно добавляют
Но и тут есть нюанс: если sample слишком маленький, запрос может вернуть меньше 10 строк.
И здесь тоже есть компромисс между скоростью и качеством случайной выборки.
выглядит красиво и удобно. Но на больших таблицах это один из тех запросов, которые начинают стоить очень дорого.
🔥 Именно такие простые запросы часто становятся причиной деградации производительности в продакшн.
➡️ SQL Ready | #практика
Если нужно вытащить случайные строки из таблицы, многие пишут так, например в PostgreSQL:
SELECT *
FROM products
ORDER BY RANDOM()
LIMIT 10;
На первый взгляд всё отлично: перемешали строки и взяли первые 10. Но под капотом всё не так красиво. Обычно база читает много строк, а часто вообще всю таблицу, затем вычисляет случайное значение для каждой строки, сортирует результат и только после этого применяет
LIMIT.То есть даже если нужна всего одна строка:
SELECT *
FROM products
ORDER BY RANDOM()
LIMIT 1;
на большой таблице это всё равно может оказаться тяжёлым запросом. Если строк миллионы, начинаются проблемы с CPU, памятью, сортировками и latency. Индексы здесь обычно не помогают.
Один из более дешёвых вариантов — случайный выбор через id:
SELECT *
FROM products
WHERE id >= 1 + FLOOR(RANDOM() * (SELECT MAX(id) FROM products))::int
ORDER BY id
LIMIT 1;
Этот вариант уже может использовать индекс по id. Но есть нюанс: если после удалений в id много дырок, распределение будет неидеальным.
Например:
id
1
2
3
10000
10001
В таком случае чаще будет выпадать первая существующая строка после большого разрыва — здесь это 10000. То есть выборка получается смещённой.
Ещё один вариант — случайный
OFFSET:SELECT *
FROM products
OFFSET FLOOR(RANDOM() * (SELECT COUNT(*) FROM products))::int
LIMIT 1;
Звучит неплохо. Но у
OFFSET тоже есть проблема: чем больше offset, тем больше строк базе придётся пропустить. Плюс в PostgreSQL COUNT(*) на большой таблице тоже может быть дорогой операцией.В PostgreSQL есть ещё
TABLESAMPLE:SELECT *
FROM products TABLESAMPLE SYSTEM (1);
Или:
SELECT *
FROM products TABLESAMPLE BERNOULLI (1);
На больших таблицах это часто работает заметно быстрее. Но важно понимать:
TABLESAMPLE выбирает примерный процент таблицы, а не конкретное количество строк.Если нужно, например, 10 строк, обычно добавляют
LIMIT:SELECT *
FROM products TABLESAMPLE SYSTEM (1)
LIMIT 10;
Но и тут есть нюанс: если sample слишком маленький, запрос может вернуть меньше 10 строк.
И здесь тоже есть компромисс между скоростью и качеством случайной выборки.
SYSTEM работает быстрее, но выбирает данные блоками, поэтому распределение менее равномерное. BERNOULLI ближе к построчной случайной выборке, но обычно дороже. В общем, мысль простая:ORDER BY RANDOM()
выглядит красиво и удобно. Но на больших таблицах это один из тех запросов, которые начинают стоить очень дорого.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥10🤝6
This media is not supported in your browser
VIEW IN TELEGRAM
0nix — Новая экосистема для профи.
Здесь не просто форум. Это готовая биржа, где заказчики находят исполнителей, а исполнители — живые деньги.
🛡 Для кибербезопасников: Threat Intelligence, Осинтеров , Геоинтеров, Red Team, разработчиков инструментов. Ваша аудитория ждет ваши услуги.
💻 Для разработчиков и дизайнеров: Отдельные разделы с заказами на разработку, дизайн и графику. Портфолио — в топ!
Условия для первых участников: Минимальные комиссии на сделки, быстрый арбитраж и модерация.
Регистрируйся и забирай свою скидку на привилегии уже сегодня → 0nix.cc , 0nix.top
Здесь не просто форум. Это готовая биржа, где заказчики находят исполнителей, а исполнители — живые деньги.
Условия для первых участников: Минимальные комиссии на сделки, быстрый арбитраж и модерация.
Регистрируйся и забирай свою скидку на привилегии уже сегодня → 0nix.cc , 0nix.top
Please open Telegram to view this post
VIEW IN TELEGRAM
👎4
Почему оконные функции могут давать неверный результат из-за RANGE вместо ROWS!
Одна из самых неприятных особенностей window functions — frame по умолчанию. Запрос выглядит корректно, проходит проверку глазами, но накопительная сумма может считаться совсем не так, как ожидает разработчик.
Особенно часто это встречается там, где важен порядок операций: расчёт балансов, транзакции, аналитика событий и любые накопительные показатели.
Есть таблица:
Допустим, нужно получить обычный running total — сумму всех предыдущих платежей до текущей строки.
Запрос выглядит очевидно:
Логика кажется простой: первая строка берёт свой amount, вторая прибавляет своё значение к предыдущей сумме, третья делает то же самое. То есть ожидается: 100 — 300 — 600. Но у оконных функций есть скрытая настройка — frame.
Если после
И именно здесь появляется разница между ожиданием и реальным результатом.
Например:
Здесь первые две записи имеют одинаковое время. Для человека это две разные операции. Но для
При таком запросе:
результат будет:
Почему? Потому что при расчёте первой строки
Но чаще всего для накопительных сумм ожидается другое поведение: каждая строка должна учитываться отдельно. Для этого используется
Он не смотрит, одинаковые ли значения сортировки у соседних строк. Для него важен порядок строк после сортировки. Например:
Теперь база получает явную инструкцию: сначала отсортировать записи, затем считать накопление строка за строкой.
Результат становится ожидаемым:
Ещё один важный момент — сама сортировка. Даже если используется
Если несколько событий произошли в одну секунду, SQL не обязан выбирать между ними определённый порядок. Например:
Какая операция должна идти первой? Без дополнительного поля ответа нет. Поэтому обычно добавляют уникальный идентификатор:
Теперь порядок строк становится однозначным, а результат расчёта стабильным.
🔥 Вывод: если
➡️ SQL Ready | #практика
Одна из самых неприятных особенностей window functions — frame по умолчанию. Запрос выглядит корректно, проходит проверку глазами, но накопительная сумма может считаться совсем не так, как ожидает разработчик.
Особенно часто это встречается там, где важен порядок операций: расчёт балансов, транзакции, аналитика событий и любые накопительные показатели.
Есть таблица:
payments(id, created_at, amount)
Допустим, нужно получить обычный running total — сумму всех предыдущих платежей до текущей строки.
Запрос выглядит очевидно:
SELECT
id,
created_at,
amount,
SUM(amount) OVER (
ORDER BY created_at
) AS running_total
FROM payments;
Логика кажется простой: первая строка берёт свой amount, вторая прибавляет своё значение к предыдущей сумме, третья делает то же самое. То есть ожидается: 100 — 300 — 600. Но у оконных функций есть скрытая настройка — frame.
Если после
ORDER BY не указать его явно, SQL использует значение по умолчанию:RANGE BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
И именно здесь появляется разница между ожиданием и реальным результатом.
RANGE работает не по физическому положению строк, а по значениям сортировки. То есть база смотрит на поле из ORDER BY и объединяет строки с одинаковым значением в одну логическую группу.Например:
id=1, created_at='2026-01-01 10:00:00', amount=100
id=2, created_at='2026-01-01 10:00:00', amount=200
id=3, created_at='2026-01-01 11:00:00', amount=300
Здесь первые две записи имеют одинаковое время. Для человека это две разные операции. Но для
RANGE они являются одной группой, потому что значение сортировки совпадает.При таком запросе:
SUM(amount) OVER (
ORDER BY created_at
)
результат будет:
300
300
600
Почему? Потому что при расчёте первой строки
RANGE уже включает обе записи с одинаковым created_at. Получается: 100 + 200 = 300. Поэтому первая строка сразу показывает итог двух операций. Это не ошибка SQL, это стандартное поведение RANGE.Но чаще всего для накопительных сумм ожидается другое поведение: каждая строка должна учитываться отдельно. Для этого используется
ROWS, который работает именно с физическими строками окна.Он не смотрит, одинаковые ли значения сортировки у соседних строк. Для него важен порядок строк после сортировки. Например:
SELECT
id,
created_at,
amount,
SUM(amount) OVER (
ORDER BY created_at, id
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW
) AS running_total
FROM payments;
Теперь база получает явную инструкцию: сначала отсортировать записи, затем считать накопление строка за строкой.
Результат становится ожидаемым:
100
300
600
Ещё один важный момент — сама сортировка. Даже если используется
ROWS, одного created_at может быть недостаточно.Если несколько событий произошли в одну секунду, SQL не обязан выбирать между ними определённый порядок. Например:
id=1, created_at='10:00:00', amount=100
id=2, created_at='10:00:00', amount=200
Какая операция должна идти первой? Без дополнительного поля ответа нет. Поэтому обычно добавляют уникальный идентификатор:
ORDER BY created_at, id
Теперь порядок строк становится однозначным, а результат расчёта стабильным.
RANGE удобен, когда нужно работать с группами одинаковых значений сортировки. ROWS нужен, когда важен порядок конкретных строк. Для обычного running total почти всегда стоит явно указывать ROWS.cumulative sum показывает одинаковые значения на нескольких строках подряд — проверьте ORDER BY и frame окна. Чаще всего причина в том, что ожидали ROWS, а получили поведение RANGE.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤7👍6
Например, сначала изучают основы SQL и работу с таблицами, затем переходят к JOIN, сложным запросам, подзапросам и оконным функциям, а дальше — к проектированию баз данных, транзакциям и оптимизации.
На картинке — путь изучения от базового уровня до продвинутого: основные команды, структура базы данных, работа с данными, производительность запросов и др.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍9🤝6
Разработчики из Anthropic обновили и структурировали масштабный хаб готовых запросов для своей языковой модели. База создана для того, чтобы пользователи могли выжать максимум из Claude при решении прикладных и технических задач, не тратя время на самостоятельный подбор формулировок.
🟦 Для безопасности: готовые конструкции для проверки кода на уязвимости и анализа граничных условий работы алгоритмов.
🟦 Для разработчиков: шаблоны для глубокого ревью кода, автоматического поиска багов, рефакторинга, написания юнит-тестов и проектирования архитектуры.
🟦 Для автоматизации и менеджмента: промпты под стратегическое планирование, парсинг неструктурированных данных, генерацию технической документации и выстраивание логики для ИИ-агентов.
Каждый шаблон снабжен подробным разбором: авторы пошагово объясняют, почему выбрана именно такая структура запроса, как модель интерпретирует переменные и как правильно передавать контекст. Поскольку библиотека официальная, все промпты оптимизированы под особенности контекстного окна и логику мышления последних моделей семейства Claude.
Нейросети отлично автоматизируют рутину и помогают искать уязвимости, но управлять ими может только тот, кто понимает саму базу и суть киберугроз.
Если вы хотите заложить мощный фундамент начните с нашего бесплатного курса (его прошли уже 1108 человек!).
🟦 Разберетесь, как работают кибератаки и как грамотно защитить свои данные.
🟦 Познакомитесь с направлениями и профессиями в кибербезопасности, чтобы выбрать свой трек.
🟦 Получите скидку, если решите продолжить обучение в CyberYozh Academy.
👉 Начните бесплатно прямо сейчас
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
В PostgreSQL уникальность можно проверять не для всей таблицы, а только для части строк!
Допустим, у вас есть мягкое удаление пользователей:
После удаления запись остаётся в таблице, но появляется дата удаления.
Обычный уникальный индекс создаст проблему:
Теперь пользователь не сможет зарегистрироваться повторно с тем же email даже после удаления старого аккаунта.
Решение — частичный уникальный индекс:
Теперь PostgreSQL будет проверять уникальность только среди активных пользователей.
Удалённые записи перестают участвовать в проверке.
Точно так же можно хранить:
В этом случае у пользователя может быть сколько угодно опубликованных статей, но только один черновик одновременно.
🔥 Полезный инструмент для ограничений, которые обычно пытаются реализовать через триггеры или код приложения.
➡️ SQL Ready | #совет
Допустим, у вас есть мягкое удаление пользователей:
SELECT email, deleted_at
FROM users;
После удаления запись остаётся в таблице, но появляется дата удаления.
Обычный уникальный индекс создаст проблему:
CREATE UNIQUE INDEX users_email_uniq
ON users (email);
Теперь пользователь не сможет зарегистрироваться повторно с тем же email даже после удаления старого аккаунта.
Решение — частичный уникальный индекс:
CREATE UNIQUE INDEX users_active_email_uniq
ON users (email)
WHERE deleted_at IS NULL;
Теперь PostgreSQL будет проверять уникальность только среди активных пользователей.
Удалённые записи перестают участвовать в проверке.
Точно так же можно хранить:
CREATE UNIQUE INDEX one_draft_per_user
ON posts (user_id)
WHERE status = 'draft';
В этом случае у пользователя может быть сколько угодно опубликованных статей, но только один черновик одновременно.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥8❤4🤝1
This media is not supported in your browser
VIEW IN TELEGRAM
Этот репозиторий собрал самые важные команды и конструкции PostgreSQL в одном месте. Здесь можно быстро вспомнить синтаксис запросов, работу с таблицами, индексами, представлениями, функциями, транзакциями и др. Такая шпаргалка особенно полезна во время работы или подготовки к собеседованию.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥6❤3
DEFERRABLE constraints — проверяй связи в конце транзакции, а не после каждой строки!
Иногда данные нужно вставить в несколько связанных таблиц внутри одной транзакции, но порядок операций временно нарушает внешний ключ.
Обычный
При необходимости проверку можно принудительно выполнить раньше прямо внутри транзакции.
Это полезно при сложных импортах, циклических связях, миграциях и переносе данных между системами, где временный неправильный порядок вставки допустим, но финальное состояние должно быть строго консистентным.
🔥 Фишка в том, что ты не отключаешь целостность данных, а просто переносишь момент проверки.
➡️ SQL Ready | #совет
Иногда данные нужно вставить в несколько связанных таблиц внутри одной транзакции, но порядок операций временно нарушает внешний ключ.
BEGIN;
INSERT INTO payments (id, order_id)
VALUES (1, 100);
INSERT INTO orders (id)
VALUES (100);
COMMIT;
Обычный
FOREIGN KEY проверяется сразу, поэтому первый INSERT упадёт, если заказа 100 ещё нет.DEFERRABLE INITIALLY DEFERRED переносит проверку ограничения на момент COMMIT, поэтому база смотрит не на промежуточное состояние, а на итог транзакции.SET CONSTRAINTS ALL IMMEDIATE;
При необходимости проверку можно принудительно выполнить раньше прямо внутри транзакции.
Это полезно при сложных импортах, циклических связях, миграциях и переносе данных между системами, где временный неправильный порядок вставки допустим, но финальное состояние должно быть строго консистентным.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥5❤4