Почему BETWEEN может незаметно исключать данные при фильтрации по датам!
При работе с временными диапазонами часто используют
Допустим, есть таблица заказов:
Нужно получить заказы за сентябрь 2026 года. На первый взгляд подходит запрос:
Однако
Например, запись со временем
Но это тоже ненадёжный вариант. Если колонка поддерживает дробные доли секунды, запись
Для временных интервалов практичнее использовать полуоткрытый диапазон:
Такой запрос включает все значения за сентябрь независимо от точности хранения времени и исключает записи следующего месяца.
Ещё одно преимущество — соседние интервалы не пересекаются. Например, при последовательной обработке данных можно использовать:
При условии, что следующая выборка начинается с предыдущего
Такой подход также хорошо сочетается с обычным B-tree индексом:
Поскольку фильтрация выполняется непосредственно по индексируемой колонке, оптимизатор может использовать диапазонное сканирование индекса. При использовании типов с часовыми поясами границы необходимо задавать в соответствии с нужной временной зоной, особенно если речь идёт о календарных днях.
🔥 Для фильтрации по временным интервалам условия
➡️ SQL Ready | #практика
При работе с временными диапазонами часто используют
BETWEEN. Но если колонка хранит дату вместе со временем, такой фильтр может возвращать не все ожидаемые записи.Допустим, есть таблица заказов:
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
created_at TIMESTAMP NOT NULL,
amount DECIMAL(12, 2)
);
Нужно получить заказы за сентябрь 2026 года. На первый взгляд подходит запрос:
SELECT *
FROM orders
WHERE created_at BETWEEN
TIMESTAMP '2026-09-01 00:00:00'
AND TIMESTAMP '2026-09-30 00:00:00';
Однако
BETWEEN включает обе границы диапазона и эквивалентен сочетанию >= и <=. Поэтому запрос вернёт записи с 1 сентября по начало 30 сентября, но исключит все заказы, созданные позднее полуночи последнего дня месяца.Например, запись со временем
2026-09-30 15:30:00 не попадёт в результат. Иногда верхнюю границу пытаются исправить вручную:WHERE created_at BETWEEN
TIMESTAMP '2026-09-01 00:00:00'
AND TIMESTAMP '2026-09-30 23:59:59';
Но это тоже ненадёжный вариант. Если колонка поддерживает дробные доли секунды, запись
2026-09-30 23:59:59.500 окажется за пределами диапазона.Для временных интервалов практичнее использовать полуоткрытый диапазон:
SELECT *
FROM orders
WHERE created_at >= TIMESTAMP '2026-09-01 00:00:00'
AND created_at < TIMESTAMP '2026-10-01 00:00:00';
Такой запрос включает все значения за сентябрь независимо от точности хранения времени и исключает записи следующего месяца.
Ещё одно преимущество — соседние интервалы не пересекаются. Например, при последовательной обработке данных можно использовать:
WHERE created_at >= :date_from
AND created_at < :date_to;
При условии, что следующая выборка начинается с предыдущего
date_to, каждая запись на границе интервалов попадёт ровно в один диапазон.Такой подход также хорошо сочетается с обычным B-tree индексом:
CREATE INDEX idx_orders_created_at
ON orders (created_at);
Поскольку фильтрация выполняется непосредственно по индексируемой колонке, оптимизатор может использовать диапазонное сканирование индекса. При использовании типов с часовыми поясами границы необходимо задавать в соответствии с нужной временной зоной, особенно если речь идёт о календарных днях.
>= и < обычно надёжнее, чем BETWEEN. Полуоткрытые диапазоны не зависят от точности TIMESTAMP, не пересекаются на границах и удобны для отчётности, пакетной обработки и инкрементальной загрузки данных.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤6👍5
Например, Event-Driven обеспечивает асинхронное распространение изменений между сервисами, а CQRS позволяет разделять операции чтения и записи с независимым обновлением моделей данных.
На картинке — 4 архитектурных подхода, которые помогают управлять согласованностью данных, снижать связанность компонентов и масштабировать распределённые системы.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤7👍4
Переносите строки без промежуточной выборки!
Обычно архивирование делают в два этапа: сначала копируют записи, затем удаляют исходные. В PostgreSQL можно связать эти действия через
А теперь используем возвращённые строки как источник для вставки:
Всё выполняется одним оператором SQL. Если вставка в архив завершится ошибкой, удаление тоже откатится.
При этом можно преобразовать данные перед архивированием:
🔥
➡️ SQL Ready | #совет
Обычно архивирование делают в два этапа: сначала копируют записи, затем удаляют исходные. В PostgreSQL можно связать эти действия через
DELETE ... RETURNING.-- Удаляем строки и сразу получаем их содержимое
DELETE FROM events
WHERE created_at < DATE '2025-01-01'
RETURNING id, user_id, payload, created_at;
А теперь используем возвращённые строки как источник для вставки:
WITH moved AS (
DELETE FROM events
WHERE created_at < DATE '2025-01-01'
RETURNING id, user_id, payload, created_at
)
INSERT INTO events_archive (
id, user_id, payload, created_at
)
SELECT id, user_id, payload, created_at
FROM moved;
Всё выполняется одним оператором SQL. Если вставка в архив завершится ошибкой, удаление тоже откатится.
При этом можно преобразовать данные перед архивированием:
WITH moved AS (
DELETE FROM events
WHERE created_at < DATE '2025-01-01'
RETURNING id, payload, created_at
)
INSERT INTO events_archive (id, payload, archived_at)
SELECT id, payload, now()
FROM moved;
DELETE ... RETURNING внутри WITH позволяет переносить строки между таблицами одним атомарным запросом, без отдельного чтения и передачи данных через приложение.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14❤8👍8
В этой статье:
• Узнаете, почему EXPLAIN не всегда помогает понять причины медленного выполнения SQL-запросов;• Разберёте, как pg_query_state позволяет отслеживать выполнение запросов в Apache Cloudberry в реальном времени;• Посмотрите, как архитектура с Go-агентами и сервисом YAGPCC помогает собирать метрики с множества узлов без перегрузки координатора.🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11🤝7🔥4
Почему перенос условия из ON в WHERE меняет семантику LEFT JOIN
При использовании
Рассмотрим запрос, который должен возвращать всех пользователей и количество их завершённых заказов:
Этот запрос исключает пользователей без завершённых заказов. Предикат
Для сохранения всех пользователей ограничение на статус необходимо включить в условие соединения:
Теперь пользователь без завершённых заказов остаётся в результате с количеством
В PostgreSQL аналогичную задачу можно решить через агрегатный
Различие между этими вариантами становится существенным при вычислении нескольких агрегатов.
Есть и важное следствие для оптимизатора PostgreSQL. Если условие в
Проверить фактический план выполнения можно через
В плане может появиться
🔥 Перенос предиката между
➡️ SQL Ready | #практика
При использовании
LEFT JOIN расположение предиката определяет не только состав результата, но и возможность сохранить строки левой таблицы без соответствий. Предикаты в ON участвуют в поиске совпадений, тогда как WHERE фильтрует результат соединения.Рассмотрим запрос, который должен возвращать всех пользователей и количество их завершённых заказов:
SELECT
u.id,
COUNT(o.id) AS completed_orders
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
WHERE o.status = 'completed'
GROUP BY u.id;
Этот запрос исключает пользователей без завершённых заказов. Предикат
o.status = 'completed' является null-rejecting: он не может вернуть TRUE, когда o.status содержит NULL. Поэтому строки, дополненные NULL при внешнем соединении, отбрасываются.Для сохранения всех пользователей ограничение на статус необходимо включить в условие соединения:
SELECT
u.id,
COUNT(o.id) AS completed_orders
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
AND o.status = 'completed'
GROUP BY u.id;
Теперь пользователь без завершённых заказов остаётся в результате с количеством
0. Здесь принципиально использование COUNT(o.id): агрегат игнорирует NULL, тогда как COUNT(*) учитывает и строку, созданную внешним соединением при отсутствии совпадений.В PostgreSQL аналогичную задачу можно решить через агрегатный
FILTER, сохранив соединение со всеми заказами:SELECT
u.id,
COUNT(o.id) FILTER (
WHERE o.status = 'completed'
) AS completed_orders
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
GROUP BY u.id;
Различие между этими вариантами становится существенным при вычислении нескольких агрегатов.
FILTER позволяет применять независимые условия к разным агрегатам без повторного соединения таблиц:SELECT
u.id,
COUNT(o.id) FILTER (
WHERE o.status = 'completed'
) AS completed_orders,
COUNT(o.id) FILTER (
WHERE o.status = 'pending'
) AS pending_orders
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
GROUP BY u.id;
Есть и важное следствие для оптимизатора PostgreSQL. Если условие в
WHERE исключает все строки с NULL со стороны правой таблицы, внешнее соединение в некоторых случаях может быть преобразовано во внутреннее. Такое преобразование сохраняет семантику исходного запроса, но не исправляет ошибочно выбранную логику фильтрации.Проверить фактический план выполнения можно через
EXPLAIN:EXPLAIN (ANALYZE, BUFFERS)
SELECT
u.id,
COUNT(o.id)
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
WHERE o.status = 'completed'
GROUP BY u.id;
В плане может появиться
Hash Join вместо Hash Left Join или другой тип внутреннего соединения. Конкретный алгоритм зависит от статистики, индексов и оценок стоимости, поэтому наличие LEFT JOIN в sql не гарантирует сохранение внешнего соединения в физическом плане.ON и WHERE при внешнем соединении — семантическое преобразование, а не просто изменение записи sql. Null-rejecting предикаты могут устранять строки без совпадений и создавать условия для упрощения LEFT JOIN до INNER JOIN оптимизатором.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤8👍7
Например, Two Pointers помогает эффективно работать с массивами, Sliding Window — находить подмассивы по заданным условиям, а Dynamic Programming — решать сложные задачи через повторное использование результатов подзадач.
На картинке — 16 популярных алгоритмических паттернов и структур данных, которые пригодятся на технических собеседованиях.
Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤5🤝4🔥3
Ускоряйте массовую загрузку через FREEZE!
При загрузке данных в новую таблицу PostgreSQL может сразу пометить строки как замороженные. Для этого у
Опция пригодится и при повторной полной загрузке таблицы:
🔥 Используйте
➡️ SQL Ready | #совет
При загрузке данных в новую таблицу PostgreSQL может сразу пометить строки как замороженные. Для этого у
COPY есть малоизвестная опция FREEZE.BEGIN;
CREATE TABLE events_import (
id bigint,
payload jsonb
);
COPY events_import
FROM '/data/events.csv'
WITH (FORMAT csv, FREEZE true);
COMMIT;
FREEZE позволяет избежать последующей работы по заморозке загруженных строк. Но есть условие: таблица должна быть создана или очищена через TRUNCATE в текущей подоперации транзакции.Опция пригодится и при повторной полной загрузке таблицы:
BEGIN;
TRUNCATE events_import;
COPY events_import
FROM '/data/events.csv'
WITH (FORMAT csv, FREEZE true);
COMMIT;
FREEZE не отключает обычные проверки ограничений и не гарантирует ускорения самого импорта. Его преимущество — уменьшение будущей работы VACUUM по заморозке строк.COPY FREEZE при полной загрузке новых или очищенных таблиц, чтобы сократить последующую работу по обслуживанию данных.Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥9❤4🤝4
This media is not supported in your browser
VIEW IN TELEGRAM
Статья о том, как работают индексы в PostgreSQL, зачем они нужны и в каких случаях помогают ускорить выполнение запросов. Материал объясняет принципы индексирования и влияние индексов на поиск, фильтрацию и обработку данных. Отдельное внимание уделяется ситуациям, когда индексы не используются или могут снижать производительность.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤7🔥3🤝2
Удаляйте данные небольшими порциями!
В PostgreSQL у
Такой запрос удалит не более 1000 строк за один запуск. Это позволяет очищать большие таблицы постепенно, не создавая одну огромную транзакцию.
Если очистку запускают несколько процессов, можно добавить блокировку с пропуском уже занятых строк:
Каждый запуск должен выполняться в отдельной транзакции. При повторении запроса таблица постепенно очищается без необходимости удалять всё сразу.
🔥
➡️ SQL Ready | #совет
В PostgreSQL у
DELETE нет LIMIT. Но ограничить количество удаляемых строк можно через подзапрос.DELETE FROM events
WHERE id IN (
SELECT id
FROM events
WHERE created_at < now() - interval '90 days'
LIMIT 1000
);
Такой запрос удалит не более 1000 строк за один запуск. Это позволяет очищать большие таблицы постепенно, не создавая одну огромную транзакцию.
Если очистку запускают несколько процессов, можно добавить блокировку с пропуском уже занятых строк:
DELETE FROM events
WHERE id IN (
SELECT id
FROM events
WHERE created_at < now() - interval '90 days'
FOR UPDATE SKIP LOCKED
LIMIT 1000
);
Каждый запуск должен выполняться в отдельной транзакции. При повторении запроса таблица постепенно очищается без необходимости удалять всё сразу.
DELETE через ограниченный подзапрос позволяет удалять данные порциями, сокращая длительность транзакций и нагрузку от массовой очистки.Please open Telegram to view this post
VIEW IN TELEGRAM
❤15👍8🔥4🤝1
Основные команды Git и схема взаимодействия между рабочей директорией, индексом (staging area), локальным и удалённым репозиториями.
На схеме также показано, как работают
git fetch, git pull и другие команды для управления изменениями и синхронизации репозиториев.Сохрани, чтобы не потерять!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍6🔥3🤝1
Почему ON CONFLICT DO NOTHING может скрывать ошибки в данных
В PostgreSQL
Допустим, есть таблица пользователей:
Добавим существующего пользователя:
Теперь приложение пытается зарегистрировать другого пользователя:
Запрос завершится без ошибки, но новая запись не появится. Причина в том, что
В данном случае
Чтобы обрабатывать только ожидаемый конфликт, следует явно указать его цель:
Теперь PostgreSQL пропустит вставку только при конфликте по
Дополнительно можно использовать
Если запись вставлена, запрос вернёт её
🔥 При использовании
➡️ SQL Ready | #практика
В PostgreSQL
ON CONFLICT DO NOTHING позволяет пропускать вставку строк при нарушении уникальности. Однако без указания конкретной цели конфликта PostgreSQL учитывает все подходящие уникальные ограничения и индексы. Из-за этого операция может быть пропущена по причине, которую бизнес-логика приложения не предусматривала.Допустим, есть таблица пользователей:
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
username TEXT NOT NULL UNIQUE
);
Добавим существующего пользователя:
INSERT INTO users (email, username)
VALUES ('alex@example.com', 'alex');
Теперь приложение пытается зарегистрировать другого пользователя:
INSERT INTO users (email, username)
VALUES ('maria@example.com', 'alex')
ON CONFLICT DO NOTHING;
Запрос завершится без ошибки, но новая запись не появится. Причина в том, что
ON CONFLICT DO NOTHING без указания цели конфликта обрабатывает нарушения уникальности по всем подходящим уникальным ограничениям и индексам.В данном случае
email уникален, но username = 'alex' уже существует. Если приложение проверяет только успешность выполнения SQL-запроса, оно может ошибочно считать регистрацию завершённой.Чтобы обрабатывать только ожидаемый конфликт, следует явно указать его цель:
INSERT INTO users (email, username)
VALUES ('maria@example.com', 'alex')
ON CONFLICT (email) DO NOTHING;
Теперь PostgreSQL пропустит вставку только при конфликте по
email. Нарушение уникальности username приведёт к ошибке 23505 (unique_violation).Дополнительно можно использовать
RETURNING:INSERT INTO users (email, username)
VALUES ('alex@example.com', 'alex2')
ON CONFLICT (email) DO NOTHING
RETURNING id;
Если запись вставлена, запрос вернёт её
id. Если вставка пропущена из-за конфликта по email, результат будет пустым. Это позволяет приложению отличать успешную вставку от пропущенной операции без дополнительного SELECT. Важно учитывать, что RETURNING при DO NOTHING не возвращает существующую строку и не сообщает причину конфликта.ON CONFLICT DO NOTHING важно явно указывать цель конфликта, если бизнес-логика допускает дубликаты только по определённому ключу. В сочетании с RETURNING это позволяет контролировать результат вставки, не скрывая нарушения уникальности по другим ключам.Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤7🔥5
Определение максимального и минимального значений с GREATEST() и LEAST(), лексикографическое сравнение строк через STRCMP(), NULL-безопасное сравнение с <=>, проверка принадлежности диапазону с BETWEEN и NOT BETWEEN, поиск совпадений в наборе значений через IN, а также сопоставление строк с шаблонами с помощью LIKE.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10🔥6🤝6👍1
Обновляйте агрегаты одним запросом!
Когда нужно пересчитать статистику по пользователям, не обязательно сначала получать агрегаты, а затем обновлять каждую запись отдельно.
PostgreSQL позволяет объединить
Здесь
Для этого на
🔥
➡️ SQL Ready | #совет
Когда нужно пересчитать статистику по пользователям, не обязательно сначала получать агрегаты, а затем обновлять каждую запись отдельно.
PostgreSQL позволяет объединить
INSERT ... SELECT с ON CONFLICT:INSERT INTO user_stats (user_id, total)
SELECT user_id, sum(amount)
FROM payments
GROUP BY user_id
ON CONFLICT (user_id)
DO UPDATE
SET total = EXCLUDED.total;
Здесь
GROUP BY вычисляет суммы, а ON CONFLICT обновляет существующие записи или добавляет новые.Для этого на
user_stats(user_id) должно быть ограничение уникальности. Можно сразу пересчитывать несколько показателей:INSERT INTO user_stats (user_id, total, orders_count)
SELECT user_id, sum(amount), count(*)
FROM payments
GROUP BY user_id
ON CONFLICT (user_id)
DO UPDATE SET
total = EXCLUDED.total,
orders_count = EXCLUDED.orders_count;
INSERT ... SELECT ... ON CONFLICT позволяет пересчитывать и сохранять агрегаты одним запросом, без циклов и промежуточной обработки в приложении.Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤5🔥5
This media is not supported in your browser
VIEW IN TELEGRAM
Статья о том, как повысить производительность PostgreSQL за счёт правильной настройки внутренних механизмов СУБД и проектирования структуры базы данных. Материал посвящён не только оптимизации SQL-запросов, но и причинам возникновения проблем с производительностью. Рассматриваются WAL (журнал предзаписи), VACUUM и AUTOVACUUM, очистка устаревших версий строк, управление транзакциями и проектирование таблиц.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤6🔥6
В этой статье:
• Узнаете, почему чтение с реплик PostgreSQL может угрожать отказоустойчивости и приводить к проблемам с актуальностью данных;• Разберёте, как организовать маршрутизацию запросов через Patroni, Consul и PgBouncer с учётом отставания репликации;• Посмотрите, как перенос части запросов на реплики помог снизить пиковую нагрузку на primary и какие риски при этом остались.🔊 Продолжай читать на Habr!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤7🔥5
Почему NOT EXISTS не всегда эквивалентен EXCEPT!
При сравнении двух наборов данных в sql часто используют
Рассмотрим задачу: найти товары поставщика, отсутствующие в основном каталоге. Создадим таблицы в PostgreSQL:
Один из вариантов решения — коррелированный
Результат (порядок строк не гарантируется):
Дубликаты сохраняются, поскольку внешний запрос не содержит
Строка с
Результат:
Здесь проявляются два свойства стандартного
Это отличается от обычного сравнения через
Результат:
Например, если товар встречается слева пять раз, а справа три раза, результат содержит две строки.
Ещё одно различие возникает при сравнении составных ключей.
Предположим, сравниваются пары
Обычное равенство не позволит сопоставить строки
Для NULL-безопасного сравнения стандарт SQL предусматривает
Чтобы одновременно устранить дубликаты и воспроизвести семантику
Результат:
Для этих столбцов запрос эквивалентен
🔥
➡️ SQL Ready | #практика
При сравнении двух наборов данных в sql часто используют
NOT EXISTS или EXCEPT. Оба подхода позволяют найти строки, присутствующие в одном наборе и отсутствующие в другом. Однако их семантика различается при наличии дубликатов и NULL.Рассмотрим задачу: найти товары поставщика, отсутствующие в основном каталоге. Создадим таблицы в PostgreSQL:
CREATE TABLE supplier_products (
product_id INT
);
CREATE TABLE catalog_products (
product_id INT
);
INSERT INTO supplier_products (product_id)
VALUES (10), (10), (20), (NULL);
INSERT INTO catalog_products (product_id)
VALUES (20), (NULL);
Один из вариантов решения — коррелированный
NOT EXISTS:SELECT s.product_id
FROM supplier_products s
WHERE NOT EXISTS (
SELECT 1
FROM catalog_products c
WHERE c.product_id = s.product_id
);
Результат (порядок строк не гарантируется):
product_id
----------
10
10
NULL
Дубликаты сохраняются, поскольку внешний запрос не содержит
DISTINCT.Строка с
NULL также попадает в результат: сравнение NULL = NULL не возвращает TRUE, поэтому соответствие не обнаруживается. Теперь выполним EXCEPT:SELECT product_id
FROM supplier_products
EXCEPT
SELECT product_id
FROM catalog_products;
Результат:
product_id
----------
10
Здесь проявляются два свойства стандартного
EXCEPT. Во-первых, EXCEPT без ALL устраняет дубликаты, оставляя только уникальные строки. Во-вторых, при операциях над множествами два значения NULL считаются неразличимыми. Поэтому NULL, присутствующий в обоих наборах, исключается из результата.Это отличается от обычного сравнения через
=. Если необходимо сохранить кратность строк, стандарт sql предусматривает EXCEPT ALL:SELECT product_id
FROM supplier_products
EXCEPT ALL
SELECT product_id
FROM catalog_products;
Результат:
product_id
----------
10
10
EXCEPT ALL работает с мультимножествами. Количество экземпляров каждой строки определяется формулой:max(количество_слева - количество_справа, 0)
Например, если товар встречается слева пять раз, а справа три раза, результат содержит две строки.
NOT EXISTS действует иначе: если найдено хотя бы одно соответствие, все строки внешнего набора с таким значением исключаются.Ещё одно различие возникает при сравнении составных ключей.
Предположим, сравниваются пары
(product_id, warehouse_id), причём warehouse_id может содержать NULL. Создадим таблицы:CREATE TABLE supplier_stock (
product_id INT,
warehouse_id INT
);
CREATE TABLE catalog_stock (
product_id INT,
warehouse_id INT
);
INSERT INTO supplier_stock VALUES
(10, NULL), (10, NULL), (20, 1);
INSERT INTO catalog_stock VALUES
(10, NULL);
Обычное равенство не позволит сопоставить строки
(10, NULL):SELECT s.product_id, s.warehouse_id
FROM supplier_stock s
WHERE NOT EXISTS (
SELECT 1
FROM catalog_stock c
WHERE c.product_id = s.product_id
AND c.warehouse_id = s.warehouse_id
);
Для NULL-безопасного сравнения стандарт SQL предусматривает
IS NOT DISTINCT FROM.Чтобы одновременно устранить дубликаты и воспроизвести семантику
EXCEPT без ALL, добавим DISTINCT:SELECT DISTINCT
s.product_id,
s.warehouse_id
FROM supplier_stock s
WHERE NOT EXISTS (
SELECT 1
FROM catalog_stock c
WHERE c.product_id IS NOT DISTINCT FROM s.product_id
AND c.warehouse_id IS NOT DISTINCT FROM s.warehouse_id
);
Результат:
product_id | warehouse_id
-----------+-------------
20 | 1
Для этих столбцов запрос эквивалентен
EXCEPT без ALL. Все примеры рассчитаны на PostgreSQL. Поддержка отдельных конструкций зависит от СУБД и версии. По производительности универсального победителя нет: результат зависит от данных, индексов и плана выполнения.NOT EXISTS проверяет отсутствие соответствия, EXCEPT вычисляет разность множеств без дубликатов, а EXCEPT ALL учитывает кратность строк. При выборе конструкции важно учитывать NULL и повторяющиеся значения.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍8❤6🤝2