Executando uma consulta complexa para cada data em um intervalo

9

Eu tenho uma tabela de pedidos

   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

Os dados têm pedidos permanentes não sobrepostos para um client_id e, ocasionalmente, um pedido temporário que substitui o pedido permanente em sua data de início, quando eles têm um client_id correspondente. Existem restrições no nível do aplicativo que impedem a sobreposição de pedidos do mesmo tipo.

 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

Por exemplo, no 2015-07-18cliente 16 tem o pedido nº 20, pois é um pedido ativo porque substitui o pedido permanente nº 19. Com alguma confusão, encontrei uma maneira eficiente de consultar IDs de pedidos ativos em uma data.

    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

Se você consultar isso 2015-07-18como espaços reservados, você obterá

 id 
----
 17
 18
 20

O plano de consulta dessa consulta em comparação com algumas das minhas outras idéias (como subconsultas que contam o número de pedidos temporários para um cliente em uma data) é muito pequeno e estou muito feliz com isso. (o design da mesa, não estou emocionado)

Agora, preciso encontrar todos os pedidos ativos para um período associado às datas em que eles estão ativos. Por exemplo, com o período de 2015-07-18até 2015-07-19eu gostaria do seguinte resultado.

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

A ordem 20 substitui a ordem 19 ativada, 2015-07-18mas não ativada 2015-07-19.

Descobri generate_series()que posso gerar um intervalo de datas, mas não faço ideia de como associá-lo a isso para obter uma tabela de datas e IDs de pedidos. Meu palpite é uma junção cruzada, mas não consigo descobrir como fazer isso funcionar nessa circunstância.

obrigado

ATUALIZAÇÃO Adicionado um violino sql .

reconectar
fonte
2
Você poderia mostrar alguns dados de exemplo? Essas coisas ativas / não ativas e temporárias não são muito claras após a primeira leitura.
Dezso
Sim, não está claro. Sua consulta encontrará um pedido por cliente e não parece ser determinístico. Se houver 2 ou mais pedidos para um cliente, com o mesmo tipo, qual dos dois será devolvido será arbitrário e variará por execução. Portanto, você tem algumas restrições na tabela que não nos informou ou sua consulta não está correta.
ypercubeᵀᴹ
Atualizei minha pergunta com muito mais detalhes, e sim, existem restrições nos dados.
reconbota 31/07

Respostas:

5

Eu usaria em select distinct onvez da função de janela, depois juntei os dias.

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

Eu posso elaborar mais se algo não estiver claro.

Simon Perepelitsa
fonte
Esta não cobre o pedido / ordem permanente temporária, mas que poderia ser feito após a juntar-se =)
reconbot
Isso especifica a mesma ordem que na sua consulta da janela. Portanto, para qualquer (data, client_id), ele selecionaria o primeiro order_type em ordem alfabética invertida.
Simon Perepelitsa
A junção interna é perfeita, e o select distinto é muito mais fácil de entender (e executa tão bem quanto) do que a janela. Algum outro motivo para eu não usar as funções de janelas?
reconbota 31/07
11
É sobre isso. Eu acho que distinct oné ainda mais otimizado do que a consulta da janela. A propósito, devo mencionar que este é um problema comum "top de grupo" no SQL: stackoverflow.com/questions/3800551/…
Simon Perepelitsa
É uma ótima leitura, tenho alguns estudos para fazer. Se você tiver algum tempo, tenho uma versão expandida desta pergunta que usa o que aprendi aqui. dba.stackexchange.com/questions/108767/… Tenho certeza de que voltarei para atualizá-lo com o que aprendi nesse link. E obrigado
reinicie o
0

Escreva uma função que utilize uma única data como parâmetro e retorne uma lista de data + IDs que têm um pedido.

Em seguida, use o generate_series conforme sugerido e chame a função no período.

Essa é uma estratégia comum ao lidar com condições complexas no SQL.

Incluímos algum código abaixo, mas a resposta SQL acima é muito mais simples.

Aqui está a função:

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 ;

E como chamá-lo:

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

SQLFiddle

Don Drake
fonte
2
Você pode esclarecer essa resposta com alguns detalhes, código de exemplo, etc. Assim, essa resposta pode ser excluída, pois é bastante vaga.
Max Vernon
Você poderia atualizar meu violino com um exemplo? sqlfiddle.com/#!15/5a420/3/0
reconbot
Atualizei minha resposta para incluir algum código, mas a resposta acima é mais simples.
Don Drake