Например, стек работает по принципу LIFO — последний добавленный элемент извлекается первым, а очередь наоборот использует FIFO — первым выходит тот, кто пришёл первым.
На картинке — 7 основных структур данных: массивы, связные списки, стек, очередь, хеш-таблица, дерево и граф. Полезная база, которую стоит знать каждому разработчику.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍9❤8🤝1
This media is not supported in your browser
VIEW IN TELEGRAM
Здесь разобраны основы SQL, проектирование баз данных, индексы, транзакции, MVCC, WAL, оптимизация запросов, репликация, бэкапы и многое другое. Подойдёт разработчикам, которые хотят системно изучить PostgreSQL и разобраться не только с SQL, но и с архитектурой, производительностью, эксплуатацией базы данных.
Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍5❤3🤝3
Оконный фрейм по группам значений!
Если по одной цене находится 50 товаров, вся эта пачка считается одной группой. Поэтому можно строить окна относительно соседних значений, не вычисляя
Ещё интереснее
Среднее здесь считается по соседним строкам, но текущая строка автоматически исключается.
🔥
➡️ SQL Ready | #совет
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.Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥7❤5
Составные индексы: правила проектирования и типичные ошибки!
Составные индексы используются для ускорения запросов по нескольким колонкам, но порядок полей внутри индекса напрямую влияет на его эффективность.
Представим таблицу заказов, где часто выполняются запросы по пользователю и дате создания заказа.
Обычный индекс на каждую колонку не всегда является оптимальным решением. СУБД может использовать несколько индексов одновременно, но такой план не всегда будет эффективнее одного правильно спроектированного составного индекса.
Для частого сценария поиска заказов конкретного пользователя за период лучше создать составной индекс:
Такой индекс эффективно работает для запросов, где используется первая колонка индекса или полный набор колонок.
В B-tree индексе данные сначала сортируются по
Поэтому СУБД быстро находит записи пользователя и затем выполняет поиск по диапазону дат. Также индекс будет использоваться:
Но он плохо подходит для поиска только по второй колонке:
Причина в структуре B-tree: данные сначала организованы по первой колонке индекса.
Для такого запроса отдельный индекс по дате будет более подходящим:
Ещё одна распространённая ошибка — добавление большого количества колонок в индекс.
Каждый дополнительный столбец увеличивает размер индекса и стоимость операций записи.
Перед созданием индекса нужно анализировать реальные запросы приложения, а не добавлять поля на всякий случай.
Для такого запроса может быть эффективнее индекс, учитывающий фильтрацию и сортировку:
Правильно спроектированный составной индекс уменьшает количество операций чтения, снижает нагрузку на CPU и помогает оптимизатору выбрать более дешёвый план выполнения.
🔥 Главное правило простое: индекс создаётся под реальные запросы приложения, а не просто под структуру таблицы.
➡️ SQL Ready | #практика
Составные индексы используются для ускорения запросов по нескольким колонкам, но порядок полей внутри индекса напрямую влияет на его эффективность.
Представим таблицу заказов, где часто выполняются запросы по пользователю и дате создания заказа.
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 и помогает оптимизатору выбрать более дешёвый план выполнения.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤8🤝5
Шпаргалка по агрегации данных и формированию отчётности в Oracle SQL: объединение значений с LISTAGG, выбор значений по рангу с KEEP (DENSE_RANK FIRST/LAST), построение промежуточных и общих итогов с ROLLUP и GROUPING SETS, а также идентификация уровней агрегации с помощью GROUPING и GROUPING_ID. Полезно для построения сводных выборок, многоуровневых отчётов и обработки агрегированных данных непосредственно средствами SQL.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤6🤝4
Почему порядок колонок в составном индексе важен!
Составной индекс часто создают, когда запрос фильтрует данные сразу по нескольким колонкам. Но просто добавить нужные поля в индекс недостаточно — их порядок влияет на то, какую часть индекса PostgreSQL сможет эффективно использовать.
Есть таблица:
Допустим, часто выполняется запрос:
Для него можно создать составной индекс:
Здесь порядок колонок выбран не случайно. Для многоколоночного B-tree наиболее эффективно работают условия равенства по ведущим колонкам, после которых может использоваться диапазонное условие по следующей колонке.
B-tree позволяет сначала ограничить сканируемую часть индекса конкретным
После этого
Такой порядок также соответствует
Теперь поменяем порядок колонок:
Для того же запроса такой индекс обычно менее удачен. Первая колонка используется по диапазону, поэтому PostgreSQL получает диапазон записей за нужный период среди всех пользователей:
Условие по
Индекс
А также для сочетания равенства и диапазона:
Но запрос только по
При этом индекс нельзя считать полностью бесполезным: начиная с PostgreSQL 18, в некоторых случаях планировщик может применить B-tree skip scan. Выбор зависит от статистики, количества различных значений ведущей колонки и оценки стоимости плана.
Поэтому порядок колонок в составном индексе выбирают под реальные условия запросов, а результат проверяют через план выполнения.
Важно, чтобы практика была показательной, таблица должна содержать достаточно данных, а статистика должна быть актуальной (
🔥 Вывод такой: для B-tree индекса
➡️ SQL Ready | #практика
Составной индекс часто создают, когда запрос фильтрует данные сразу по нескольким колонкам. Но просто добавить нужные поля в индекс недостаточно — их порядок влияет на то, какую часть индекса 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, и это будет нормальным поведением оптимизатора.(a, b) особенно эффективен сценарий, когда сначала ограничивается ведущая колонка a, а затем используется диапазон по b. Если запрос содержит равенство и диапазон, колонку с равенством часто имеет смысл поставить перед колонкой с диапазоном. Но окончательный выбор индекса должен подтверждаться реальным execution plan.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤6👍6🤝3
This media is not supported in your browser
VIEW IN TELEGRAM
Авторский блог с материалами для тех, кто изучает SQL, аналитику данных и готовится к работе в этой сфере. Основной акцент сделан не только на синтаксисе, но и на решении аналитических задач и понимании бизнес-логики. В статьях разбираются оконные функции,
JOIN, GROUP BY, CTE, работа с аномалиями и метриками, задачи с технических собеседований и подходы к анализу данных.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍8🤝6
Разворачивайте несколько массивов вместе!
Когда приложение передаёт несколько связанных массивов, не нужно отдельно делать
PostgreSQL сопоставит элементы по позиции и сразу вернёт строки
Это удобно и для bulk-операций:
Можно обновлять пачку строк тем же способом:
🔥
➡️ SQL Ready | #совет
Когда приложение передаёт несколько связанных массивов, не нужно отдельно делать
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, ...) объединяет связанные массивы в строки и позволяет массово добавлять или обновлять данные одним запросом.Please open Telegram to view this post
VIEW IN TELEGRAM
🤝11👍7🔥5❤2
Например,
SELECT используется для получения данных из таблиц, а CREATE, ALTER и DROP помогают управлять структурой базы данных. JOIN позволяет объединять данные из нескольких таблиц, а агрегатные функции (COUNT, SUM, AVG) — анализировать большие объёмы информации.На картинке — шпаргалка с основными категориями команд, операторами, ключевыми словами, объектами базы данных, ограничениями, функциями агрегации, типами JOIN.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11🔥5🤝2
Как атомарно резервировать лимит без SELECT FOR UPDATE!
При работе с квотами, остатками и лимитами важно не допустить, чтобы конкурентные запросы одновременно прошли проверку одного и того же доступного значения. Если проверка выполняется отдельно от изменения данных, между этими операциями появляется окно гонки:
Если два процесса одновременно получили
Проверка ограничения должна выполняться непосредственно в операции изменения данных:
Такой запрос одновременно проверяет условие и изменяет строку. В PostgreSQL конкурентный
Если строка вернулась через
Предполагается, что
Тот же паттерн применяется к резервированию товара, квотам API, счётчикам использования и другим инвариантам, которые выражаются условием над одной изменяемой строкой. Отдельный
🔥 Делаем вывод: инвариант вида
➡️ SQL Ready | #практика
При работе с квотами, остатками и лимитами важно не допустить, чтобы конкурентные запросы одновременно прошли проверку одного и того же доступного значения. Если проверка выполняется отдельно от изменения данных, между этими операциями появляется окно гонки:
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 в приложении. Это сокращает критическую секцию и корректно работает при конкурентном изменении строки.Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤7🔥6
Почему 5 систем могут потребовать 10 интеграций, а 10 — уже 45?
Когда в data stack появляется несколько отдельных инструментов для хранения, обработки и аналитики, между ними приходится выстраивать обмен данными. Для пяти систем таких связок может быть до 10, а для десяти — уже до 45.
Yandex B2B Tech представила DataLens Platform на основе архитектуры lakehouse. Исходные и подготовленные данные хранятся в общей среде на S3 с использованием Apache Iceberg.
Из технических деталей — storage и compute масштабируются независимо. То есть рост объема данных не обязательно означает пропорциональное увеличение вычислительных ресурсов, и наоборот.
👉 Подробнее об архитектуре DataLens Platform — в материале СМИ.
Когда в 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
Здесь собраны практические запросы для работы со строками, JSON и массивами, поиска и оптимизации, массового обновления данных, индексов, миграций и администрирования БД. Есть решения для реальных задач: от
UPSERT и поиска дубликатов до EXPLAIN, работы с миллионами записей, мониторинга запросов и обслуживания PostgreSQL. Оставляю ссылочку: GitHub📱
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤7👍5