Выполнение сложного запроса для каждой даты в диапазоне

9

У меня есть стол заказов

   Column   |            Type             |                      Modifiers                      
------------+-----------------------------+-----------------------------------------------------
 id         | integer                     | not null default nextval('orders_id_seq'::regclass)
 client_id  | integer                     | not null
 start_date | date                        | not null
 end_date   | date                        | 
 order_type | character varying           | not null

Данные имеют неперекрывающиеся постоянные заказы для client_id и иногда временный заказ, который переопределяет постоянный заказ на их start_date, когда они имеют совпадающий client_id. Существуют ограничения уровня приложения, которые не допускают перекрытия заказов одного типа.

 id | client_id | start_date |  end_date  | order_type 
----+-----------+------------+------------+------------
 17 |        11 | 2014-02-05 |            | standing
 18 |        15 | 2014-07-16 | 2015-07-19 | standing
 19 |        16 | 2015-04-01 |            | standing
 20 |        16 | 2015-07-18 | 2015-07-18 | temporary

Например, на 2015-07-18клиенте 16 есть заказ № 20, поскольку он является активным заказом, потому что он отменяет постоянный заказ № 19. С некоторой суетой я нашел эффективный способ запрашивать идентификаторы активных заказов на дату.

    SELECT id from (
      SELECT
        id,
        first_value(id) OVER (PARTITION BY client_id ORDER BY order_type DESC) active_order_id
      FROM orders
      WHERE start_date <= ? and (end_date is null OR end_date >= ?)
    ) active_orders
    WHERE id = active_order_id

Если вы запросите это 2015-07-18в качестве заполнителей, вы получите

 id 
----
 17
 18
 20

План запроса по этому запросу по сравнению с некоторыми другими моими идеями (такими как подзапросы, подсчитывающие количество временных заказов для клиента на дату) довольно мал, и я очень доволен этим. (дизайн стола, я не в восторге)

Теперь мне нужно найти все активные заказы для диапазона дат, объединенного с датами, в которые они активны. Например, с диапазоном дат 2015-07-18до 2015-07-19я бы хотел следующий результат.

active_date | id 
------------+----
 2015-07-18 | 17
 2015-07-18 | 18
 2015-07-18 | 20
 2015-07-19 | 17
 2015-07-19 | 18
 2015-07-19 | 19

Порядок 20 переопределяет порядок 19, 2015-07-18но не включен 2015-07-19.

Я обнаружил, generate_series()что могу сгенерировать диапазон дат, но я понятия не имею, как соединить это с этим, чтобы получить таблицу дат и идентификаторы заказов. Я предчувствую, что это взаимное объединение, но я не могу понять, как заставить это работать в таких условиях.

Спасибо

ОБНОВЛЕНИЕ Добавлена скрипта sql .

reconbot
источник
2
Не могли бы вы показать пример данных? Это активные / неактивные и временные вещи не очень понятны после первого чтения.
Дезсо
Да не понятно Ваш запрос найдет один заказ на клиента, и он не будет детерминированным. Если для клиента есть 2 или более заказов одного типа, то какой из двух будет возвращен, будет произвольным и варьироваться в зависимости от исполнения. Итак, у вас есть какие-то ограничения на таблицу, о которых вы нам не сообщили, или ваш запрос неверный.
ypercubeᵀᴹ
Я обновил свой вопрос с гораздо большим количеством деталей, и да, есть ограничения на данные.
перепроверю

Ответы:

5

Я бы использовал select distinct onвместо оконной функции, а затем просто присоединиться к дням.

select 
    distinct on (date, client_id) date, 
    id 
from orders
inner join generate_series('2015-07-18'::date, '2015-07-19'::date, '1 day') date
  on start_date <= date and (end_date is null or date <= end_date)
order by date, client_id, order_type desc

http://sqlfiddle.com/#!15/5a420/16/0

Я могу уточнить, если что-то не понятно.

Симон Перепелица
источник
Это не распространяется на временный или постоянный порядок, но это может быть сделано после объединения =)
перепроверка
Это определяет тот же порядок, что и в вашем окне запроса. Таким образом, для любого (date, client_id) он выбрал бы первый order_type в обратном алфавитном порядке.
Симон Перепелица
Внутреннее соединение идеально, а выделенное отличное намного проще для понимания (и работает примерно так же хорошо), чем окно. Любая другая причина, я не должен использовать функции управления окнами?
перепроверю
1
Вот и все. Я думаю, что distinct onэто даже более оптимизировано, чем запрос окна. Кстати, я должен упомянуть, что это распространенная проблема «top-in-group» в SQL: stackoverflow.com/questions/3800551/…
Симон Перепелица
Это отличное чтение, у меня есть кое-что для изучения. Если у вас есть время, у меня есть расширенная версия этого вопроса, которая использует то, что я узнал здесь. dba.stackexchange.com/questions/108767/… Я уверен, что вернусь, чтобы обновить его с тем, что я узнал по этой ссылке. И спасибо
перезагрузите
0

Напишите функцию, которая принимает одну дату в качестве параметра и возвращает список дат + идентификаторы, которые имеют порядок.

Затем используйте generate_series, как вы предложили, и вызывайте функцию в диапазоне дат.

Это общая стратегия при работе со сложными условиями в SQL.

Я включил немного кода ниже, но ответ на SQL выше намного проще.

Вот функция:

create or replace function o( date) returns setof INT AS '
SELECT id from (
 SELECT
  id,
  first_value(id) OVER (PARTITION BY client_id ORDER BY order_type DESC) active_order_id
 FROM orders
 WHERE start_date <= $1 and (end_date is null OR end_date >= $1)
) active_orders
WHERE id = active_order_id;
' LANGUAGE sql ;

И как это назвать:

select distinct d, o(d::date) 
from generate_series('2015-07-18'::date, '2015-07-19'::date, '1 day') as d;

SQLFiddle

Дон дрейк
источник
2
Возможно, вы захотите удалить этот ответ с некоторыми подробностями, примером кода и т. Д. Как таковой, этот ответ может быть удален, так как он довольно расплывчатый.
Макс Вернон
Вы могли бы обновить мою скрипку с примером? sqlfiddle.com/#!15/5a420/3/0
перенастроить
Я обновил свой ответ, включив в него некоторый код, но ответ выше проще.
Дон Дрейк