Нужно замерить размер таблицы в Postgres? Не забудь про индексы и TOAST.
👉 @SQLPortal
SELECT pg_relation_size('table_name'); покажет размер основного файла таблицы, но pg_relation_size() возвращает только размер самого HEAP-сегмента. Он не учитывает индексы, TOAST-таблицы, карты свободного пространства и прочие файлы, которые связаны с таблицей на диске.pg_total_relation_size() вернёт полный размер объекта вместе с heap-данными, индексами, TOAST, Free Space Map и Visibility Map.Please open Telegram to view this post
VIEW IN TELEGRAM
👍13
В Oracle Database 23.9 появилась фича
Дани Шнайдер показывает, как это удобно использовать для поиска дубликатов:
👉 @SQLPortal
GROUP BY ALL, которая автоматически добавляет в GROUP BY все колонки из SELECT, кроме агрегатных.Дани Шнайдер показывает, как это удобно использовать для поиска дубликатов:
SELECT t.*, COUNT(*)
FROM t
GROUP BY ALL
HAVING COUNT(*) > 1;
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤7
Postgres NTILE
Функция NTILE это оконная функция, которая делит результирующий набор на заданное количество примерно равных частей, называемых тайлами или бакетами. Каждой строке присваивается уникальный номер тайла, что позволяет категоризировать данные и анализировать их распределение.
Эта функция особенно полезна в статистическом и финансовом анализе, когда нужно понять, как данные распределяются по сегментам, выявить тренды или сравнить группы с разными характеристиками.
Например, вызов NTILE(4) разбивает данные на четыре квартиля и помещает каждую строку в одну из четырёх групп.
В этом примере данные о дневных продажах делятся на четыре группы по величине выручки. Первая группа — дни с самыми большими продажами, четвёртая -- с самыми маленькими.
👉 @SQLPortal
Функция NTILE это оконная функция, которая делит результирующий набор на заданное количество примерно равных частей, называемых тайлами или бакетами. Каждой строке присваивается уникальный номер тайла, что позволяет категоризировать данные и анализировать их распределение.
Эта функция особенно полезна в статистическом и финансовом анализе, когда нужно понять, как данные распределяются по сегментам, выявить тренды или сравнить группы с разными характеристиками.
Например, вызов NTILE(4) разбивает данные на четыре квартиля и помещает каждую строку в одну из четырёх групп.
WITH DailySales AS (
SELECT
date_trunc('day', o.order_date) AS sales_date,
SUM(o.total_amount) AS daily_total_sales
FROM
orders o
GROUP BY
date_trunc('day', o.order_date)
)
SELECT
sales_date,
daily_total_sales,
NTILE(4) OVER (
ORDER BY daily_total_sales DESC
) AS sales_quartile
FROM
DailySales
ORDER BY
sales_date;
В этом примере данные о дневных продажах делятся на четыре группы по величине выручки. Первая группа — дни с самыми большими продажами, четвёртая -- с самыми маленькими.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
В Oracle Database виртуальные колонки давно позволяют определять вычисляемые поля.
По умолчанию выражение считается при чтении.
А начиная с релиза 23.7 можно хранить результат выражения при записи с помощью синтаксиса:
Gerald Venzl разобрал это подробнее
👉 @SQLPortal
По умолчанию выражение считается при чтении.
А начиная с релиза 23.7 можно хранить результат выражения при записи с помощью синтаксиса:
<col> <data_type> AS ( ... ) MATERIALIZED
Gerald Venzl разобрал это подробнее
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤3🔥3
Lateral join в Postgres:
Иногда
Типовые кейсы:
- выборка последней записи в группе
- получение топ-N связанных записей для каждой строки
- агрегации или ранжирование по строкам
- объединение JSON или массивов с данными из других таблиц
Классный интерактивный туториал в браузере: тык
👉 @SQLPortal
LATERAL позволяет подзапросу ссылаться на колонки таблицы, которая указана раньше в секции FROM. По сути это что-то вроде for-each цикла между двумя таблицами.Иногда
lateral join может заменить коррелированные подзапросы, и при этом работать быстрее.Типовые кейсы:
- выборка последней записи в группе
- получение топ-N связанных записей для каждой строки
- агрегации или ранжирование по строкам
- объединение JSON или массивов с данными из других таблиц
Классный интерактивный туториал в браузере: тык
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤2
This media is not supported in your browser
VIEW IN TELEGRAM
Выборка top-N строк в Oracle SQL:
👉 @SQLPortal
FETCH FIRST n ROWS ONLY — вернуть первые n строк.FETCH FIRST n ROWS WITH TIES — вернуть первые n строк плюс все строки с тем же значением сортировки, что и у n-й строки.FETCH FIRST n PERCENT ROWS [ ONLY | WITH TIES ] — вернуть заданный процент строк (с опцией только n% строк или n% плюс все «ничейные» строки при одинаковом значении сортировки).Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍2
Опции Postgres EXPLAIN
➣ Базовый EXPLAIN выглядит так:
Он показывает:
как выполнялся скан — последовательное сканирование, индексное сканирование и т. д.
cost — внутренний расчетный показатель, основанный на I/O, CPU и прочем
rows — количество строк, которые будут обработаны
width — количество байт на строку
➣ EXPLAIN ANALYZE даёт больше инфы, добавляя время планирования и выполнения:
➣ EXPLAIN (ANALYZE, BUFFERS) добавит данные о том, из буфера (shared buffer cache) бралось или с диска.
Начиная с Postgres 18 — BUFFERS будет включен по умолчанию.
EXPLAIN умеет выдавать результат не только в текстовом формате, к которому все привыкли. Есть ещё JSON, XML и YAML.
YAML удобен тем, что наглядно показывает каждое поле и его значение.
Пример:
Вывод:
👉 @SQLPortal
➣ Базовый EXPLAIN выглядит так:
QUERY PLAN
Seq Scan on orders (cost=0.00..17.00 rows=1000 width=18)
(1 row)
Он показывает:
как выполнялся скан — последовательное сканирование, индексное сканирование и т. д.
cost — внутренний расчетный показатель, основанный на I/O, CPU и прочем
rows — количество строк, которые будут обработаны
width — количество байт на строку
➣ EXPLAIN ANALYZE даёт больше инфы, добавляя время планирования и выполнения:
Seq Scan on orders (cost=0.00..17.00 rows=1000 width=18)
(actual time=0.136..0.440 rows=1000 loops=1)
Planning Time: 0.209 ms
Execution Time: 0.660 ms
➣ EXPLAIN (ANALYZE, BUFFERS) добавит данные о том, из буфера (shared buffer cache) бралось или с диска.
Начиная с Postgres 18 — BUFFERS будет включен по умолчанию.
Seq Scan on orders (cost=0.00..17.00 rows=1000 width=18)
(actual time=0.080..0.283 rows=1000 loops=1)
Buffers: shared hit=7
Planning Time: 0.126 ms
Execution Time: 0.440 ms
EXPLAIN умеет выдавать результат не только в текстовом формате, к которому все привыкли. Есть ещё JSON, XML и YAML.
YAML удобен тем, что наглядно показывает каждое поле и его значение.
Пример:
EXPLAIN (ANALYZE, BUFFERS, FORMAT YAML)
Вывод:
Plan:
Node Type: "Seq Scan"
Parallel Aware: false
Async Capable: false
Relation Name: "orders"
Alias: "orders"
Startup Cost: 0.00
Total Cost: 17.00
Plan Rows: 1000
Plan Width: 18
Actual Startup Time: 0.102
Actual Total Time: 0.451
Actual Rows: 1000
Actual Loops: 1
Shared Hit Blocks: 7
Shared Read Blocks: 0
Shared Dirtied Blocks: 0
Shared Written Blocks: 0
Local Hit Blocks: 0
Local Read Blocks: 0
Local Dirtied Blocks: 0
Local Written Blocks: 0
Temp Read Blocks: 0
Temp Written Blocks: 0
Planning:
Shared Hit Blocks: 0
Shared Read Blocks: 0
Shared Dirtied Blocks: 0
Shared Written Blocks: 0
Local Hit Blocks: 0
Local Read Blocks: 0
Local Dirtied Blocks: 0
Local Written Blocks: 0
Temp Read Blocks: 0
Temp Written Blocks: 0
Planning Time: 0.261
Triggers:
Execution Time: 0.745
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍5🔥2
Oracle Database 23.9 добавила не-позиционные вставки.
Это позволяет задавать значения столбцов в выражении SET (похоже на UPDATE).
Gerald Venzl подробно объясняет детали и рассказывает предысторию.
👉 @SQLPortal
Это позволяет задавать значения столбцов в выражении SET (похоже на UPDATE).
INSERT INTO employees
SET employee_id = 210, first_name = 'Gerald', ...
Gerald Venzl подробно объясняет детали и рассказывает предысторию.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3
Охотьтесь на N+1 и убирайте их
Проблема N+1-запросов опасна и легко может ускользнуть из виду. Сценарий такой. Один запрос получает набор данных. Затем для каждой строки нужен ещё один запрос. Количество запросов при загрузке страницы зависит от размера выборки, и если N большой, производительность падает
Пример на псевдокоде
Лучше переписать на один запрос с JOIN, чтобы забрать всё сразу
Интересный момент в том, что иногда N+1 почти не заметен. Если N маленький, база настроена и находится рядом с приложением, всё может работать быстро. Но если N=100, сразу ощущается тормоз и база нагружается лишней работой. Исправив это, вы улучшите UX и снизите нагрузку на сервер БД
Иногда такие запросы пишут руками. Но часто их генерирует ORM незаметно для вас. В любом случае избавляйтесь от них
👉 @SQLPortal
Проблема N+1-запросов опасна и легко может ускользнуть из виду. Сценарий такой. Один запрос получает набор данных. Затем для каждой строки нужен ещё один запрос. Количество запросов при загрузке страницы зависит от размера выборки, и если N большой, производительность падает
Пример на псевдокоде
// один запрос
users = db.query('SELECT * FROM users')
// превращается в N дополнительных
for (const user of users) {
posts = db.query(`
SELECT * FROM posts
WHERE user_id = user->id`)
user.posts = posts
}
// только теперь можно вернуть результат
Лучше переписать на один запрос с JOIN, чтобы забрать всё сразу
results = await db.query(
`SELECT u.*, p.*
FROM users u
LEFT JOIN posts p
ON u.id = p.user_id`)
Интересный момент в том, что иногда N+1 почти не заметен. Если N маленький, база настроена и находится рядом с приложением, всё может работать быстро. Но если N=100, сразу ощущается тормоз и база нагружается лишней работой. Исправив это, вы улучшите UX и снизите нагрузку на сервер БД
Иногда такие запросы пишут руками. Но часто их генерирует ORM незаметно для вас. В любом случае избавляйтесь от них
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤8
Skip scan для B-деревьев в Postgres 18
В Postgres 18 появится заметный прирост производительности для некоторых многоколонковых B-tree индексов.
Классический B-tree-поиск работает так: индекс читается, начиная с позиции, определяемой условиями запроса по колонкам индекса в их порядке. Если в запросе нет условия (WHERE или LIMIT BY) по первой (ведущей) колонке, то база не может использовать этот метод и вынуждена выполнять более медленный полный скан таблицы.
Skip scanning позволяет «пропускать» части индекса и всё же эффективно использовать его.
Этот подход работает, когда в запросе отсутствует условие по ведущей колонке, и при этом у пропущенной колонки низкая кардинальность (небольшое количество уникальных значений).
Оптимизация выполняется следующим образом:
I. Определяются все уникальные значения в пропущенных ведущих колонках.
II. Для каждого значения выполняется целевой проход по индексу.
Пример:
Допустим, у нас есть таблица sales со столбцами status и date.
Создаём многоколонковый индекс:
Далее выполняется запрос без фильтрации по status:
До версии 18 приходилось выполнять полный скан.
В Postgres 18+, если у status низкая кардинальность и всего несколько различных значений, планировщик сможет выполнять отдельные индексные проходы (в подходящих случаях).
Всё это работает прозрачно для пользователя — никаких ручных включений не требуется. Идея состоит в том, чтобы ускорить аналитические сценарии, где фильтры и условия часто меняются и не всегда совпадают с индексами.
Планировщик сам решает, стоит ли использовать skip scan, опираясь на статистику таблицы и количество уникальных значений в пропускаемых колонках. Если уникальных значений слишком много, последовательное сканирование может оказаться быстрее.
👉 @SQLPortal
В Postgres 18 появится заметный прирост производительности для некоторых многоколонковых B-tree индексов.
Классический B-tree-поиск работает так: индекс читается, начиная с позиции, определяемой условиями запроса по колонкам индекса в их порядке. Если в запросе нет условия (WHERE или LIMIT BY) по первой (ведущей) колонке, то база не может использовать этот метод и вынуждена выполнять более медленный полный скан таблицы.
Skip scanning позволяет «пропускать» части индекса и всё же эффективно использовать его.
Этот подход работает, когда в запросе отсутствует условие по ведущей колонке, и при этом у пропущенной колонки низкая кардинальность (небольшое количество уникальных значений).
Оптимизация выполняется следующим образом:
I. Определяются все уникальные значения в пропущенных ведущих колонках.
II. Для каждого значения выполняется целевой проход по индексу.
Пример:
Допустим, у нас есть таблица sales со столбцами status и date.
Создаём многоколонковый индекс:
CREATE INDEX idx_status_date
ON sales (status, date);
Далее выполняется запрос без фильтрации по status:
SELECT * FROM orders
WHERE order_date > '2025-01-01';
До версии 18 приходилось выполнять полный скан.
В Postgres 18+, если у status низкая кардинальность и всего несколько различных значений, планировщик сможет выполнять отдельные индексные проходы (в подходящих случаях).
Всё это работает прозрачно для пользователя — никаких ручных включений не требуется. Идея состоит в том, чтобы ускорить аналитические сценарии, где фильтры и условия часто меняются и не всегда совпадают с индексами.
Планировщик сам решает, стоит ли использовать skip scan, опираясь на статистику таблицы и количество уникальных значений в пропускаемых колонках. Если уникальных значений слишком много, последовательное сканирование может оказаться быстрее.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍3🔥2
Я понял, почему никто не пользуется MongoDB.
Путь перегорожен B-деревьями.😈
Карта из книги Designing Data-Intensive Applications
👉 @SQLPortal
Путь перегорожен B-деревьями.
Please open Telegram to view this post
VIEW IN TELEGRAM
😁16❤5
Преобразование строк в список уникальных значений, разделённых запятыми, в Oracle SQL с помощью
Функция LISTAGG появилась в 11g Release 2.
Поддержка DISTINCT добавлена в 19c.
👉 @SQLPortal
LISTAGG (DISTINCT val, ', ') WITHIN GROUP (ORDER BY ...)
Функция LISTAGG появилась в 11g Release 2.
Поддержка DISTINCT добавлена в 19c.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍4🤔2
Doom в SQL уже здесь
Представьте себе шутер уровня AAA, но… целиком построенный на SQL-запросах.
Вся игровая реальность живёт в таблицах базы данных.
Герой двигается и стреляет не с клавиатуры, а через запросы.
Даже такие штуки как raycasting, отрисовка спрайтов, работа с HUD и обработка видимости — реализованы с помощью VIEW.
Короче, хотите сделать хэдшот, то пишите правильный SELECT.🎯
https://github.com/cedardb/DOOMQL
👉 @SQLPortal
Представьте себе шутер уровня AAA, но… целиком построенный на SQL-запросах.
Вся игровая реальность живёт в таблицах базы данных.
Герой двигается и стреляет не с клавиатуры, а через запросы.
Даже такие штуки как raycasting, отрисовка спрайтов, работа с HUD и обработка видимости — реализованы с помощью VIEW.
Короче, хотите сделать хэдшот, то пишите правильный SELECT.
https://github.com/cedardb/DOOMQL
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍4🤯4😁1
Учись работать с высокоуровневой аналитикой производительности, такой как коэффициент попаданий в кэш, на учебном сайте:
https://crunchydata.com/developers/playground/high-level-performance-analytics
👉 @SQLPortal
https://crunchydata.com/developers/playground/high-level-performance-analytics
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Алгоритм хэш-соединения
1. Строит хэш-таблицу по колонкам соединения меньшей таблицы.
2. Читает большую таблицу и проверяет, хэшируются ли её колонки соединения в таблицу, построенную на шаге 1.
Сложность растёт линейно от размеров таблиц: O(n + m)
Vlad Mihalcea объясняет здесь
👉 @SQLPortal
1. Строит хэш-таблицу по колонкам соединения меньшей таблицы.
2. Читает большую таблицу и проверяет, хэшируются ли её колонки соединения в таблицу, построенную на шаге 1.
Сложность растёт линейно от размеров таблиц: O(n + m)
Vlad Mihalcea объясняет здесь
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Самый частый вопрос на собеседованиях по SQL:
Как обработать дубликаты?
Вот простой способ:
I. Использовать
II. Сделать
III. Добавить
👉 @SQLPortal
Как обработать дубликаты?
Вот простой способ:
I. Использовать
COUNT(*)II. Сделать
GROUP BY по полю, которое должно быть уникальнымIII. Добавить
HAVING > 1 (как WHERE, но для сгруппированных данных), чтобы показать строки, встречающиеся более одного разаPlease open Telegram to view this post
VIEW IN TELEGRAM
👍16❤8🤔1
Этот курс по PostgreSQL для начинающих поможет понять, подходит ли она для ваших проектов.
В курсе разбираются оператор SELECT, выражения WHERE, агрегатные функции и другие темы.
https://freecodecamp.org/news/posgresql-course-for-beginners/
👉 @SQLPortal
В курсе разбираются оператор SELECT, выражения WHERE, агрегатные функции и другие темы.
https://freecodecamp.org/news/posgresql-course-for-beginners/
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍2
Для больших наборов данных использование LIMIT + OFFSET работает не всегда эффективно, особенно если значение OFFSET большое.
Вместо этого можно применять постраничную выборку на основе курсора, где фильтрация идёт по значениям сортировки.
Именно поэтому сервисы вроде GitHub используют такой подход к пагинации
👉 @SQLPortal
Вместо этого можно применять постраничную выборку на основе курсора, где фильтрация идёт по значениям сортировки.
Именно поэтому сервисы вроде GitHub используют такой подход к пагинации
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍4🔥3
Нельзя понять производительность SQL-запроса просто по его коду. Для этого нужен план выполнения.
Пример:
Из этого кода не видно, как быстро выполнится запрос.
Если на колонке grade нет индекса, база сделает полное сканирование таблицы, и выполнение займёт несколько секунд в зависимости от числа строк.
Если добавить индекс на grade, тот же запрос выполнится быстрее.
Если сделать индекс покрывающим (добавить id как включённый столбец), то это будет уже index-only scan, и запрос станет ещё быстрее.
Если вставить миллион записей и сразу выполнить запрос (без работы сборщика мусора MVCC), он снова замедлится, так как нужно проверять видимость строк в heap.
Во всех этих случаях код на бэкенде не менялся — один и тот же запрос.
Но его производительность разная и зависит от состояния базы.
План выполнения показывает, как именно запрос исполнялся. Это ключ к пониманию.
👉 @SQLPortal
Пример:
select id from student where grade >= 99
Из этого кода не видно, как быстро выполнится запрос.
Если на колонке grade нет индекса, база сделает полное сканирование таблицы, и выполнение займёт несколько секунд в зависимости от числа строк.
Если добавить индекс на grade, тот же запрос выполнится быстрее.
Если сделать индекс покрывающим (добавить id как включённый столбец), то это будет уже index-only scan, и запрос станет ещё быстрее.
Если вставить миллион записей и сразу выполнить запрос (без работы сборщика мусора MVCC), он снова замедлится, так как нужно проверять видимость строк в heap.
Во всех этих случаях код на бэкенде не менялся — один и тот же запрос.
Но его производительность разная и зависит от состояния базы.
План выполнения показывает, как именно запрос исполнялся. Это ключ к пониманию.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤1
Будьте осторожны при фильтрации внутренней таблицы в OUTER JOIN.
Например, правой таблицы в LEFT OUTER JOIN.
Если применить фильтр к ней в секции WHERE, то соединение фактически превращается в INNER JOIN.
Чтобы сохранить именно OUTER JOIN, фильтровать внутреннюю таблицу нужно в секции ON.
На схеме показан OUTER JOIN по числовой колонке с внутренними фильтрами.
👉 @SQLPortal
Например, правой таблицы в LEFT OUTER JOIN.
Если применить фильтр к ней в секции WHERE, то соединение фактически превращается в INNER JOIN.
Чтобы сохранить именно OUTER JOIN, фильтровать внутреннюю таблицу нужно в секции ON.
На схеме показан OUTER JOIN по числовой колонке с внутренними фильтрами.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥3👍2