Це дуже залежить від обставин та точних вимог. Розгляньте мій коментар до питання .
Просте рішення
З 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 (працює з усіма мені відомими RDBMS):
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
це переможець, і ви отримуєте відсортований результат поверх нього. Але для певних випадків інші методи запиту (набагато) швидші, але. Дивіться нижче.
Рішення з підзапросами для обчислення значень max / min, як правило, повільніше. Варіанти з 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);
Обов’язково прочитайте останні два глави цієї пов’язаної відповіді на ТА:
Ви можете навіть мати кілька індексів з різними порівняннями в одних і тих же стовпцях - якщо вам також потрібні товари, відсортовані за іншим (або за замовчуванням) порівнянням в інших запитах.
Нормалізувати
Надлишки рядків (ім'я хорошого) також роздувають ваші таблиці та індекси, що робить все ще повільніше. При правильному розташуванні таблиці ви могли б уникнути більшості проблем. Може виглядати так:
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+ має автоматизовану підтримку матеріалізованих представлень. Ви можете легко реалізувати базову версію в старих версіях.