SQL Ready | Базы Данных
17K subscribers
1.35K photos
110 videos
2 files
750 links
Авторский канал про Базы Данных и SQL
Ресурсы, гайды, задачи, шпаргалки.
Информация ежедневно пополняется!

Cотрудничество: @energy_c

РКН: https://clck.ru/3QREBc
Download Telegram
📂 Шпаргалка по структурам данных!

Например, стек работает по принципу LIFO — последний добавленный элемент извлекается первым, а очередь наоборот использует FIFO — первым выходит тот, кто пришёл первым.

На картинке — 7 основных структур данных: массивы, связные списки, стек, очередь, хеш-таблица, дерево и граф. Полезная база, которую стоит знать каждому разработчику.

Сохрани, чтобы не потерять!

➡️ SQL Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍98🤝1
This media is not supported in your browser
VIEW IN TELEGRAM
😍 PostgreSQL Zero to Hero — большой русскоязычный материал по изучению PostgreSQL!

Здесь разобраны основы SQL, проектирование баз данных, индексы, транзакции, MVCC, WAL, оптимизация запросов, репликация, бэкапы и многое другое. Подойдёт разработчикам, которые хотят системно изучить PostgreSQL и разобраться не только с SQL, но и с архитектурой, производительностью, эксплуатацией базы данных.

Оставляю ссылочку: GitHub 📱


➡️ SQL Ready | #репозиторий
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍53🤝3
Оконный фрейм по группам значений!

ROWS считает физические строки. GROUPS считает группы строк с одинаковым значением ORDER BY.
SELECT price,
count(*) OVER (
ORDER BY price
GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING
)
FROM products;


Если по одной цене находится 50 товаров, вся эта пачка считается одной группой. Поэтому можно строить окна относительно соседних значений, не вычисляя dense_rank() и не добавляя ещё один уровень запроса.

Ещё интереснее EXCLUDE, который можно использовать прямо внутри оконного фрейма:
SELECT id,
avg(score) OVER (
ORDER BY created_at
ROWS BETWEEN 10 PRECEDING AND 10 FOLLOWING
EXCLUDE CURRENT ROW
) AS neighbors_avg
FROM measurements;


Среднее здесь считается по соседним строкам, но текущая строка автоматически исключается.
SELECT price,
sum(amount) OVER (
ORDER BY price
GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
EXCLUDE GROUP
)
FROM sales;


EXCLUDE GROUP исключит из расчёта всю текущую peer-группу, а не только одну строку.

🔥 GROUPS + EXCLUDE позволяют описывать сложные окна непосредственно в OVER, где обычно появляются dense_rank(), дополнительные CTE и self join.

➡️ SQL Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥75
Составные индексы: правила проектирования и типичные ошибки!

Составные индексы используются для ускорения запросов по нескольким колонкам, но порядок полей внутри индекса напрямую влияет на его эффективность.

Представим таблицу заказов, где часто выполняются запросы по пользователю и дате создания заказа.
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
status VARCHAR(30),
created_at TIMESTAMP NOT NULL,
amount NUMERIC(10,2)
);


Обычный индекс на каждую колонку не всегда является оптимальным решением. СУБД может использовать несколько индексов одновременно, но такой план не всегда будет эффективнее одного правильно спроектированного составного индекса.
CREATE INDEX idx_orders_user
ON orders(user_id);

CREATE INDEX idx_orders_created_at
ON orders(created_at);


Для частого сценария поиска заказов конкретного пользователя за период лучше создать составной индекс:
CREATE INDEX idx_orders_user_created_at
ON orders(user_id, created_at);


Такой индекс эффективно работает для запросов, где используется первая колонка индекса или полный набор колонок.
SELECT *
FROM orders
WHERE user_id = 42
AND created_at >= '2026-01-01';


В B-tree индексе данные сначала сортируются по user_id, а внутри одинаковых значений user_id — по created_at.

Поэтому СУБД быстро находит записи пользователя и затем выполняет поиск по диапазону дат. Также индекс будет использоваться:
SELECT *
FROM orders
WHERE user_id = 42;


Но он плохо подходит для поиска только по второй колонке:
SELECT *
FROM orders
WHERE created_at >= '2026-01-01';


Причина в структуре B-tree: данные сначала организованы по первой колонке индекса.

Для такого запроса отдельный индекс по дате будет более подходящим:
CREATE INDEX idx_orders_created_at
ON orders(created_at);


Ещё одна распространённая ошибка — добавление большого количества колонок в индекс.
CREATE INDEX idx_orders_all_columns
ON orders(user_id, status, created_at, amount);


Каждый дополнительный столбец увеличивает размер индекса и стоимость операций записи.

Перед созданием индекса нужно анализировать реальные запросы приложения, а не добавлять поля на всякий случай.
EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE user_id = 42
AND status = 'paid'
ORDER BY created_at DESC;


Для такого запроса может быть эффективнее индекс, учитывающий фильтрацию и сортировку:
CREATE INDEX idx_orders_user_status_created_at
ON orders(user_id, status, created_at DESC);


Правильно спроектированный составной индекс уменьшает количество операций чтения, снижает нагрузку на CPU и помогает оптимизатору выбрать более дешёвый план выполнения.

🔥 Главное правило простое: индекс создаётся под реальные запросы приложения, а не просто под структуру таблицы.

➡️ SQL Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍128🤝5
🖥 Oracle — агрегация данных и отчётность!

Шпаргалка по агрегации данных и формированию отчётности в Oracle SQL: объединение значений с LISTAGG, выбор значений по рангу с KEEP (DENSE_RANK FIRST/LAST), построение промежуточных и общих итогов с ROLLUP и GROUPING SETS, а также идентификация уровней агрегации с помощью GROUPING и GROUPING_ID. Полезно для построения сводных выборок, многоуровневых отчётов и обработки агрегированных данных непосредственно средствами SQL.

➡️ SQL Ready | #шпора
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍106🤝4
Почему порядок колонок в составном индексе важен!

Составной индекс часто создают, когда запрос фильтрует данные сразу по нескольким колонкам. Но просто добавить нужные поля в индекс недостаточно — их порядок влияет на то, какую часть индекса PostgreSQL сможет эффективно использовать.

Есть таблица:
CREATE TABLE orders (
id BIGINT,
user_id BIGINT,
status VARCHAR(20),
created_at TIMESTAMPTZ,
amount NUMERIC(12,2)
);


Допустим, часто выполняется запрос:
SELECT
id,
created_at,
amount
FROM orders
WHERE user_id = 1500
AND created_at >= TIMESTAMPTZ '2026-09-01 00:00:00+00'
ORDER BY created_at;


Для него можно создать составной индекс:
CREATE INDEX idx_orders_user_created
ON orders (user_id, created_at);


Здесь порядок колонок выбран не случайно. Для многоколоночного B-tree наиболее эффективно работают условия равенства по ведущим колонкам, после которых может использоваться диапазонное условие по следующей колонке.

B-tree позволяет сначала ограничить сканируемую часть индекса конкретным user_id:
user_id = 1500


После этого created_at задаёт диапазон уже внутри записей этого пользователя:
created_at >= TIMESTAMPTZ '2026-09-01 00:00:00+00'


Такой порядок также соответствует ORDER BY created_at: при фиксированном user_id PostgreSQL может получить строки из индекса уже в нужном порядке и при подходящем плане обойтись без отдельной сортировки.

Теперь поменяем порядок колонок:
CREATE INDEX idx_orders_created_user
ON orders (created_at, user_id);


Для того же запроса такой индекс обычно менее удачен. Первая колонка используется по диапазону, поэтому PostgreSQL получает диапазон записей за нужный период среди всех пользователей:
created_at >= TIMESTAMPTZ '2026-09-01 00:00:00+00'


Условие по user_id всё ещё может участвовать в индексном сканировании, но уже не сокращает начальный диапазон B-tree так же эффективно, как в варианте (user_id, created_at). Разница особенно заметна, если за выбранный период накопились миллионы заказов разных пользователей.

Индекс (user_id, created_at) хорошо подходит и для запроса только по ведущей колонке:
WHERE user_id = 1500;


А также для сочетания равенства и диапазона:
WHERE user_id = 1500
AND created_at >= TIMESTAMPTZ '2026-09-01 00:00:00+00';


Но запрос только по created_at обычно не получает от этого индекса того же преимущества, поскольку условие на ведущую колонку отсутствует:
WHERE created_at >= TIMESTAMPTZ '2026-09-01 00:00:00+00';


При этом индекс нельзя считать полностью бесполезным: начиная с PostgreSQL 18, в некоторых случаях планировщик может применить B-tree skip scan. Выбор зависит от статистики, количества различных значений ведущей колонки и оценки стоимости плана.

Поэтому порядок колонок в составном индексе выбирают под реальные условия запросов, а результат проверяют через план выполнения.
EXPLAIN (ANALYZE, BUFFERS)
SELECT
id,
created_at,
amount
FROM orders
WHERE user_id = 1500
AND created_at >= TIMESTAMPTZ '2026-09-01 00:00:00+00'
ORDER BY created_at;


Важно, чтобы практика была показательной, таблица должна содержать достаточно данных, а статистика должна быть актуальной (ANALYZE orders;). На маленькой или пустой таблице PostgreSQL вполне может выбрать Seq Scan, и это будет нормальным поведением оптимизатора.

🔥 Вывод такой: для B-tree индекса (a, b) особенно эффективен сценарий, когда сначала ограничивается ведущая колонка a, а затем используется диапазон по b. Если запрос содержит равенство и диапазон, колонку с равенством часто имеет смысл поставить перед колонкой с диапазоном. Но окончательный выбор индекса должен подтверждаться реальным execution plan.

➡️ SQL Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥116👍6🤝3
This media is not supported in your browser
VIEW IN TELEGRAM
🤔 MentorData — материалы по SQL и аналитике данных!

Авторский блог с материалами для тех, кто изучает SQL, аналитику данных и готовится к работе в этой сфере. Основной акцент сделан не только на синтаксисе, но и на решении аналитических задач и понимании бизнес-логики. В статьях разбираются оконные функции, JOIN, GROUP BY, CTE, работа с аномалиями и метриками, задачи с технических собеседований и подходы к анализу данных.

📌 Оставляю ссылочку: mentordata.ru

➡️ SQL Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍8🤝6
Разворачивайте несколько массивов вместе!

Когда приложение передаёт несколько связанных массивов, не нужно отдельно делать unnest(), нумеровать элементы и потом соединять их по позиции.
SELECT *
FROM unnest(
ARRAY[101, 102, 103],
ARRAY['book', 'mouse', 'keyboard'],
ARRAY[2, 1, 4]
) AS x(id, name, qty);


PostgreSQL сопоставит элементы по позиции и сразу вернёт строки 101/book/2, 102/mouse/1, 103/keyboard/4.

Это удобно и для bulk-операций:
INSERT INTO order_items (product_id, quantity)
SELECT *
FROM unnest(
:product_ids::bigint[],
:quantities::integer[]
);


Можно обновлять пачку строк тем же способом:
UPDATE products p
SET price = x.price
FROM unnest(
:ids::bigint[],
:prices::numeric[]
) AS x(id, price)
WHERE p.id = x.id;


🔥 unnest(array1, array2, ...) объединяет связанные массивы в строки и позволяет массово добавлять или обновлять данные одним запросом.

➡️ SQL Ready | #совет
Please open Telegram to view this post
VIEW IN TELEGRAM
🤝11👍7🔥52
📂 Шпаргалка по SQL для разработчиков!

Например, SELECT используется для получения данных из таблиц, а CREATE, ALTER и DROP помогают управлять структурой базы данных. JOIN позволяет объединять данные из нескольких таблиц, а агрегатные функции (COUNT, SUM, AVG) — анализировать большие объёмы информации.

На картинке — шпаргалка с основными категориями команд, операторами, ключевыми словами, объектами базы данных, ограничениями, функциями агрегации, типами JOIN.

Сохрани, чтобы не потерять!

➡️ SQL Ready | #ресурс
Please open Telegram to view this post
VIEW IN TELEGRAM
11🔥5🤝2
Как атомарно резервировать лимит без SELECT FOR UPDATE!

При работе с квотами, остатками и лимитами важно не допустить, чтобы конкурентные запросы одновременно прошли проверку одного и того же доступного значения. Если проверка выполняется отдельно от изменения данных, между этими операциями появляется окно гонки:
SELECT used, quota
FROM accounts
WHERE id = 42;


Если два процесса одновременно получили used = 80 при quota = 100 и каждый собирается зарезервировать ещё 15, обе проверки в приложении могут успешно пройти.

Проверка ограничения должна выполняться непосредственно в операции изменения данных:
UPDATE accounts
SET used = used + 15
WHERE id = 42
AND used + 15 <= quota
RETURNING used;


Такой запрос одновременно проверяет условие и изменяет строку. В PostgreSQL конкурентный UPDATE одной строки сериализуется через row-level locking. Если другая транзакция успела изменить строку, условие WHERE для ожидавшего UPDATE будет повторно проверено относительно актуальной версии строки в READ COMMITTED:
UPDATE accounts
SET used = used + :amount
WHERE id = :account_id
AND used <= quota - :amount
RETURNING id, used, quota;


Если строка вернулась через RETURNING, резервирование выполнено. Если результат пустой, строка отсутствует либо доступного лимита недостаточно. Для прикладной логики эти случаи при необходимости можно различить отдельной проверкой после неуспешной попытки.

Предполагается, что amount >= 0: это стоит валидировать в приложении или закрепить ограничением на уровне БД:
UPDATE inventory
SET reserved = reserved + :qty
WHERE product_id = :product_id
AND reserved <= stock - :qty
RETURNING product_id, reserved;


Тот же паттерн применяется к резервированию товара, квотам API, счётчикам использования и другим инвариантам, которые выражаются условием над одной изменяемой строкой. Отдельный SELECT FOR UPDATE здесь нужен не всегда: если проверку можно выразить непосредственно в WHERE, сам UPDATE становится точкой синхронизации:
UPDATE limits
SET consumed = consumed + :delta
WHERE id = :id
AND consumed <= maximum - :delta
RETURNING consumed;


🔥 Делаем вывод: инвариант вида consumed + delta <= maximum над одной строкой лучше проверять внутри атомарного UPDATE, а не между SELECT и UPDATE в приложении. Это сокращает критическую секцию и корректно работает при конкурентном изменении строки.

➡️ SQL Ready | #практика
Please open Telegram to view this post
VIEW IN TELEGRAM
👍117🔥6
Почему 5 систем могут потребовать 10 интеграций, а 10 — уже 45?

Когда в data stack появляется несколько отдельных инструментов для хранения, обработки и аналитики, между ними приходится выстраивать обмен данными. Для пяти систем таких связок может быть до 10, а для десяти — уже до 45.

Yandex B2B Tech представила DataLens Platform на основе архитектуры lakehouse. Исходные и подготовленные данные хранятся в общей среде на S3 с использованием Apache Iceberg.

Из технических деталей — storage и compute масштабируются независимо. То есть рост объема данных не обязательно означает пропорциональное увеличение вычислительных ресурсов, и наоборот.

👉 Подробнее об архитектуре DataLens Platform — в материале СМИ.
6
This media is not supported in your browser
VIEW IN TELEGRAM
👨‍💻 SQL PostgreSQL Patterns Library — большая коллекция готовых SQL-решений для PostgreSQL!

Здесь собраны практические запросы для работы со строками, JSON и массивами, поиска и оптимизации, массового обновления данных, индексов, миграций и администрирования БД. Есть решения для реальных задач: от UPSERT и поиска дубликатов до EXPLAIN, работы с миллионами записей, мониторинга запросов и обслуживания PostgreSQL.

Оставляю ссылочку: GitHub 📱


➡️ SQL Ready | #репозиторий
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥97👍5