SQL Ready | Базы Данных
Авторский канал про Базы Данных и SQL Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_it РКН: https://clck.ru/3QREBc Реклама на бирже: https://telega.in/c/sql_ready
Показати більше📈 Аналітичний огляд Telegram-каналу SQL Ready | Базы Данных
Канал SQL Ready | Базы Данных (@sql_ready) у мовному сегменті Російська є активним учасником. На даний момент спільнота об'єднує 15 560 підписників, посідаючи 8 395 місце в категорії Технології та додатки та 43 172 місце у регіоні Росія.
📊 Показники аудиторії та динаміка
З моменту свого створення невідомо, проект продемонстрував стрімке зростання, зібравши аудиторію у 15 560 підписників.
За останніми даними від 10 червня, 2026, канал демонструє стабільну активність. Хоча за останні 30 днів спостерігається зміна кількості учасників на 57, а за останні 24 години на -8, загальне охоплення залишається високим.
- Статус верифікації: Не верифікований
- Рівень залученості (ER): Середній показник залученості аудиторії становить 11.95%. Протягом перших 24 годин після публікації контент зазвичай збирає 6.07% реакцій від загальної кількості підписників.
- Охоплення публікацій: В середньому кожен допис отримує 1 860 переглядів. Протягом першої доби публікація в середньому набирає 945 переглядів.
- Реакції та взаємодія: Аудиторія активно підтримує контент: середня кількість реакцій на один пост – 25.
- Тематичні інтереси: Контент зосереджений навколо ключових тем, таких як sql, строка, user_id, created_at, desc.
📝 Опис та контентна політика
Автор описує ресурс як майданчик для висловлення суб'єктивної думки:
“Авторский канал про Базы Данных и SQL
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!
Автор: @energy_it
РКН: https://clck.ru/3QREBc
Реклама на бирже: https://telega.in/c/sql_ready”
Завдяки високій частоті оновлень (останні дані отримано 11 червня, 2026), канал підтримує актуальність та високий рівень охоплення публікацій. Аналітика показує, що аудиторія активно взаємодіє з контентом, що робить його важливою точкою впливу в категорії Технології та додатки.
Оставляю ссылочку: GitHub 📱➡️ SQL Ready | #репозиторий
CHECK constraint требует, чтобы условие всегда было TRUE. Но это не так, CHECK запрещает только FALSE. А результат UNKNOWN — пропускается.
Именно поэтому nullable-колонки внутри CHECK могут вести себя не так, как ожидает разработчик.
Допустим, есть таблица товаров:
products(
id,
price,
discount
)
Хотим запретить скидку больше цены:
ALTER TABLE products
ADD CONSTRAINT chk_discount_price
CHECK (discount <= price);
На первый взгляд всё выглядит правильно.
Теперь такой INSERT действительно не пройдёт:
INSERT INTO products(id, price, discount)
VALUES (1, 100, 150);
Потому что проверка: 150 <= 100 даёт FALSE. А CHECK constraint запрещает строки, где выражение возвращает FALSE. Но дальше начинается важный нюанс SQL и трёхзначной логики.
Вот такой INSERT уже может пройти:
INSERT INTO products(id, price, discount)
VALUES (2, 100, NULL);
И такой тоже:
INSERT INTO products(id, price, discount)
VALUES (3, NULL, 50);
Многие ожидают, что CHECK отклонит такие строки. Но SQL работает иначе, если в сравнении участвует NULL, результатом становится не TRUE и не FALSE, а: UNKNOWN
То есть:
150 <= 100 -- FALSE
NULL <= 100 -- UNKNOWN
50 <= NULL -- UNKNOWN
NULL <= NULL -- UNKNOWN
И вот здесь самая важная мысль: CHECK constraint считает строку валидной, если результат выражения — TRUE или UNKNOWN. Запрещается только явно FALSE.
Это поведение связано с SQL three-valued logic — логикой с тремя состояниями: TRUE, FALSE и UNKNOWN. Именно поэтому CHECK сам по себе НЕ заменяет NOT NULL. Если колонка обязательная — это нужно указывать отдельно.
Правильный вариант:
CREATE TABLE products(
id bigint PRIMARY KEY,
price numeric NOT NULL,
discount numeric NOT NULL,
CONSTRAINT chk_discount_price
CHECK (discount <= price)
);
Теперь NULL уже не сможет пройти, потому что NOT NULL сработает раньше CHECK. Но в реальных системах скидка часто может отсутствовать. То есть NULL — это нормальное состояние: скидки нет.
В таком случае constraint лучше писать явно и читаемо:
ALTER TABLE products
ADD CONSTRAINT chk_discount_price
CHECK (
discount IS NULL
OR discount <= price
);
Такой вариант намного понятнее при чтении схемы. Он явно показывает бизнес-логику: либо скидки нет, либо она не больше цены. Но здесь есть ещё один тонкий момент.
Если price остаётся nullable: price numeric, то выражение: discount <= price снова может вернуть UNKNOWN. Например:
discount = 50
price = NULL
Результат проверки: 50 <= NULL, будет UNKNOWN, а строка снова станет валидной.
Поэтому если цена обязательна — нужен отдельный NOT NULL:
price numeric NOT NULL
Похожая ситуация встречается с датами. Например:
CHECK (end_date >= start_date)
Разработчик может думать, что constraint гарантирует корректный диапазон дат. Но если end_date nullable, такой CHECK спокойно пропускает:
end_date = NULL
потому что результат сравнения снова UNKNOWN.
И это может быть абсолютно нормальным поведением. Например, если NULL означает: период ещё не завершён. Но если обе даты обязательны, это нужно фиксировать явно:
start_date date NOT NULL,
end_date date NOT NULL,
CHECK (end_date >= start_date)
🔥 Вывод: если в CHECK участвуют nullable-поля, constraint может пропускать строки из-за UNKNOWN, CHECK не заменяет NOT NULL. Для обязательных значений всегда нужен отдельный NOT NULL constraint.
➡️ SQL Ready | #практикаSKIP LOCKED только для очередей через SELECT ... FOR UPDATE. Но мало кто использует его для параллельной пакетной обработки внутри PostgreSQL.
Если несколько воркеров одновременно обрабатывают огромную таблицу задач:
SELECT id
FROM jobs
WHERE processed = false
FOR UPDATE;
Без SKIP LOCKED процессы начинают ждать друг друга даже при наличии свободных строк.
PostgreSQL позволяет просто пропускать уже занятые записи:
SELECT id
FROM jobs
WHERE processed = false
FOR UPDATE SKIP LOCKED;
Теперь каждый воркер мгновенно получает только свободные строки без ожидания и конфликтов.
Это можно встроить прямо в UPDATE:
WITH cte AS (
SELECT id
FROM jobs
WHERE processed = false
LIMIT 100
FOR UPDATE SKIP LOCKED
)
UPDATE jobs
SET processed = true
WHERE id IN (SELECT id FROM cte);
Получается параллельная обработка на уровне PostgreSQL без внешних систем очередей.
🔥 Аналогично строят высоконагруженные фоновые обработчики, обработку писем, биллинг и массовые пакетные операции.
➡️ SQL Ready | #советusers(id, email)
orders(id, user_id)
Многие пытаются писать через NOT IN:
id="x8d2qa"
SELECT *
FROM users
WHERE id NOT IN (
SELECT user_id
FROM orders
);
Но здесь есть проблема: если подзапрос вернёт хотя бы один NULL, результат может стать пустым.
Причина — логика NULL в SQL ломает сравнение NOT IN.
Решением может служить антиджойн через NOT EXISTS:
id="m4z7pk"
SELECT *
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
);
SQL проверяет отсутствие связанной строки и сразу останавливается при первом совпадении.
Ту же задачу можно решить через LEFT JOIN:
id="f1q9vc"
SELECT u.*
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
WHERE o.id IS NULL;
LEFT JOIN оставляет все строки users, а WHERE o.id IS NULL отбирает только те, где совпадений не нашлось.
Этот паттерн и называется антиджойн — верни строки, для которых связи не существует.
Особенно полезно это в проверках целостности данных:
id="k6n2yb"
SELECT *
FROM orders o
WHERE NOT EXISTS (
SELECT 1
FROM users u
WHERE u.id = o.user_id
);
Так можно быстро найти битые записи с отсутствующими foreign key.
🔥 Антиджойны пригодятся в аналитике, ETL, аудитах данных и поиске проблемных связей между таблицами.
➡️ SQL Ready | #практикаВ описании она выглядела скучно, а по факту - одна из самых интересных компаний, с которыми я общался.Весь процесс - от первого собеседования до оффера - занял 4 дня. P.S. Попробовать Софи бесплатно можно будет 16 июня. Не пропусти анонс здесь.
Оставляю ссылочку: GitHub 📱➡️ SQL Ready | #репозиторий
CHECK или FOREIGN KEY на большую таблицу рискованная операция, потому что PostgreSQL начинает сразу сканировать все старые данные.
ALTER TABLE orders
ADD CONSTRAINT orders_user_fk
FOREIGN KEY (user_id)
REFERENCES users(id);
На production-таблицах в сотни строк это может превратиться в очень долгую блокировку DDL.
Но в PostgreSQL есть фича — NOT VALID:
ALTER TABLE orders
ADD CONSTRAINT orders_price_check
CHECK (price > 0)
NOT VALID;
Без полного сканирования таблицы, сразу начинает проверять все новые записи. При этом старые строки пока не валидируются.
Позже ограничение можно провалидировать отдельно:
ALTER TABLE orders
VALIDATE CONSTRAINT orders_price_check;
Самое интересное — VALIDATE CONSTRAINT не блокирует обычный concurrent DML как классический ALTER TABLE.
То же самое работает и для FOREIGN KEY:
ALTER TABLE orders
ADD CONSTRAINT orders_user_fk
FOREIGN KEY (user_id)
REFERENCES users(id)
NOT VALID;
🔥 Это одна из самых полезных возможностей PostgreSQL для безопасных миграций, постепенного внедрения ограничений и наведения порядка в старых базах без длительного простоя.
➡️ SQL Ready | #советВ этой шпаргалке собраны ключевые команды для создания, изменения и удаления пользователей, назначения и отзыва прав, а также проверки текущих ролей и сессий. Они применяются при управлении безопасностью базы данных, настройке доступа и аналитической работе с ролями.
➡️ SQL Ready | #шпора• Разбирается, почему массивы в PostgreSQL — это отдельная модель хранения со своими ограничениями и компромиссами;
• Показываются скрытые проблемы массивов: потеря ссылочной целостности, особенности GIN-индексов, TOAST, MVCC и дорогостоящие обновления;
• Объясняется, как правильно работать с массивами, когда использовать JSONB, intarray, pgvector и в каких случаях массивы действительно оправданы.
🔊 Продолжайте читать на Habr!➡️ SQL Ready | #статья
UPDATE даже тогда, когда данные вообще не изменились.
UPDATE users
SET name = 'Alex'
WHERE id = 1;
Даже если name уже равен 'Alex', PostgreSQL всё равно создаст новую версию строки (MVCC), запишет WAL, обновит индексы и увеличит нагрузку на autovacuum.
Проверить это можно через системную статистику:
SELECT n_tup_upd
FROM pg_stat_user_tables
WHERE relname = 'users';
Чтобы избежать “пустых” UPDATE, можно сравнивать старые и новые значения прямо в WHERE:
UPDATE users
SET
name = $1,
email = $2
WHERE id = $3
AND (name, email) IS DISTINCT FROM ($1, $2);
IS DISTINCT FROM безопасно сравнивает даже NULL значения, в отличие от обычного !=:
SELECT
NULL = NULL,
NULL IS DISTINCT FROM NULL;
На highload-системах это может заметно уменьшить WAL, bloat, количество HOT/non-HOT update и нагрузку на autovacuum без изменения архитектуры.
➡️ SQL Ready | #совет🐾 АИСыч напоминает: б бы «Backup — это не стратегия. Стратегия — это проверенное восстановление».На курсе отдельно разбираем, как проектировать PostgreSQL-инфраструктуру так, чтобы система переживала сбои без потери критичных данных. 🎁 Для наших подписчиков действует скидка 20% по промокоду:
SQLREADY20
Записаться на курс прямо сейчас
А ещё больше практики, ИИ и ИБ — в MAX Академии, где сейчас проходит розыгрыш курса за 1 рубль 🔥
Реклама АНО ДПО ЦПК "АИС", ИНН: 7720346012, erid: 2SDnjdvRDWzОставляю ссылочку: GitHub 📱➡️ SQL Ready | #репозиторий
users(
id,
email,
created_at
)
Индекс:
CREATE INDEX idx_users_email
ON users(email);
Кажется, что такой запрос точно должен использовать индекс:
SELECT *
FROM users
WHERE email = 'test@example.com';
И обычно действительно будет Index Scan.
Но достаточно небольшой детали — и индекс может перестать использоваться. Например:
SELECT *
FROM users
WHERE LOWER(email) = 'test@example.com';
Проблема в том, что индекс построен по колонке: email. А в условии используется выражение:
LOWER(email)
Для оптимизатора это уже не то же самое условие. В итоге серверу часто приходится: применять LOWER() к строкам; сравнивать результат; читать гораздо больше данных, чем ожидалось.
Исправляется это expression/functional index:
CREATE INDEX idx_users_email_lower
ON users(LOWER(email));
То же самое часто происходит с датами:
SELECT *
FROM orders
WHERE DATE(created_at) = '2025-01-10';
Из-за:
DATE(created_at)
обычный индекс по created_at может не помочь, потому что функция применяется к колонке.
Правильнее писать диапазон:
SELECT *
FROM orders
WHERE created_at >= '2025-01-10'
AND created_at < '2025-01-11';
Так условие остаётся sargable — то есть пригодным для эффективного использования индекса.
Ещё одна частая проблема — преобразования типов. Например, плохо:
WHERE user_id::text = '100'
Если user_id — integer, то здесь преобразование применяется к колонке. В такой ситуации обычный индекс по user_id может не использоваться.
Правильнее:
WHERE user_id = 100
Важно: простое условие вида:
WHERE user_id = '100'
в некоторых СУБД может нормально привести литерал к integer и всё равно использовать индекс. Проблема чаще начинается там, где преобразуется сама колонка или выражение становится сложнее.
Популярная ошибка с LIKE:
WHERE email LIKE '%gmail.com'
Здесь обычный B-Tree индекс обычно бесполезен. Так как поиск начинается не с начала строки; сервер не может эффективно использовать упорядоченность индекса.
Если таблица маленькая: 100–1000 строк, то Full Scan может быть дешевле, чем прыжки по индексу.
🔥 Наличие индекса ещё не гарантирует его использование. Функции над колонками, преобразования типов, неправильные LIKE, OR-условия и устаревшая статистика могут сделать индекс бесполезным или менее выгодным для оптимизатора.
➡️ SQL Ready | #практика
Вже доступно! Дослідження Telegram за 2025 — головні інсайти року 
