2013-10-22 11 views
5

un sistema di noleggio utilizza una tabella di prenotazione per memorizzare tutte le prenotazioni e le prenotazioni:Trova abbastanza grande lacune nella tabella di prenotazione

booking | item | startdate  | enddate 
1  | 42 | 2013-10-25 16:00 | 2013-10-27 12:00 
2  | 42 | 2013-10-27 14:00 | 2013-10-28 18:00 
3  | 42 | 2013-10-30 09:00 | 2013-11-01 09:00 
… 

Diciamo che un utente vuole affittare voce 42 dal 2013/10/27 12:00 alle 2013-10-28 12:00, che è un periodo di un giorno. Il sistema gli dirà che l'oggetto non è disponibile in un dato periodo di tempo, dal momento che la prenotazione no. 2 si scontra.

Ora desidero suggerire la data e l'ora di noleggio meno recenti quando l'elemento selezionato è di nuovo disponibile. Naturalmente considerando il periodo richiesto dall'utente (1 giorno) a partire dalla data e ora desiderate dall'utente.

Quindi nel caso precedente, sto cercando una query SQL che restituisce 2013-10-28 18:00, dalla prima data dal 2013-10-27 alle 12:00 in cui l'elemento 42 sarà disponibile per 1 giorno, dal 2013-10-28 alle 18:00 fino al 2013-10-29 alle 18:00.

Quindi ho bisogno di trovare un divario tra le prenotazioni, che è abbastanza grande da contenere la prenotazione dell'utente e che è il più vicino possibile alla data di inizio desiderata.

O in altre parole: ho bisogno di trovare la prima prenotazione per un determinato articolo, dopo di che c'è abbastanza tempo libero per effettuare la prenotazione dell'utente.

Ciò è possibile in SQL semplice senza dover eseguire iterazioni su ogni prenotazione e sul suo successore?

+0

"il più vicino possibile alla data di inizio desiderata." Significa veramente "il prima possibile * o dopo * la data/ora di inizio desiderata?" –

+0

@OllieJones Significa il prima possibile * dopo * la data/ora desiderata. – Rob

+0

I sistemi di prenotazione di solito hanno una granularità definita, è questo il tuo caso? * (I tempi di prenotazione devono essere orari esatti, o forse consentire minuti, ma solo in multipli di 15? 12:00, 12:15, ecc.?) * – MatBailie

risposta

1

La ricerca di intervalli di date sovrapposti genera generalmente prestazioni scarse in SQL. Per questo motivo avere un "Calendario" di slot disponibili spesso rende le cose molto più efficienti.

Ad esempio, la prenotazione 2013-10-25 16:00 => 2013-10-27 12:00 sarebbe effettivamente rappresentata da 44 record, ciascuno della durata di un'ora.

Il "gap" fino alla prossima prenotazione allo 2013-10-27 14:00 sarebbe quindi rappresentato da 2 record, ciascuno lungo un'ora.

Quindi, ogni record potrebbe anche avere la durata (in tempo o numero di slot) fino alla successiva modifica.

slot_start_time | booking | item | remaining_duration 
------------------+---------+------+-------------------- 
2013-10-27 10:00 | 1 | 42 |  2 
2013-10-27 11:00 | 1 | 42 |  1 
2013-10-27 12:00 | NULL | 42 |  2 
2013-10-27 13:00 | NULL | 42 |  1 
2013-10-27 14:00 | 2 | 42 |  28 
2013-10-27 15:00 | 2 | 42 |  27 
...    | ... | ... | ... 
2013-10-28 17:00 | 2 | 42 |  1 
2013-10-28 18:00 | NULL | 42 |  39 
2013-10-28 19:00 | NULL | 42 |  38 

Poi la ricerca diventa solo:

SELECT 
    * 
FROM 
    slots 
WHERE 
    slot_start_time >= '2013-10-27 12:00' 
    AND remaining_duration >= 24 
    AND booking IS NULL 
ORDER BY 
    slot_start_time ASC 
LIMIT 
    1 
+0

Interessante, ma ciò significa che quando la nuova possibilità di prenotazione trovata viene effettivamente accettata dal cliente ed è necessario "INSERIRE" nel database, è necessario modificare il valore di "restituzione_durata" di tutti i record appartenenti alla prenotazione subito prima nuova prenotazione, corretta? – funkwurm

2
SELECT startfree,secondsfree FROM (
    SELECT 
    @lastenddate AS startfree, 
    UNIX_TIMESTAMP(startdate)-UNIX_TIMESTAMP(@lastenddate) AS secondsfree, 
    @lastenddate:=enddate AS ignoreme 
    FROM 
    (SELECT startdate,enddate FROM bookings WHERE item=42) AS schedule, 
    (SELECT @lastenddate:=NOW()) AS init 
    ORDER BY startdate 
) AS baseview 
WHERE startfree>='2013-10-27 12:00:00' 
    AND secondsfree>=86400 
ORDER BY startfree 
LIMIT 1 
; 

Qualche spiegazione: La query interna utilizza una variabile per spostare l'iterazione in SQL, query esterna rileva la riga necessaria.

Detto questo, non lo farei in SQL, se la struttura del DB è come data. È possibile ridurre il conteggio delle iterazioni utilizzando un po 'di smusso WHERE nella query interna con un intervallo di tempo ragionevole, ma è probabile che ciò non si ripercuota correttamente.

EDIT

Un avvertimento: Non ho controllato, ma suppongo, questo non funziona, se non ci sono previa prenotazione nella lista - questo non dovrebbe essere un problema, come in questo caso il tuo primo tentativo di prenotazione (ora originale) funzionerà.

EDIT

SQLfiddle

+0

Vorrei pre-seed @lastenddate. Qualcosa come 'SELECT @lastenddate: = COALESCE (MAX (enddate), '2013-10-27 12:00:00') DALLE prenotazioni DOVE startdate <'2013-10-27 12: 00: 00'', quindi usa anche quello in una clausola WHERE per cercare solo da quel punto in poi. – MatBailie

+0

Non l'ho fatto, visto che tecnicamente sono 2 query ... –

+0

Hai già diverse query nidificate, sono sicuro che andrebbe bene. Basta cambiare 'init' e aggiungere' WHERE schedule.startdate> = init.timestamp'? Qualcosa del genere? – MatBailie

1

OK questo non è abbastanza in MySQL. Questo perché dobbiamo falsificare i valori del rownum nelle subquery.

L'approccio di base è quello di unire il sottoinsieme appropriato della tabella di prenotazione a se stesso sfasato di uno.

Ecco l'elenco di base delle prenotazioni per l'articolo 42, ordinato per ora di prenotazione. Non possiamo ordinare tramite booking_id, perché non è garantito che questi siano in ordine di orario di prenotazione. (Stai cercando di inserire una nuova prenotazione tra due esistenti, eh?) http://sqlfiddle.com/#!2/62383/9/0

SELECT @aserial := @aserial+1 AS rownum, 
     booking.* 
    FROM booking, 
     (SELECT @aserial:= 0) AS q 
    WHERE item = 42 
    ORDER BY startdate, enddate 

Ecco quel sottoinsieme unito a se stesso. Il trucco è lo a.rownum+1 = b.rownum, che unisce ciascuna riga a quella immediatamente successiva nel sottoinsieme della tabella di prenotazione. http://sqlfiddle.com/#!2/62383/8/0

SELECT a.booking_id, a.startdate asta, a.enddate aend, 
        b.startdate bsta, b.enddate bend 
    FROM (
     SELECT @aserial := @aserial+1 AS rownum, 
      booking.* 
     FROM booking, 
      (SELECT @aserial:= 0) AS q 
     WHERE item = 42 
     ORDER BY startdate, enddate 
     ) AS a 
    JOIN (
     SELECT @bserial := @bserial+1 AS rownum, 
      booking.* 
     FROM booking, 
      (SELECT @bserial:= 0) AS q 
     WHERE item = 42 
     ORDER BY startdate, enddate 
     ) AS b ON a.rownum+1 = b.rownum 

eccolo di nuovo, mostrando ogni prenotazione (tranne l'ultimo) e il numero di ore dopo di esso. http://sqlfiddle.com/#!2/62383/15/0

SELECT a.booking_id, a.startdate, a.enddate, 
     TIMESTAMPDIFF(HOUR, a.enddate, b.startdate) gaphours 
    FROM (
     SELECT @aserial := @aserial+1 AS rownum, 
      booking.* 
     FROM booking, 
      (SELECT @aserial:= 0) AS q 
     WHERE item = 42 
     ORDER BY startdate, enddate 
     ) AS a 
    JOIN (
     SELECT @bserial := @bserial+1 AS rownum, 
      booking.* 
     FROM booking, 
      (SELECT @bserial:= 0) AS q 
     WHERE item = 42 
     ORDER BY startdate, enddate 
     ) AS b ON a.rownum+1 = b.rownum 

Quindi, se siete alla ricerca per l'ora di inizio e l'ora del primo slot di dodici ore fine è possibile utilizzare tale set di risultati per fare questo: http://sqlfiddle.com/#!2/62383/18/0

SELECT MIN(enddate) startdate, MIN(enddate) + INTERVAL 12 HOUR as enddate 
    FROM (
    SELECT a.booking_id, a.startdate, a.enddate, 
      TIMESTAMPDIFF(HOUR, a.enddate, b.startdate) gaphours 
     FROM (
      SELECT @aserial := @aserial+1 AS rownum, 
       booking.* 
      FROM booking, 
       (SELECT @aserial:= 0) AS q 
      WHERE item = 42 
      ORDER BY startdate, enddate 
      ) AS a 
     JOIN (
      SELECT @bserial := @bserial+1 AS rownum, 
       booking.* 
      FROM booking, 
       (SELECT @bserial:= 0) AS q 
      WHERE item = 42 
      ORDER BY startdate, enddate 
      ) AS b ON a.rownum+1 = b.rownum 
    ) AS gaps 
    WHERE gaphours >= 12 
1

qui è la interrogazione, sarà data di ritorno necessaria, condizione ovvia - ci dovrebbero essere alcuni prenotazioni in tavola, ma per come la vedo dalla domanda - si fa questo controllo:

SELECT min(enddate) 
FROM 
(
    select a.enddate from table4 as a 
    where 
     a.item=42 
    and 
     DATE_ADD(a.enddate, INTERVAL 1 day) <= ifnull(
      (select min(b.startdate) 
      from table4 as b where b.startdate>=a.enddate and a.item=b.item), 
     a.enddate) 
    and 
     a.enddate>=now() 
    union all 
    select greatest(ifnull(max(enddate), now()),now()) from table4 
) as q 

si chan ge change INTERVAL 1 day a INTERVAL ### hour

+0

Condizione ** s **. Hai bisogno di una prenotazione dopo così come prima del periodo che stai cercando di prenotare. Probabilmente "indicatori stupidi", una prenotazione di 0 ore al 2000-01-01 e 2999-12-31 – MatBailie

+0

@MatBailie hai ragione, la query aggiornata per funzionare correttamente anche in questo caso –

3

Se non è possibile riprogettare il database per utilizzare qualcosa di più efficiente, si otterrà la risposta. Ovviamente vorrete parametrizzarlo. Dice trovare sia la data desiderata, o alla prima data di fine in cui l'intervallo di noleggio non si sovrapponga una prenotazione:

Select 
    min(startdate) 
From (
    select 
     cast('2013-10-27 12:00' as datetime) startdate 
    from 
     dual 
    union all 
    select 
     enddate 
    from 
     booking 
    where 
     enddate > cast('2013-10-27 12:00' as datetime) and 
     item = 42 
    ) b1 
Where 
    not exists (
     select 
      'x' 
     from 
      booking b2 
     where 
      item = 42 and 
      b1.startdate < b2.enddate and 
      b2.startdate < date_add(b1.startdate, interval 24 hour) 
    ); 

Example Fiddle

1

Se ho capito bene le vostre esigenze, potrete potrebbe provare autonomamente l'unione di book con se stesso, per ottenere gli spazi "vuoti" e quindi adattarsi. Questo è solo MySQL (credo che può essere adattato ad altri - certamente PostgreSQL):

SELECT book.*, TIMESTAMPDIFF(MINUTE, book.enddate, book.best) AS width FROM 
(
    SELECT book.*, MIN(book1.startdate) AS best 
    FROM book 
    JOIN book AS book1 USING (item) 
    WHERE item = 42 AND book1.startdate >= book.enddate 
    GROUP BY book.booking 
) AS book HAVING width > 110 ORDER BY startdate LIMIT 1; 

Nell'esempio di cui sopra, "110" è la guardò per la larghezza minima in pochi minuti.

Stessa cosa, un po 'meno leggibile (per me), un SELECT rimosso (molto veloce SELECT, così poco vantaggio):

SELECT book.*, MIN(book1.startdate) AS best 
    FROM book 
    JOIN book AS book1 ON (book.item = book1.item AND book.item = 42) 
WHERE book1.startdate >= book.enddate 
    GROUP BY book.booking 
    HAVING TIMESTAMPDIFF(MINUTE, book.enddate, best) > 110 
    ORDER BY startdate LIMIT 1; 

Nel tuo caso, un giorno è 1440 minuti e

SELECT book.*, MIN(book1.startdate) AS best  FROM book  JOIN book AS book1 ON (book.item = book1.item AND book.item = 42)  WHERE book1.startdate >= book.enddate  GROUP BY book.booking  HAVING TIMESTAMPDIFF(MINUTE, book.enddate, best) >= 1440  ORDER BY startdate LIMIT 1; 
+---------+------+---------------------+---------------------+---------------------+ 
| booking | item | startdate   | enddate    | best    | 
+---------+------+---------------------+---------------------+---------------------+ 
|  2 | 42 | 2013-10-27 14:00:00 | 2013-10-28 18:00:00 | 2013-10-30 09:00:00 | 
+---------+------+---------------------+---------------------+---------------------+ 
1 row in set (0.00 sec) 

... il periodo restituito è 2, vale a dire, al termine della prenotazione 2, e fino a "migliore" che è la prenotazione 3, è disponibile un periodo di almeno 1440 minuti.

Un problema potrebbe essere che se non periodi sono disponibili, la query restituisce nulla - allora avete bisogno di un altro query per recuperare il più lontano enddate. È possibile farlo con un UNION e LIMIT 1 ovviamente, ma penso che sarebbe meglio solo eseguire la query di "recupero" su richiesta, a livello di codice (ad esempio if empty(query) then new_query...).

Inoltre, all'interno WHERE è necessario aggiungere un assegno per NOW() per evitare date in passato. Se le prenotazioni scadute vengono spostate nella memoria inattiva, ciò potrebbe non essere necessario.

Problemi correlati