PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Cara menemukan waktu mulai gratis pertama dari reservasi di Postgres

Skema yang disesuaikan

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

Poin utama

  • Jangan gunakan char(n) . Melainkan varchar(n) , atau lebih baik lagi, varchar atau cukup text .

  • Jangan gunakan nama pekerja sebagai kunci utama. Belum tentu unik dan bisa berubah. Gunakan kunci primer pengganti sebagai gantinya, sebaiknya serial . Juga buat entri di reservat lebih kecil, indeks lebih kecil, kueri lebih cepat, ...

  • Pembaruan: Untuk penyimpanan yang lebih murah (8 byte, bukan 22) dan penanganan yang lebih sederhana, saya menyimpan awal dan akhir sebagai time sekarang dan buat rentang dengan cepat untuk batasan pengecualian:

    EXCLUDE USING gist (worker_id WITH =, day WITH =
                      , timerange(work_from, work_to) WITH &&)
    
  • Karena rentang Anda tidak boleh melewati batas tanggal menurut definisi, akan lebih efisien untuk memiliki date yang terpisah kolom (day dalam implementasi saya) dan rentang waktu . Jenis timerange tidak dikirimkan dalam instalasi default, tetapi mudah dibuat. Dengan cara ini Anda dapat menyederhanakan batasan pemeriksaan Anda.

  • Gunakan EXTRACT('isodow', ...) untuk menyederhanakan kecuali hari Minggu

  • Saya berasumsi Anda ingin mengizinkan batas atas '21:00'.

  • Batas dianggap termasuk untuk batas bawah dan tidak termasuk untuk batas atas.

  • Pemeriksaan apakah hari baru / yang diperbarui terletak dalam waktu satu bulan dari "sekarang" bukan IMMUTABLE . Memindahkannya dari CHECK kendala pada pemicu - jika tidak, Anda mungkin mengalami masalah dengan dump / restore! Detail:

Samping
Selain menyederhanakan input dan memeriksa batasan, saya mengharapkan timerange untuk menghemat 8 byte penyimpanan dibandingkan dengan tsrange sejak time hanya menempati 4 byte. Tapi ternyata timerange menempati 22 byte pada disk (25 dalam RAM), seperti tsrange (atau tstzrange ). Jadi Anda bisa menggunakan tsrange demikian juga. Prinsip batasan kueri dan pengecualian adalah sama.

Kueri

Dibungkus ke dalam fungsi SQL untuk penanganan parameter yang nyaman:

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;

Telepon:

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

SQL Fiddle di Postgres 9.3 sekarang.

Jelaskan

  • Fungsi ini mengambil _start timestamp sebagai waktu mulai minimum dan _duration interval . Berhati-hatilah untuk hanya mengesampingkan waktu yang lebih awal pada mulai hari, bukan hari-hari berikutnya. Paling sederhana hanya dengan menambahkan hari dan waktu:t + d > _start .
    Untuk memesan reservasi mulai "sekarang", cukup lewati now()::timestamp :

    SELECT * FROM f_next_free(`now()::timestamp`, '1.5 h'::interval);
    
  • Subkueri d menghasilkan hari mulai dari nilai input _day . Tidak termasuk hari libur.

  • Hari digabungkan silang dengan kemungkinan rentang waktu yang dihasilkan dalam subkueri t .
  • Itu digabungkan ke semua pekerja yang tersedia w .
  • Akhirnya hilangkan semua kandidat yang berbenturan dengan reservasi yang ada menggunakan NOT EXISTS anti-semi-join, dan khususnya operator tumpang tindih && .

Terkait:



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. PostgresSQL / pgAdmin4 / dump versi server tidak cocok

  2. Banyak-ke-Banyak Hubungan antara dua tabel dalam dua database yang berbeda

  3. Bagaimana saya bisa membuat kolom di postgres dari nilai dan pilihan berdasarkan kolom lain?

  4. Kesalahan dengan auto_increment saat terhubung ke Postgres melalui psql dan putTY

  5. PostgreSQL - nilai serial berikutnya dalam sebuah tabel