PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Как да намерите първите безплатни начални часове от резервации в Postgres

Адаптирана схема

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();

Основни точки

Настрани
Освен опростяване на ограниченията за въвеждане и проверка очаквах timerange за да спестите 8 байта място за съхранение в сравнение с tsrange от time заема само 4 байта. Но се оказва timerange заема 22 байта на диска (25 в RAM), точно като tsrange (или tstzrange ). Така че можете да използвате tsrange както добре. Принципът на заявката и ограничението за изключване са еднакви.

Запитване

Обвито в SQL функция за удобна работа с параметри:

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;

Обаждане:

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

SQL Fiddle на Postgres 9.3 сега.

Обяснете

  • Функцията приема _start timestamp като минимално начално време и _duration interval . Внимавайте да изключите само по-ранните часове на началото ден, а не следващите дни. Най-простият чрез просто добавяне на ден и час:t + d > _start .
    За да резервирате резервация, започваща "сега", просто подайте now()::timestamp :

    SELECT * FROM f_next_free(`now()::timestamp`, '1.5 h'::interval);
    
  • Подзаявка d генерира дни, започвайки от въведената стойност _day . Празниците са изключени.

  • Дните са кръстосано свързани с възможни времеви диапазони, генерирани в подзаявка t .
  • Това е кръстосано свързано с всички налични работници w .
  • Накрая елиминирайте всички кандидати, които се сблъскват със съществуващи резервации, като използвате NOT EXISTS анти-полусъединяване, и по-специално операторът за припокриване && .

Свързани:



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Намерете записи, където присъединяването не съществува

  2. Rails отчетите не могат да намерят колона, която е там

  3. Създаване на таблица от MySQL към PostgreSQL - набор от знаци и сортиране

  4. postgres заявката с IN е много бавна

  5. Cygnus-NGSI няма да записва данни в PostgreSQL