sql >> データベース >  >> RDS >> Oracle

日付と時間の範囲で教室の利用可能な時間を取得する

    少なくとも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;
    

    これにより、指定した開始日と終了日の間に57の15分のスロットが与えられます。 date_time_rangeのCTE 厳密に必要というわけではありません。日付を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;
    

    しかし、それらを連続した範囲に折りたたむ必要があります。それを行うにはさまざまな方法があります。ここでは、前の行と次の行を調べて、特定の値が範囲の端であるかどうかを判断しています。

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

    これで、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 
    

    したがって、これらはエッジを表しますが、別々の行にあり、最後のステップでそれらを組み合わせます。

    ...
    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)
    

    または、それらすべてをまとめます:

    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;
    

    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 
    

    SQLフィドル



    1. コマンドライン引数を使用してPostgreSQL.sqlファイルを実行します

    2. MySql:数字である2つの文字列を比較しますか?

    3. Postgresql-別のテーブルに一致しないテーブルのエントリを取得する方法

    4. Oracle Apex:結果を待機するときにプログレスバーを作成する