Это очень зависит от обстоятельств и точных требований. Рассмотрим мой комментарий к вопросу .
Простое решение
С DISTINCT ON
в Postgres:
SELECT DISTINCT ON (i.good, i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good, i.the_date, p.the_date DESC;
Заказанный результат.
Или NOT EXISTS
в стандартном SQL (работает с каждой знакомой мне СУБД):
SELECT i.the_date, p.the_date AS pricing_date, i.good, i.quantity, p.price
FROM inventory i
LEFT JOIN price p ON p.good = i.good AND p.the_date <= i.the_date
WHERE NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good = p.good
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
Тот же результат, но с произвольным порядком сортировки - если только вы не добавите ORDER BY
.
В зависимости от распределения данных, точных требований и показателей любой из них может быть быстрее.
Как правило, DISTINCT ON
это победитель, и вы получаете отсортированный результат поверх него. Но в некоторых случаях другие методы запросов (намного) еще быстрее. Смотри ниже.
Решения с подзапросами для вычисления максимальных / минимальных значений обычно медленнее. Варианты с CTE, как правило, медленнее, но все же.
Простые представления (например, предложенные в другом ответе) совсем не помогают производительности в Postgres.
SQL Fiddle.
Правильное решение
Строки и сопоставление
Прежде всего, вы страдаете от неоптимального расположения таблицы. Это может показаться тривиальным, но нормализация вашей схемы может иметь большое значение.
Сортировка по типам символов ( text
, varchar
, ...) должно быть сделано в соответствии с локалью - в COLLATION в частности. Скорее всего, ваша БД использует некоторый локальный набор правил (например, в моем случае:) de_AT.UTF-8
. Узнайте с помощью:
SHOW lc_collate;
Это замедляет сортировку и поиск по индексу . Чем дольше ваши строки (названия товаров), тем хуже. Если вы на самом деле не заботитесь о правилах сортировки в выходных данных (или о порядке сортировки вообще), это может быть быстрее, если вы добавите COLLATE "C"
:
SELECT DISTINCT ON (i.good COLLATE "C", i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good COLLATE "C", i.the_date, p.the_date DESC;
Обратите внимание, как я добавил сопоставление в двух местах.
В два раза быстрее в моем тесте с 20 тыс. Строк в каждой и очень простыми именами ('good123').
Индекс
Если ваш запрос должен использовать индекс, столбцы с символьными данными должны использовать сопоставление ( good
в примере):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
Обязательно прочитайте последние две главы этого связанного ответа на SO:
Вы можете даже иметь несколько индексов с разными параметрами сортировки в одних и тех же столбцах - если вам также нужны товары, отсортированные согласно другому (или стандартному) параметру сортировки в других запросах.
Нормализовать
Избыточные строки (имя хорошо) также раздувают ваши таблицы и индексы, что делает все еще медленнее. При правильном расположении таблицы вы можете избежать большинства проблем с самого начала. Может выглядеть так:
CREATE TABLE good (
good_id serial PRIMARY KEY
, good text NOT NULL
);
CREATE TABLE inventory (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, quantity int NOT NULL
, PRIMARY KEY(good_id, the_date)
);
CREATE TABLE price (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, price numeric NOT NULL
, PRIMARY KEY(good_id, the_date));
Первичные ключи автоматически предоставляют (почти) все нужные нам индексы.
В зависимости от отсутствующих деталей, многоколоночный индекс в price
порядке убывания во втором столбце может повысить производительность:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
Опять же, сопоставление должно соответствовать вашему запросу (см. Выше).
В Postgres 9.2 или более поздних версиях «индексы покрытия» для сканирования только по индексу могут помочь еще больше - особенно если в ваших таблицах содержатся дополнительные столбцы, что делает таблицу значительно больше, чем индекс покрытия.
Эти результирующие запросы выполняются намного быстрее:
НЕ СУЩЕСТВУЕТ
SELECT i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
AND NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good_id = p.good_id
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
ОТЛИЧАЕТСЯ НА
SELECT DISTINCT ON (i.the_date)
i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
ORDER BY i.the_date, p.the_date DESC;
SQL Fiddle.
Более быстрые решения
Если это все еще не достаточно быстро, могут быть более быстрые решения.
Рекурсивный CTE / JOIN LATERAL
/ коррелированный подзапрос
Специально для распространения данных со многими ценами за товар :
Материализованный вид
Если вам нужно выполнить это часто и быстро, я предлагаю вам создать материализованное представление. Я думаю, можно с уверенностью предположить, что цены и запасы за прошедшие даты редко меняются. Вычислите результат один раз и сохраните снимок как материализованное представление.
Postgres 9.3+ имеет автоматическую поддержку материализованных представлений. Вы можете легко реализовать базовую версию в более старых версиях.