Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Uzyskaj dostępne godziny zajęć w zakresie dat

Szukałem rozwiązania podobnego w koncepcji przynajmniej do Wernfrieda, ale myślę, że jest wystarczająco inne, aby je opublikować. Początek jest taki sam, najpierw generując możliwe przedziały czasowe i zakładając, że patrzysz na 15-minutowe okna:używam CTE, ponieważ myślę, że są one wyraźniejsze niż zagnieżdżone selekcje, szczególnie na tak wielu poziomach.

with date_time_range as (
  select to_date('10/10/2013 07:00', 'DD/MM/YYYY HH24:MI') as date_start,
    to_date('10/10/2013 21:15', 'DD/MM/YYYY HH24:MI') as date_end
  from dual
),
time_slots as (
  select level as slot_num,
    dtr.date_start + (level - 1) * interval '15' minute as slot_start,
    dtr.date_start + level * interval '15' minute as slot_end
  from date_time_range dtr
  connect by level <= (dtr.date_end - dtr.date_start) * (24 * 4) -- 15-minutes
)
select * from time_slots;

Daje to 57 15-minutowych przedziałów między podaną datą początkową a końcową. CTE dla date_time_range nie jest absolutnie konieczne, możesz umieścić swoje daty bezpośrednio w time_slots warunki, ale trzeba by je powtórzyć, a to wprowadza możliwy punkt awarii (i oznacza wielokrotne wiązanie tej samej wartości, z JDBC lub gdziekolwiek).

Te miejsca można następnie połączyć krzyżowo z listą sal lekcyjnych, które, jak zakładam, znajdują się już w innej tabeli, co daje 171 (3x57) kombinacji; a te można porównać z istniejącymi rezerwacjami – po ich wyeliminowaniu pozostają 153 15-minutowe przedziały, w których nie ma rezerwacji.

with date_time_range as (...),
time_slots as (...),
free_slots as (
  select c.classroom, ts.slot_num, ts.slot_start, ts.slot_end,
    lag(ts.slot_end) over (partition by c.classroom order by ts.slot_num)
      as lag_end,
    lead(ts.slot_start) over (partition by c.classroom order by ts.slot_num)
      as lead_start
  from time_slots ts
  cross join classrooms c
  left join occupied_classrooms oc on oc.classroom = c.classroom
    and not (oc.occupied_end <= ts.slot_start 
      or oc.occupied_start >= ts.slot_end)
  where oc.classroom is null
)
select * from free_slots;

Ale potem musisz je zwinąć w ciągłe zakresy. Można to zrobić na różne sposoby; tutaj zerkam na poprzedni i następny wiersz, aby zdecydować, czy konkretna wartość jest krawędzią zakresu:

with date_time_range as (...),
time_slots as (...),
free_slots as (...),
free_slots_extended as (
  select fs.classroom, fs.slot_num,
    case when fs.lag_end is null or fs.lag_end != fs.slot_start
      then fs.slot_start end as slot_start,
    case when fs.lead_start is null or fs.lead_start != fs.slot_end
      then fs.slot_end end as slot_end
  from free_slots fs
)
select * from free_slots_extended
where (fse.slot_start is not null or fse.slot_end is not null);

Teraz mamy już 12 rzędów. (Zewnętrzny where klauzula eliminuje wszystkie 141 z 153 slotów z poprzedniego kroku, które są w średnim zakresie, ponieważ zależy nam tylko na krawędziach):

CLASSROOM   SLOT_NUM SLOT_START       SLOT_END       
--------- ---------- ---------------- ----------------
A                  1 2013-10-10 07:00                  
A                 12                  2013-10-10 10:00 
A                 19 2013-10-10 11:30                  
A                 57                  2013-10-10 21:15 
B                  1 2013-10-10 07:00                  
B                  9                  2013-10-10 09:15 
B                 16 2013-10-10 10:45                  
B                 30                  2013-10-10 14:30 
B                 37 2013-10-10 16:00                  
B                 57                  2013-10-10 21:15 
C                  1 2013-10-10 07:00                  
C                 57                  2013-10-10 21:15 

Tak więc reprezentują one krawędzie, ale w oddzielnych rzędach, a ostatni krok łączy je:

...
select distinct fse.classroom,
  nvl(fse.slot_start, lag(fse.slot_start)
    over (partition by fse.classroom order by fse.slot_num)) as slot_start,
  nvl(fse.slot_end, lead(fse.slot_end)
    over (partition by fse.classroom order by fse.slot_num)) as slot_end
from free_slots_extended fse
where (fse.slot_start is not null or fse.slot_end is not null)

Lub łącząc to wszystko razem:

with date_time_range as (
  select to_date('10/10/2013 07:00', 'DD/MM/YYYY HH24:MI') as date_start,
    to_date('10/10/2013 21:15', 'DD/MM/YYYY HH24:MI') as date_end
  from dual
),
time_slots as (
  select level as slot_num,
    dtr.date_start + (level - 1) * interval '15' minute as slot_start,
    dtr.date_start + level * interval '15' minute as slot_end
  from date_time_range dtr
  connect by level <= (dtr.date_end - dtr.date_start) * (24 * 4) -- 15-minutes
),
free_slots as (
  select c.classroom, ts.slot_num, ts.slot_start, ts.slot_end,
    lag(ts.slot_end) over (partition by c.classroom order by ts.slot_num)
      as lag_end,
    lead(ts.slot_start) over (partition by c.classroom order by ts.slot_num)
      as lead_start
  from time_slots ts
  cross join classrooms c
  left join occupied_classrooms oc on oc.classroom = c.classroom
    and not (oc.occupied_end <= ts.slot_start
      or oc.occupied_start >= ts.slot_end)
  where oc.classroom is null
),
free_slots_extended as (
  select fs.classroom, fs.slot_num,
    case when fs.lag_end is null or fs.lag_end != fs.slot_start
      then fs.slot_start end as slot_start,
    case when fs.lead_start is null or fs.lead_start != fs.slot_end
      then fs.slot_end end as slot_end
  from free_slots fs
)
select distinct fse.classroom,
  nvl(fse.slot_start, lag(fse.slot_start)
    over (partition by fse.classroom order by fse.slot_num)) as slot_start,
  nvl(fse.slot_end, lead(fse.slot_end)
    over (partition by fse.classroom order by fse.slot_num)) as slot_end
from free_slots_extended fse
where (fse.slot_start is not null or fse.slot_end is not null)
order by 1, 2;

Co daje:

CLASSROOM SLOT_START       SLOT_END       
--------- ---------------- ----------------
A         2013-10-10 07:00 2013-10-10 10:00 
A         2013-10-10 11:30 2013-10-10 21:15 
B         2013-10-10 07:00 2013-10-10 09:15 
B         2013-10-10 10:45 2013-10-10 14:30 
B         2013-10-10 16:00 2013-10-10 21:15 
C         2013-10-10 07:00 2013-10-10 21:15 

Skrzypce SQL .



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. WIDTH_BUCKET() Funkcja w Oracle

  2. Czy istnieje różnica wydajności między concat a || w wyroczni

  3. W serwerze SQL, jak mogę wysłać zapytanie do kolumny Oracle Timestamp za pośrednictwem połączenia z serwerem połączonym?

  4. Standardowa alternatywa SQL dla Oracle DECODE

  5. Dynamiczne partycjonowanie tabel w Oracle