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
С oracle sql 25.2 теперь можно запускать MCP-серверы.
Они позволяют взаимодействовать с вашей Oracle Database через AI-агентов.
That Jeffsmith подготовил FAQ, который помогает понять, как это работает, включая:
• Работает ли это с 11g или 12c? Скорее всего, да
• Как сделать использование безопасным
👉 @SQLPortal
Они позволяют взаимодействовать с вашей Oracle Database через AI-агентов.
That Jeffsmith подготовил FAQ, который помогает понять, как это работает, включая:
• Работает ли это с 11g или 12c? Скорее всего, да
• Как сделать использование безопасным
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4
Работа с SQL на больших данных — опыт перехода с 4 млн на 42 млн клиентов
Разработчик поделился историей. В новой компании с 42 млн пользователей его первый SQL-запрос «упал». Масштаб данных потребовал изменить подход.
Что изменилось
I. Дробление запросов
Вместо монолитных SQL-скриптов в 500 строк используются промежуточные staging-таблицы, пошаговая логика и CTE. Это помогает базе работать стабильнее и быстрее.
II. Разделение данных
Огромная таблица заменена на две структуры
- Current содержит только актуальный срез для быстрых дашбордов
- History хранит полную историю для комплаенса
Так удалось сбалансировать скорость и полноту.
III. Точность требований
Формулировки вроде «дайте все данные» больше не работают. Теперь разработчик уточняет какой период нужен, какие сегменты важны, какой бизнес-вопрос стоит за задачей.
При работе с десятками миллионов клиентов важно дробить запросы, разделять данные на текущие и архивные и добиваться конкретных требований. Такой подход ускоряет аналитику и снижает нагрузку на базы.
👉 @SQLPortal
Разработчик поделился историей. В новой компании с 42 млн пользователей его первый SQL-запрос «упал». Масштаб данных потребовал изменить подход.
Что изменилось
I. Дробление запросов
Вместо монолитных SQL-скриптов в 500 строк используются промежуточные staging-таблицы, пошаговая логика и CTE. Это помогает базе работать стабильнее и быстрее.
II. Разделение данных
Огромная таблица заменена на две структуры
- Current содержит только актуальный срез для быстрых дашбордов
- History хранит полную историю для комплаенса
Так удалось сбалансировать скорость и полноту.
III. Точность требований
Формулировки вроде «дайте все данные» больше не работают. Теперь разработчик уточняет какой период нужен, какие сегменты важны, какой бизнес-вопрос стоит за задачей.
При работе с десятками миллионов клиентов важно дробить запросы, разделять данные на текущие и архивные и добиваться конкретных требований. Такой подход ускоряет аналитику и снижает нагрузку на базы.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍4
Внешние таблицы — отличный способ читать текстовые файлы в Oracle Database с помощью SQL.
Но что, если у тебя есть поля длиннее 4000 символов?
Anti Kyte разбирает:
I. Маппинг CHAR в ACCESS PARAMETERS на CLOB
II. Увеличение READSIZE
III. Использование big data driver
👉 @SQLPortal
Но что, если у тебя есть поля длиннее 4000 символов?
Anti Kyte разбирает:
I. Маппинг CHAR в ACCESS PARAMETERS на CLOB
II. Увеличение READSIZE
III. Использование big data driver
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3