Esecuzione di una query complessa per ogni data in un intervallo


9

Ho una tabella di ordini

   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

I dati hanno ordini permanenti non sovrapposti per un client_id e, occasionalmente, un ordine temporaneo che sovrascrive l'ordine permanente alla sua data_inizio quando hanno un client_id corrispondente. Esistono vincoli a livello di applicazione che impediscono la sovrapposizione di ordini dello stesso 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

Ad esempio, sul 2015-07-18client 16 ha l'ordine n. 20 come ordine attivo perché sovrascrive l'ordine permanente n. 19. Con un po 'di confusione ho trovato un modo efficiente di interrogare gli ID degli ordini attivi in ​​una 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 lo richiedi 2015-07-18come segnaposto, otterrai

 id 
----
 17
 18
 20

Il piano di query su questa query rispetto ad alcune delle mie altre idee (come le query secondarie che contano il numero di ordini temporanei per un cliente in una data) è piuttosto piccolo e ne sono abbastanza soddisfatto. (il design del tavolo, non sono elettrizzato)

Ora ho bisogno di un per trovare tutti gli ordini attivi per un intervallo di date unito alle date in cui sono attivi. Ad esempio, con l'intervallo di date di 2015-07-18a 2015-07-19vorrei il seguente risultato.

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

L'ordine 20 ha la precedenza sull'ordine 19, 2015-07-18ma non su 2015-07-19.

Ho scoperto generate_series()che posso generare un intervallo di date, ma non ho idea di come unirmi a questo per ottenere una tabella di date e ordinare gli ID. Il mio sospetto è un cross join ma non riesco a capire come farlo funzionare in questa circostanza.

Grazie

AGGIORNAMENTO Aggiunto un violino sql .


2
Potresti mostrare alcuni dati di esempio? Queste cose attive / non attive e temporanee non sono molto chiare dopo la prima lettura.
dezso

Sì, non è chiaro. La tua query troverà un ordine per client e non sembra essere deterministica. Se ci sono 2 o più ordini per un cliente, con lo stesso tipo, quale dei due verrà restituito sarà arbitrario e varierà per esecuzione. Quindi, hai alcuni vincoli sulla tabella che non ci hai detto o la tua query non è corretta.
ypercubeᵀᴹ

Ho aggiornato la mia domanda con molti più dettagli e sì, ci sono vincoli sui dati.
Riconnettere il

Risposte:


5

Vorrei utilizzare al select distinct onposto della funzione finestra, quindi solo unire i giorni.

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

Posso elaborare di più se qualcosa non è chiaro.


Questo non copre l'ordine temporaneo / l'ordine permanente ma ciò potrebbe essere fatto dopo il join =)
riconnettere il

Questo specifica lo stesso ordine della query della finestra. Quindi per qualsiasi (data, client_id) selezionerebbe il primo tipo_ordine in ordine alfabetico inverso.
Simon Perepelitsa,

L'unione interna è perfetta e la selezione distinta è molto più facile da capire (e si comporta altrettanto bene) rispetto alla finestra. Qualsiasi altro motivo per cui non dovrei usare le funzioni di windowing?
Riconnettere il

1
Questo è tutto. Penso che distinct onsia ancora più ottimizzato rispetto alla query della finestra. A proposito, devo dire che questo è un comune "top-in-group" problema di SQL: stackoverflow.com/questions/3800551/...
Simon Perepelitsa

È un'ottima lettura, ho qualche studio da fare. Se hai un po 'di tempo, ho una versione estesa di questa domanda che utilizza ciò che ho imparato qui. dba.stackexchange.com/questions/108767/… Sono sicuro che tornerò per aggiornarlo con quello che ho imparato da quel link. E grazie
riconcordo il

0

Scrivi una funzione che accetta una singola data come parametro e restituisce un elenco di data + ID che hanno un ordine.

Quindi, utilizzare generate_series come suggerito e chiamare la funzione nell'intervallo di date.

Questa è una strategia comune quando si affrontano condizioni complesse in SQL.

Ho incluso un po 'di codice qui sotto, ma la risposta SQL sopra è molto più semplice.

Ecco la funzione:

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 come chiamarlo:

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

SQLFiddle


2
Potresti voler cancellare quella risposta con alcuni dettagli, codice di esempio, ecc. Così com'è, questa risposta potrebbe essere cancellata poiché è piuttosto vaga.
Max Vernon,

Saresti in grado di aggiornare il mio violino con un esempio? sqlfiddle.com/#!15/5a420/3/0
riconnettere il

Ho aggiornato la mia risposta per includere un po 'di codice ma la risposta sopra è più semplice.
Don Drake,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.