SQL Ready | Базы Данных
Авторский канал про Базы Данных и SQL Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_c РКН: https://clck.ru/3QREBc Реклама на бирже: https://telega.in/c/sql_ready
إظهار المزيد📈 نظرة تحليلية على قناة تيليجرام SQL Ready | Базы Данных
تُعد قناة SQL Ready | Базы Данных (@sql_ready) في القطاع اللغوي الروسية لاعباً نشطاً. يضم المجتمع حالياً 16 616 مشتركاً، محتلاً المرتبة 7 640 في فئة التكنولوجيات والتطبيقات والمرتبة 39 717 في منطقة روسيا.
📊 مؤشرات الجمهور والحراك
منذ تأسيسه في невідомо، حقق المشروع نمواً سريعاً وجمع 16 616 مشتركاً.
بحسب آخر البيانات بتاريخ 01 سبتمبر, 2026، تحافظ القناة على نشاط مستقر. خلال آخر 30 يوماً تغيّر عدد الأعضاء بمقدار 1 233، وفي آخر 24 ساعة بمقدار -2، مع بقاء الوصول العام مرتفعاً.
- حالة التحقق: غير موثّقة
- معدل التفاعل (ER): يبلغ متوسط تفاعل الجمهور 12.49%. وخلال أول 24 ساعة من النشر يحصد المحتوى عادةً 6.17% من ردود الفعل نسبةً إلى إجمالي المشتركين.
- وصول المنشورات: يحصل كل منشور على متوسط 2 075 مشاهدة. وخلال اليوم الأول يجمع عادةً 1 025 مشاهدة.
- التفاعلات والاستجابة: يتفاعل الجمهور بانتظام؛ متوسط التفاعلات لكل منشور يبلغ 23.
- الاهتمامات الموضوعية: يركز المحتوى على مواضيع رئيسية مثل sql, строка, users, индекс, user_id.
📝 الوصف وسياسة المحتوى
يصف المؤلف القناة بأنها مساحة للتعبير عن الآراء الذاتية:
“Авторский канал про Базы Данных и SQL
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!
Автор: @energy_c
РКН: https://clck.ru/3QREBc
Реклама на бирже: https://telega.in/c/sql_ready”
بفضل وتيرة التحديث المرتفعة (أحدث البيانات بتاريخ 02 سبتمبر, 2026) تحافظ القناة على حداثتها ومستوى وصول مرتفع. وتُظهر التحليلات تفاعلاً نشطاً من الجمهور، ما يجعلها نقطة تأثير مهمة ضمن فئة التكنولوجيات والتطبيقات.
ROW_NUMBER(), RANK() и DENSE_RANK() все умеют добавлять номера к строкам, но поведение меняется, когда в данных появляются одинаковые значения.
На тестовых данных разница может быть незаметной. Но в реальных задачах это влияет на рейтинги пользователей, отчёты, аналитику и любые запросы, где важно правильное место записи.
Например, есть таблица с результатами:
score
-----
100
100
90
80
Если нужно просто пронумеровать строки после сортировки, обычно используют ROW_NUMBER():
SELECT
score,
ROW_NUMBER() OVER (
ORDER BY score DESC
) AS position
FROM results;
Результат:
score | position
------+---------
100 | 1
100 | 2
90 | 3
80 | 4
ROW_NUMBER() всегда выдаёт уникальный номер для каждой строки. Даже если два значения одинаковые, они всё равно получат разные позиции.
Это удобно, когда нужна обычная последовательная нумерация. Например, для пагинации, выбора последней записи из группы или получения одной строки после сортировки. Но для рейтингов такой вариант часто подходит не всегда.
Если два пользователя набрали одинаковое количество баллов, обычно ожидается, что они займут одинаковое место. Для такой задачи используется RANK():
SELECT
score,
RANK() OVER (
ORDER BY score DESC
) AS position
FROM results;
Результат:
score | position
------+---------
100 | 1
100 | 1
90 | 3
80 | 4
RANK() учитывает одинаковые значения и присваивает им одинаковый номер. Но после одинаковых результатов появляются пропуски. В примере выше два участника заняли первое место, поэтому следующий результат получает третье место.
Иногда такое поведение является правильным. Например, в спортивных рейтингах, где нужно учитывать фактическое количество участников с одинаковым результатом. Но бывают случаи, когда пропуски не нужны. Тогда используется DENSE_RANK():
SELECT
score,
DENSE_RANK() OVER (
ORDER BY score DESC
) AS position
FROM results;
Результат:
score | position
------+---------
100 | 1
100 | 1
90 | 2
80 | 3
DENSE_RANK() работает похоже на RANK(), но продолжает нумерацию без пропусков. Получается простое правило: ROW_NUMBER() — когда каждая строка должна получить свой уникальный номер; RANK() — когда одинаковые значения должны иметь одинаковое место, а пропуски после них допустимы; DENSE_RANK() — когда одинаковые значения должны иметь одинаковое место, но нумерация должна идти без пропусков.
Есть ещё один практический момент, который часто забывают при использовании ROW_NUMBER(). Если несколько строк имеют одинаковое значение для сортировки:
SELECT
id,
score,
ROW_NUMBER() OVER (
ORDER BY score DESC
) AS position
FROM results;
то порядок таких строк может быть нестабильным. База данных не обязана всегда возвращать их в одинаковой последовательности.
Поэтому в реальных проектах часто добавляют дополнительное поле для сортировки:
SELECT
id,
score,
ROW_NUMBER() OVER (
ORDER BY score DESC, id
) AS position
FROM results;
🔥 ROW_NUMBER(), RANK() и DENSE_RANK() решают похожую задачу, но предназначены для разных сценариев. Главное — понимать, нужна ли вам обычная нумерация строк или именно логика рейтинга.
➡️ SQL Ready | #практикаОставляю ссылочку: GitHub 📱➡️ SQL Ready | #репозиторий
UPDATE делают отдельный SELECT, чтобы получить изменённые данные.
Но PostgreSQL уже умеет вернуть результат изменения через RETURNING.
UPDATE orders
SET status = 'paid',
paid_at = now()
WHERE id = 1001
RETURNING id, status, paid_at;
Один запрос вместо двух. RETURNING можно сразу передавать дальше.
Например, обновили заказ и сразу записали историю изменения:
WITH changed AS (
UPDATE orders
SET status = 'cancelled'
WHERE id = 1001
RETURNING id, status
)
INSERT INTO order_history(order_id, new_status)
SELECT id, status
FROM changed;
Без промежуточных переменных и без повторного поиска строки.
Также можно использовать это для атомарного переноса данных:
WITH moved AS (
DELETE FROM queue
WHERE id = 55
RETURNING *
)
INSERT INTO archive_queue
SELECT *
FROM moved;
🔥 Получается "забрать строку и положить в другую таблицу" одной транзакционной операцией. Часто заменяет сложный код в сервисах и убирает лишние запросы к базе.
➡️ SQL Ready | #совет🥇место – 1 000 000 рублей 🥈место – 700 000 рублей 🥉место – 500 000 рублей Награда за 4-8 места - 100 000 рублейПройди заочный онлайн-этап и получи путевку на очный этап турнира в Республику Татарстан! Перелет, питание, проживание - за счет организаторов. 🇷🇺 Подать заявку и узнать подробности 🇷🇺
CONCAT_WS — хороший пример.
Когда работаешь с тестовыми данными, кажется, что собрать строку очень просто. Есть имя, фамилия, отчество — соединяем их через пробел и получаем красивый результат.
Обычно первый вариант выглядит так:
SELECT
first_name || ' ' || middle_name || ' ' || last_name AS full_name
FROM users;
На первый взгляд всё нормально. Но потом база начинает жить настоящими данными. У одного пользователя нет отчества. У другого не заполнена фамилия. После миграции часть полей приходит пустыми. И вот здесь простой запрос начинает вести себя не так, как ожидает разработчик.
Например:
first_name = 'John'
middle_name = NULL
last_name = 'Smith'
Кажется логичным получить:
John Smith
Но PostgreSQL вернёт:
NULL
Причина в том, что NULL — это не пустое значение. Это отсутствие значения. При обычной конкатенации строк неизвестная часть делает неизвестным весь результат.
Конечно, можно начать добавлять COALESCE:
SELECT
first_name || ' ' ||
COALESCE(middle_name || ' ', '') ||
last_name
FROM users;
Для двух-трёх полей это ещё нормально. Но в реальном запросе таких частей может быть намного больше: адрес, название компании, описание товара, дополнительные параметры.
И постепенно вместо простой сборки строки появляется большая конструкция из проверок.
В PostgreSQL для таких задач есть более удобный вариант:
SELECT
CONCAT_WS(
' ',
first_name,
middle_name,
last_name
) AS full_name
FROM users;
CONCAT_WS расшифровывается как "concatenate with separator". Первый аргумент — разделитель, который нужно использовать между значениями.
Главная особенность в том, что функция сама пропускает NULL. То есть:
John + NULL + Smith
превратится в:
John Smith
а не в строку с лишним пробелом или неожиданный NULL.
На практике это особенно удобно, когда собираете данные для отображения пользователю. Например, адрес:
Germany, Berlin, Alexanderplatz 10
может состоять из нескольких частей, но квартира или дополнительный номер могут отсутствовать. Не хочется писать отдельные проверки для каждого поля только ради красивого вывода.
Но есть нюанс, который часто забывают. CONCAT_WS пропускает NULL, но пустую строку считает настоящим значением.
Например, после загрузки данных из CSV можно получить:
middle_name = ''
Для пользователя это выглядит так же, как отсутствие значения. Но PostgreSQL различает эти случаи. В результате можно получить: John Smith с двойным пробелом.
Поэтому в системах, где данные приходят из разных источников, часто сначала нормализуют значения:
NULLIF(middle_name, '')
После этого пустые строки становятся NULL, и CONCAT_WS уже работает предсказуемо. На небольших примерах это кажется мелочью. Но когда у тебя несколько миллионов записей и данные собираются из разных сервисов, именно такие детали начинают влиять на качество данных и количество лишнего кода.
🔥 CONCAT_WS — хороший пример того, как правильный инструмент делает SQL проще. Если нужно собрать строку из нескольких необязательных полей — часто лучше использовать её, чем вручную обрабатывать каждый NULL.
➡️ SQL Ready | #практикаold_value IS DISTINCT FROM new_value
В SQL NULL никогда не равен даже другому NULL, поэтому сравнение начинает вести себя не так, как ожидается.
SELECT
NULL = NULL,
NULL != NULL;
Оба выражения вернут не TRUE и не FALSE, а NULL. Именно из-за этого часто ломается логика сравнения.
SELECT *
FROM users
WHERE old_email != new_email;
Кажется, что запрос покажет все изменённые email. Но если одно из значений NULL, строка может просто не попасть в выборку.
Для таких случаев в PostgreSQL есть специальный оператор.
SELECT *
FROM users
WHERE old_email IS DISTINCT FROM new_email;
Теперь NULL корректно участвует в сравнении. PostgreSQL считает NULL обычным сравниваемым состоянием.
NULL IS DISTINCT FROM NULL -- false
NULL IS DISTINCT FROM 'test' -- true
'a' IS DISTINCT FROM 'b' -- true
Пригодится в аудитах изменений, UPSERT-логике, ETL и триггерах, где важно понять: значение реально изменилось или нет.
🔥 Оператор, который убирает большое количество багов при работе с NULL.
➡️ SQL Ready | #совет• Объясняется разница между шардированным PostgreSQL и настоящими распределёнными СУБД;
• Разбирается, почему Citus и похожие решения не дают полноценные ACID-гарантии для multi-shard транзакций;
• Показывается, как работают 2PC, distributed snapshot и уровни изоляции транзакций;
• На примерах объясняются проблемы consistency и eventual consistency в распределённых системах;
🔊 Продолжай читать на Habr!➡️ 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;
При необходимости проверку можно принудительно выполнить раньше прямо внутри транзакции.
Это полезно при сложных импортах, циклических связях, миграциях и переносе данных между системами, где временный неправильный порядок вставки допустим, но финальное состояние должно быть строго консистентным.
🔥 Фишка в том, что ты не отключаешь целостность данных, а просто переносишь момент проверки.
➡️ SQL Ready | #советОставляю ссылочку: GitHub 📱➡️ 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';
В этом случае у пользователя может быть сколько угодно опубликованных статей, но только один черновик одновременно.
🔥 Полезный инструмент для ограничений, которые обычно пытаются реализовать через триггеры или код приложения.
➡️ SQL Ready | #совет🟦Для безопасности: готовые конструкции для проверки кода на уязвимости и анализа граничных условий работы алгоритмов.
🟦Для разработчиков: шаблоны для глубокого ревью кода, автоматического поиска багов, рефакторинга, написания юнит-тестов и проектирования архитектуры.
🟦Для автоматизации и менеджмента: промпты под стратегическое планирование, парсинг неструктурированных данных, генерацию технической документации и выстраивание логики для ИИ-агентов.Каждый шаблон снабжен подробным разбором: авторы пошагово объясняют, почему выбрана именно такая структура запроса, как модель интерпретирует переменные и как правильно передавать контекст. Поскольку библиотека официальная, все промпты оптимизированы под особенности контекстного окна и логику мышления последних моделей семейства Claude. ❤️🩹Сохраняйте в закладки и внедряйте в свои рабочие процессы. Нейросети отлично автоматизируют рутину и помогают искать уязвимости, но управлять ими может только тот, кто понимает саму базу и суть киберугроз. Если вы хотите заложить мощный фундамент начните с нашего бесплатного курса (его прошли уже 1108 человек!).
🟦Разберетесь, как работают кибератаки и как грамотно защитить свои данные.
🟦Познакомитесь с направлениями и профессиями в кибербезопасности, чтобы выбрать свой трек.
🟦Получите скидку, если решите продолжить обучение в CyberYozh Academy.👉 Начните бесплатно прямо сейчас 🦔 CyberYozh
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.
➡️ SQL Ready | #практика