7786
Авторский канал про Базы Данных и SQL Ресурсы, гайды, задачи, шпаргалки. Информация ежедневно пополняется! Автор: @energy_it
🐱 Крутейшая статья вышла на Хабре: «Научил ИИ-агента помнить важное и забывать лишнее в SQLite»!
В этой статье:• Разбирается, почему классический RAG плохо подходит для долгоживущих AI-агентов;
• Показана архитектура когнитивной памяти на SQLite: граф узлов и рёбер, сущности, гибридный поиск и механизм разрешения конфликтов знаний;
• Объясняется, как реализовать память агента с механизмом забывания и консолидацией фактов.
🔊 Продолжайте читать на Habr!
❤️ RusauSQL — подробный учебник по SQL для тестировщиков и разработчиков!
Это большой гайд по SQL, где последовательно объясняется, как работать с базами данных и писать запросы. На сайте разбираются фундаментальные вещи: что такое SQL, как устроены таблицы и базы данных, как выполнять запросы, проверять данные и взаимодействовать с сервером через язык запросов. Отличный ресурс, если хочешь разобраться в SQL с практической стороны и понять работу баз данных.
📌 Оставляю ссылочку: rusau.net
➡️ SQL Ready | #сайт
📂 Напоминалка по SQL JOIN!
Например, LEFT JOIN позволяет получить все строки из левой таблицы, даже если соответствующих записей в правой таблице нет, а FULL OUTER JOIN возвращает все строки из обеих таблиц, заполняя отсутствующие значения NULL.
На изображении показаны 4 основных типа SQL JOIN, которые чаще всего используются при объединении данных из нескольких таблиц.
📌 Сохрани, чтобы не потерять!
➡️ SQL Ready | #ресурс
💡 Rows — умные таблицы с встроенным ИИ!
Сервис для работы с данными в формате таблиц (аналог Excel / Google Sheets) с интегрированными AI-инструментами. Позволяет анализировать данные, создавать формулы по описанию и подтягивать информацию из интернета и API. Подходит для аналитики, автоматизации и обработки больших массивов данных.
📌 Оставляю ссылочку: rows.com
➡️ SQL Ready | #ресурс
☕️ Полезную статью нашёл на Хабре: «Инженерная история: добавляем 3-ю СУБД в карточный процессинг»!
В этой статье:• Разобрано, зачем в высоконагруженной системе может понадобиться сразу несколько СУБД и как между ними строится абстрактный слой доступа к данным;
• Показан опыт интеграции распределённой базы данных YDB в существующую архитектуру;
• Разбираются технические нюансы: батч-операции, строгая типизация запросов, особенности драйверов и результаты нагрузочного тестирования.
🔊 Продолжайте читать на Habr!
❤️ Stepik SQL — практический курс с примерами запросов и упражнениями!
Этот репозиторий содержит структурированные задания, схемы баз данных и примеры SQL-запросов из курса на Stepik. Материалы охватывают работу с таблицами, фильтрацию, JOIN, группировки, агрегаты и другие ключевые темы языка, с акцентом на практику. Отличный ресурс для тех, кто хочет закрепить теорию через задачи.
Оставляю ссылочку: GitHub 📱
📂 Напоминалка: как выполняется SQL-запрос в базе данных!
Например, когда отправляешь SELECT, UPDATE или другой SQL, СУБД сначала обрабатывает его внутренне, а уже потом выполняет.
На схеме — путь запроса: приём — разбор (парсер) — оптимизация — выполнение — доступ к данным — буферы, транзакции и блокировки — чтение/запись (память или диск).
Сохрани, чтобы не забыть!
➡️ SQL Ready | #ресурс
🖥 Oracle: работа с датой и временем!Основные функции и приёмы работы с календарными значениями в Oracle: получение текущих даты и времени на стороне БД, вычисление интервалов, смещение по месяцам, усечение до нужной гранулярности, извлечение компонентов даты, а также корректное преобразование строк в DATE и TIMESTAMP. Полезно для отчётов, аналитики, планирования, ETL-процессов и любой логики, связанной с датами и временем.
➡️ SQL Ready | #шпора
Проверка совпадений сразу по нескольким полям одной операцией!
Когда нужно сравнить строки по составному ключу, многие пишут условия с AND, которые хуже читаются и сложнее поддерживаются:
u.email = b.email AND u.phone = b.phone
(email, phone)
NULL и наличие составных индексов, оптимизация не всегда идентична AND.
✍️ Bilabon — задачи и теория по MySQL!
Это шпаргалка с ключевой теорией и упражнениями: управление БД и таблицами, типы данных, индексы, транзакции и оптимизация запросов. Также приведены практические задачи, позволяющие закрепить знания на примерах реальных предметных областей.
Оставляю ссылочку: GitHub 📱
📂 Напоминалка по SQL — базовые команды и порядок выполнения запроса!
Например, SELECT отвечает за выборку данных, FROM — за указание источника (таблицы), а WHERE позволяет отфильтровать строки по условию.
На картинке — основные операторы SQL, типы JOIN’ов и правильный порядок выполнения запроса внутри СУБД.
Сохрани, чтобы не забыть!
➡️ SQL Ready | #ресурс
💡 SQL Fiddle — онлайн платформа для тестирования SQL без установки БД!
Сервис позволяет писать, запускать и проверять SQL-запросы прямо в браузере. Можно создать схему базы, заполнить тестовыми данными и сразу увидеть результат выполнения, идеально для экспериментов, обучения и разбора задач. Поддерживает разные СУБД (MySQL, PostgreSQL, Oracle, SQL Server и др.), поэтому удобно сравнивать поведение запросов.
📌 Оставляю ссылочку: sqlfiddle.com
➡️ SQL Ready | #сайт
TRUNC(date) — как правильно обрезать дату в Oracle без потери индекса!
В Oracle тип DATE хранит дату и время одновременно. Частая задача — сравнить только по дате, игнорируя время. Многие используют TRUNC(date), но это может сильно ухудшить производительность.
Представим таблицу:
orders(id, created_at DATE)
SELECT *
FROM orders
WHERE TRUNC(created_at) = DATE '2026-02-18';
created_at в основном не используется, и Oracle вынужден вычислять TRUNC для множества строк, что часто приводит к полному сканированию.SELECT *
FROM orders
WHERE created_at >= DATE '2026-02-18'
AND created_at < DATE '2026-02-19';
created_at.TRUNC(date) полезен в SELECT или GROUP BY, когда фильтрация по индексу не критична:SELECT
TRUNC(created_at) AS day,
COUNT(*) AS orders_count
FROM orders
GROUP BY TRUNC(created_at)
ORDER BY day;
TRUNC(created_at), можно создать функциональный индекс:CREATE INDEX idx_orders_created_day
ON orders (TRUNC(created_at));
TRUNC сможет использовать индекс.TRUNC умеет обрезать дату до разных уровней:SELECT
TRUNC(SYSDATE) AS day,
TRUNC(SYSDATE, 'MM') AS month_start,
TRUNC(SYSDATE, 'YYYY') AS year_start
FROM dual;
DATE хранит дату со временем, поэтому = DATE '2026-02-18' не находит все записи за день; функции над колонками мешают индексам.TRUNC — для отображения, агрегации или вместе с функциональным индексом.
Проверка наличия связанных данных!
Когда нужно проверить существование связанных строк, обычный JOIN часто увеличивает результат или заставляет делать DISTINCT, что бьёт по производительности:
SELECT *
FROM orders o
WHERE EXISTS (
SELECT 1 FROM payments p
WHERE p.order_id = o.id
);
EXISTS останавливается на первой найденной строке и не создаёт дубликатов, поэтому обычно работает быстрее и предсказуемее:SELECT *
FROM orders o
WHERE NOT EXISTS (
SELECT 1 FROM payments p
WHERE p.order_id = o.id
);
NOT EXISTS - наиболее корректный способ найти записи без соответствующих связанных данных. NOT IN он корректнее работает при наличии NULL:SELECT *
FROM orders o
LEFT JOIN payments p ON p.order_id = o.id
WHERE p.order_id IS NULL;
LEFT JOIN + IS NULL делает то же самое логически, но чаще требует больше работы оптимизатора и может уступать по производительности на больших объёмах.
LATERAL — коррелированные подзапросы!
Часто бывает нужно для каждой строки основной таблицы взять одну лучшую связанную строку - последнюю, первую, максимальную, минимальную, валидную.
Обычно это делают через оконки, CTE или GROUP BY + JOIN, что сложно читать и часто ломается при расширении логики:
LEFT JOIN LATERAL (...)
ORDER BY created_at DESC
LIMIT 1
ON true
SEMI JOIN через EXISTS — как корректно проверять наличие связанных строк!
Частая задача в SQL — выбрать строки из одной таблицы, если в другой таблице существует хотя бы одна связанная запись.
Таблицы:
customers(id, name)
orders(id, customer_id, created_at)
JOIN:SELECT
c.id,
c.name
FROM customers c
JOIN orders o
ON o.customer_id = c.id;
DISTINCT:SELECT DISTINCT
c.id,
c.name
FROM customers c
JOIN orders o
ON o.customer_id = c.id;
JOIN формирует строки для каждого совпадения, а DISTINCT затем устраняет повторяющиеся значения.customers, если существует хотя бы одна связанная строка в orders.EXISTS:SELECT
c.id,
c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
orders, связанной с текущим клиентом.EXISTS является естественным инструментом для проверки существования строк.SELECT
c.id,
c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
AND o.created_at >= '2025-01-01'
);
EXISTS не возвращает данные из подзапроса — он только проверяет факт существования строки.SELECT 1
SELECT *
EXISTS список выбираемых выражений не влияет на результат.EXISTS — стандартный и выразительный способ реализовать semi join. Он точно отражает намерение запроса и часто позволяет оптимизатору построить более эффективный план выполнения.
Атомарное перемещение данных между таблицами!
Иногда нужно архивировать старые данные: удалить их из основной таблицы и одновременно сохранить в архиве. Многие делают это двумя запросами (INSERT => DELETE):
DELETE FROM orders
RETURNING *
RETURNING позволяет сразу вернуть удалённые строки как результирующий набор, не выполняя повторный SELECT.WITH moved AS (...)
CTE превращает результат удаления во временный набор данных, который можно использовать в следующей операции:INSERT INTO orders_archive
SELECT * FROM moved;
🖥 Анализ средней длительности сессий пользователей по устройствам!
В этой задаче напишем SQL-запрос, который поможет вычислить среднее время сессии для пользователей на разных устройствах за последние 30 дней.
Что делаем:
• Фильтруем сессии по времени и устройствам.
• Считаем длительность каждой сессии.
• Группируем и находим среднее время по типам устройств.
Агрегирование по группам с оконными функциями!
Когда нужно посчитать агрегаты по группе и при этом сохранить исходные строки, многие делают подзапрос с GROUP BY и потом джойнят его обратно:
SUM(o.total) OVER (PARTITION BY o.user_id)
GROUP BY, ни повторное соединение:AVG(o.total) OVER (PARTITION BY o.user_id)
COUNT(*) OVER (PARTITION BY o.user_id)
Почему OFFSET может ломать производительность пагинации?OFFSET выглядит удобно для страниц, но база всё равно должна просканировать и пропустить все предыдущие строки. Поэтому чем дальше страница, тем медленнее запрос:
OFFSET 100000
OFFSET:SELECT *
FROM orders
WHERE id > :last_seen_id
ORDER BY id
LIMIT 10;
keyset-pagination — продолжать выборку от последнего значения ключа, а не пропускать строки:WHERE id > :last_seen_id
:last_seen_id — это id последней строки предыдущей страницы.created_at), добавляйте tie-breaker:ORDER BY created_at, id
WHERE (created_at, id) > (:last_seen_created_at, :last_seen_id)
Анти-JOIN — как корректно выбирать строки без связанных записей!
Частая задача в SQL — найти строки, для которых отсутствуют связанные записи в другой таблице. Это классический anti-join. Например, нужно получить клиентов, которые ни разу не сделали заказ.
Таблицы:
customers(id, name)
orders(id, customer_id, created_at)
LEFT JOIN + IS NULL:SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
WHERE o.id IS NULL;
LEFT JOIN возвращает всех клиентов. Если совпадений нет, поля из orders становятся NULL, и фильтр оставляет только клиентов без заказов.NOT NULL колонку правой таблицы (обычно PK, например o.id), иначе возможны логические ошибки.NOT EXISTS (предпочтительно):SELECT c.id, c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
LEFT JOIN, но лучше отражает намерение, устойчив к NULL в подзапросе и часто даёт более предсказуемый план при усложнении условий. NOT IN:SELECT c.id, c.name
FROM customers c
WHERE c.id NOT IN (
SELECT customer_id
FROM orders
);
NULL, результат может стать пустым из-за трёхзначной логики SQL. Использовать этот вариант безопасно только при гарантированном NOT NULL в orders.customer_id.JOIN:SELECT c.id
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
AND o.created_at >= DATE '2026-01-01'
WHERE o.id IS NULL;
JOIN нельзя — это меняет бизнес-смысл. В таком случае корректнее использовать NOT EXISTS.orders(customer_id). При частой фильтрации по дате логичен составной индекс (customer_id, created_at).
❤️ Мега интересную статью нашёл на Хабре: «DOOMQL: DOOM-подобный многопользовательский шутер на чистом SQL»!
В этой статье:• Показано, как реализовать полноценную игру, от хранения состояния до рендеринга 3D-сцены — средствами одной лишь базы данных;
• Разобрана архитектура, игровой цикл на транзакциях и многопользовательская синхронизация через SQL;
• На реальном проекте демонстрируются неожиданные возможности СУБД далеко за пределами типичных CRUD-задач.
🔊 Продолжайте читать на Habr!
Дедупликация с приоритетом, оставляем лучшую строку!
Когда есть дубликаты по ключу (например, email), нужно сохранить запись с максимальным приоритетом — подтверждённую или самую новую.
Таблица: users(id, email, is_verified, created_at)
Сначала можно увидеть, какая строка будет считаться главной. Нумеруем строки внутри каждого email по приоритету:
SELECT id, email,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY is_verified DESC, created_at DESC
) rn
FROM users;
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY email
ORDER BY is_verified DESC, created_at DESC
) rn
FROM users
) t
WHERE rn = 1;
SELECT DISTINCT ON (email)
id, email, is_verified, created_at
FROM users
ORDER BY email, is_verified DESC, created_at DESC;
SYSDATE в условиях — почему = SYSDATE почти никогда не то, что нужно!
В Oracle SYSDATE возвращает текущие дату и время с точностью до секунды. Частая ошибка — пытаться выбрать записи «за сегодня» через прямое сравнение.
Представим таблицу:
orders(id, created_at DATE)
SELECT *
FROM orders
WHERE created_at = SYSDATE;
DATE в Oracle хранит и дату, и время (до секунды). Чтобы совпадение произошло, created_at должен быть ровно равен текущему моменту до секунды. На практике такой запрос почти всегда возвращает пустой результат.TRUNC:SELECT *
FROM orders
WHERE TRUNC(created_at) = TRUNC(SYSDATE);
created_at не используется (если только нет function-based index на TRUNC(created_at)).SELECT *
FROM orders
WHERE created_at >= TRUNC(SYSDATE)
AND created_at < TRUNC(SYSDATE) + 1;
WHERE created_at >= DATE '2026-02-18'
AND created_at < DATE '2026-02-19'
DATE хранит время, прямое равенство по дате почти никогда не подходит для выборки «за день».SYSDATE.
🖥 Объединяем пересекающиеся интервалы автоматически!
В реальных данных интервалы часто перекрываются или соприкасаются, из-за чего сложно понять фактические периоды непрерывной активности или занятости.
Сегодня в задаче:
• Определим, насколько далеко тянется текущая занятость, отслеживая максимальный конец интервалов;
• Найдём точки разрыва, где начинается новый независимый блок времени;
• Объединим пересечения в итоговые непрерывные диапазоны.
Вычисление временных интервалов между датами!
PostgreSQL умеет возвращать полноценный интервал одной операцией:
now() - created_at
age(), которая учитывает календарь, а не просто секунды:SELECT age(now(), created_at)
FROM orders;
EXTRACT(EPOCH FROM now() - created_at) / 3600
FlowchartAI — это бесплатный AI-генератор блок-схем, который из текста или кода автоматически строит диаграмму. Он анализирует твой ввод (описание процесса/алгоритма или код) и превращает его в визуальную блок-схему, которую можно смотреть прямо в браузере без регистрации.
📌 Оставляю ссылочку: flowchartai.org
➡️ SQL Ready | #ресурс
💡 Отличную статью нашёл на Хабре: «Демобаза 2.0 для PostgreSQL»!
В этой статье:• Показано, как устроена учебная база, приближенная к реальным продакшен-сценариям с миллионами записей;
• Разобрано, как генерируются правдоподобные данные через имитацию работы системы, включая бронирования и жизненный цикл событий;
• Объясняется, какие задачи по SQL, аналитике и производительности можно отрабатывать на такой базе.
🔊 Продолжайте читать на Habr!
👍 TutorialsPoint — сайт с теорий и практикой в одном месте!
Удобный онлайн-редактор, где можно быстро проверить SQL-запросы без установки СУБД и настройки окружения. Встроенные примеры позволяют сразу попробовать SELECT, JOIN, агрегатные функции, подзапросы и другие конструкции на готовых таблицах. Подходит для обучения, практики, подготовки к собесам. Также можно использовать, как мини шпаргалку.
📌 Оставляю ссылочку: tutorialspoint.com
➡️ SQL Ready | #сайт
WHERE vs HAVING — фильтрация строк и фильтрация групп!
В SQL иногда путают WHERE и HAVING, потому что внешне они решают похожую задачу — отфильтровать результат. На практике это два разных этапа выполнения запроса с разной семантикой.
Представим таблицу заказов:
orders(id, customer_id, amount)
SELECT *
FROM orders
WHERE amount > 100;
WHERE применяется до агрегации и фильтрует отдельные строки исходной таблицы.SELECT customer_id, SUM(amount) AS total_amount
FROM orders
WHERE SUM(amount) > 1000
GROUP BY customer_id;
WHERE, потому что на момент применения WHERE агрегатов ещё не существует.HAVING:SELECT customer_id, SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 1000;
HAVING применяется после GROUP BY и фильтрует уже сформированные группы.WHERE — фильтрует строки до группировки; GROUP BY — формирует группы; HAVING — фильтрует группы; SELECT — формирует итоговый результат.WHERE в HAVING без необходимости:SELECT customer_id, SUM(amount)
FROM orders
GROUP BY customer_id
HAVING amount > 100;
amount — это поле строки, а HAVING работает с группой.SELECT customer_id, SUM(amount) AS total_amount
FROM orders
WHERE amount > 100
GROUP BY customer_id
HAVING SUM(amount) > 1000;
WHERE отсекает мелкие заказы до агрегации (меньше данных); HAVING проверяет условие на уровне группы.WHERE для фильтрации строк и HAVING — только для условий на агрегаты и группы. Это влияет не только на корректность, но и на производительность запроса