PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak znaleźć pierwsze darmowe godziny startu z rezerwacji w Postgres

Schemat dostosowany

CREATE EXTENSION btree_gist;
CREATE TYPE timerange AS RANGE (subtype = time);  -- create type once

-- Workers
CREATE TABLE worker(
   worker_id serial PRIMARY KEY
 , worker text NOT NULL
);
INSERT INTO worker(worker) VALUES ('JOHN'), ('MARY');

-- Holidays
CREATE TABLE pyha(pyha date PRIMARY KEY);

-- Reservations
CREATE TABLE reservat (
   reservat_id serial PRIMARY KEY
 , worker_id   int NOT NULL REFERENCES worker ON UPDATE CASCADE
 , day         date NOT NULL CHECK (EXTRACT('isodow' FROM day) < 7)
 , work_from   time NOT NULL -- including lower bound
 , work_to     time NOT NULL -- excluding upper bound
 , CHECK (work_from >= '10:00' AND work_to <= '21:00'
      AND work_to - work_from BETWEEN interval '15 min' AND interval '4 h'
      AND EXTRACT('minute' FROM work_from) IN (0, 15, 30, 45)
      AND EXTRACT('minute' FROM work_from) IN (0, 15, 30, 45)
    )
 , EXCLUDE USING gist (worker_id WITH =, day WITH =
                     , timerange(work_from, work_to) WITH &&)
);
INSERT INTO reservat (worker_id, day, work_from, work_to) VALUES 
   (1, '2014-10-28', '10:00', '11:30')  -- JOHN
 , (2, '2014-10-28', '11:30', '13:00'); -- MARY

-- Trigger for volatile checks
CREATE OR REPLACE FUNCTION holiday_check()
  RETURNS trigger AS
$func$
BEGIN
   IF EXISTS (SELECT 1 FROM pyha WHERE pyha = NEW.day) THEN
      RAISE EXCEPTION 'public holiday: %', NEW.day;
   ELSIF NEW.day < now()::date OR NEW.day > now()::date + 31 THEN
      RAISE EXCEPTION 'day out of range: %', NEW.day;
   END IF;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql STABLE; -- can be "STABLE"

CREATE TRIGGER insupbef_holiday_check
BEFORE INSERT OR UPDATE ON reservat
FOR EACH ROW EXECUTE PROCEDURE holiday_check();

Główne punkty

  • Nie używaj char(n) . Raczej varchar(n) lub jeszcze lepiej varchar lub po prostu text .

  • Nie używaj nazwy pracownika jako klucza podstawowego. Niekoniecznie jest wyjątkowy i może się zmieniać. Zamiast tego użyj zastępczego klucza podstawowego, najlepiej serial . Tworzy również wpisy w reservat mniejsze, indeksy mniejsze, zapytania szybsze, ...

  • Aktualizacja: Dla tańszego przechowywania (8 bajtów zamiast 22) i prostszej obsługi zapisuję początek i koniec jako time teraz i skonstruuj w locie zakres dla ograniczenia wykluczenia:

    EXCLUDE USING gist (worker_id WITH =, day WITH =
                      , timerange(work_from, work_to) WITH &&)
    
  • Ponieważ Twoje zakresy nigdy nie przekroczą granicy dat z definicji bardziej efektywne byłoby posiadanie oddzielnej date kolumna (day w mojej implementacji) i zakres czasu . Typ timerange nie jest dostarczany w domyślnych instalacjach, ale można go łatwo utworzyć. W ten sposób możesz w dużym stopniu uprościć ograniczenia sprawdzania.

  • Użyj EXTRACT('isodow', ...) uprościć wykluczenie niedziel

  • Zakładam, że chcesz zezwolić górna granica „21:00”.

  • Zakłada się, że granice obejmują dolną granicę i wykluczają górną granicę.

  • Sprawdzenie, czy nowe / zaktualizowane dni leżą w ciągu miesiąca od „teraz”, nie jest IMMUTABLE . Przeniesiono go z CHECK ograniczenie do wyzwalacza - w przeciwnym razie możesz napotkać problemy ze zrzutem / przywróceniem! Szczegóły:

Na bok
Oprócz uproszczenia wprowadzania i sprawdzania ograniczeń spodziewałem się timerange zaoszczędzić 8 bajtów pamięci w porównaniu z tsrange od time zajmuje tylko 4 bajty. Ale okazuje się, że timerange zajmuje 22 bajty na dysku (25 w pamięci RAM), podobnie jak tsrange (lub tstzrange ). Więc możesz iść z tsrange również. Zasada zapytania i ograniczenia wykluczenia jest taka sama.

Zapytanie

Opakowane w funkcję SQL dla wygodnej obsługi parametrów:

CREATE OR REPLACE FUNCTION f_next_free(_start timestamp, _duration interval)
  RETURNS TABLE (worker_id int, worker text, day date
               , start_time time, end_time time) AS
$func$
   SELECT w.worker_id, w.worker
        , d.d AS day
        , t.t AS start_time
        ,(t.t + _duration) AS end_time
   FROM  (
      SELECT _start::date + i AS d
      FROM   generate_series(0, 31) i
      LEFT   JOIN pyha p ON p.pyha = _start::date + i
      WHERE  p.pyha IS NULL   -- eliminate holidays
      ) d
   CROSS  JOIN (
      SELECT t::time
      FROM   generate_series (timestamp '2000-1-1 10:00'
                            , timestamp '2000-1-1 21:00' - _duration
                            , interval '15 min') t
      ) t  -- times
   CROSS  JOIN worker w
   WHERE  d.d + t.t > _start  -- rule out past timestamps
   AND    NOT EXISTS (
      SELECT 1
      FROM   reservat r
      WHERE  r.worker_id = w.worker_id
      AND    r.day = d.d
      AND    timerange(r.work_from, r.work_to) && timerange(t.t, t.t + _duration)
      )
   ORDER  BY d.d, t.t, w.worker, w.worker_id
   LIMIT  30  -- could also be parameterized
$func$ LANGUAGE sql STABLE;

Zadzwoń:

SELECT * FROM f_next_free('2014-10-28 12:00'::timestamp, '1.5 h'::interval);

Skrzypce SQL w Postgresie 9.3 teraz.

Wyjaśnij

  • Funkcja przyjmuje _start timestamp jako minimalny czas rozpoczęcia i _duration interval . Uważaj, aby rozpoczęcie wykluczyć tylko wcześniejszy czas dzień, a nie kolejne dni. Najprościej, wystarczy dodać dzień i godzinę:t + d > _start .
    Aby zarezerwować rezerwację rozpoczynającą się „teraz”, po prostu przekaż now()::timestamp :

    SELECT * FROM f_next_free(`now()::timestamp`, '1.5 h'::interval);
    
  • Podzapytanie d generuje dni zaczynając od wartości wejściowej _day . Wyłączone święta.

  • Dni są połączone z możliwymi zakresami czasu wygenerowanymi w podzapytaniu t .
  • To jest połączone krzyżowo ze wszystkimi dostępnymi procesami roboczymi w .
  • Na koniec wyeliminuj wszystkich kandydatów, którzy kolidują z istniejącymi rezerwacjami, używając NOT EXISTS anti-semi-join, a w szczególności operator nakładania się && .

Powiązane:



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Funkcja plpgsql, która zwraca wiele kolumn, jest wywoływana wielokrotnie

  2. Znajdź najdłuższą passę doskonałych wyników na gracza

  3. Logowanie wyzwalaczy w postgresie 9.1

  4. Django unikalny związek z polem i wieloma kobietami na sobie

  5. Funkcja MAX() w PostgreSQL