在日期时间范围之间提供课堂时间

oec*_*ray 4 sql oracle datetime plsql

我使用的是Oracle 11g,我遇到了这个问题.我无法想出任何解决它的想法.

我有一张带有教室的桌子.我需要找到的是日期时间范围之间的可用时间.例如,我有房间A,B和C,被占用教室的桌子如下所示:

Classroom        start                 end  
   A         10/10/2013 10:00      10/10/2013 11:30  
   B         10/10/2013 09:15      10/10/2013 10:45  
   B         10/10/2013 14:30      10/10/2013 16:00  
Run Code Online (Sandbox Code Playgroud)

我需要得到的是这样的:

with date time range between '10/10/2013 07:00' and '10/10/2013 21:15'

Classroom    avalailable_from        available_to  
   A         10/10/2013 07:00      10/10/2013 10:00  
   A         10/10/2013 11:30      10/10/2013 21:15  
   B         10/10/2013 07:00      10/10/2013 09:15  
   B         10/10/2013 10:45      10/10/2013 14:30  
   B         10/10/2013 16:00      10/10/2013 21:15  
   C         10/10/2013 07:00      10/10/2013 21:15  
Run Code Online (Sandbox Code Playgroud)

有没有办法用sql或pl/sql来实现?

Ale*_*ole 5

至少对Wernfried来说,我正在寻找一个类似于概念的解决方案,但我认为它的发布也不同.开始是相同的想法,首先生成可能的时间段,并假设您正在查看15分钟的窗口:我使用CTE因为我认为它们比嵌套选择更清晰,特别是在这么多级别.

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;
Run Code Online (Sandbox Code Playgroud)

这将为您提供指定的开始日期和结束日期之间的57个15分钟的插槽.CTE for date_time_range并不是绝对必要的,您可以将日期直接放入time_slots条件中,但是您必须重复它们然后引入可能的故障点(并且意味着从JDBC或任何地方多次绑定相同的值) .

那些插槽可以交叉连接到教室列表,我假设已经在另一个表中,这给你171(3x57)组合; 这些可以与现有的预订进行比较 - 一旦被淘汰,你将被留下153个没有预订的15分钟时段.

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;
Run Code Online (Sandbox Code Playgroud)

但是你必须将它们折叠成连续的范围.有各种方法可以做到这一点; 在这里,我正在查看前一行和下一行,以确定特定值是否是范围的边缘:

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);
Run Code Online (Sandbox Code Playgroud)

现在我们已经减少了12行.(外部where条款消除了上一步中153个插槽中的所有141个中间范围,因为我们只关心边缘):

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 
Run Code Online (Sandbox Code Playgroud)

那些代表边缘,但在不同的行上,最后一步结合它们:

...
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)
Run Code Online (Sandbox Code Playgroud)

或者把所有这些放在一起:

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;
Run Code Online (Sandbox Code Playgroud)

这使:

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 
Run Code Online (Sandbox Code Playgroud)

SQL小提琴.