Create the Table
create table meeting_attendees (  
  attendee_id integer  
    not null,  
  start_date date  
    not null,  
  end_date date,  
  primary key (   
    attendee_id, start_date  
  )  
)
                        Table created.
Load the Data
begin   
  insert into meeting_attendees   
    values ( 1, date'2020-06-17' + 9/24, date'2020-06-17' + 10/24 );  
  insert into meeting_attendees   
    values ( 1, date'2020-06-17' + 10/24, date'2020-06-17' + 10.5/24 );  
  insert into meeting_attendees   
    values ( 1, date'2020-06-17' + 11/24, date'2020-06-17' + 13/24 );  
  insert into meeting_attendees   
    values ( 1, date'2020-06-17' + 16/24, date'2020-06-17' + 17/24 );  
      
  insert into meeting_attendees   
    values ( 2, date'2020-06-17' + 9/24, date'2020-06-17' + 12/24 );  
  insert into meeting_attendees   
    values ( 2, date'2020-06-17' + 12.5/24, date'2020-06-17' + 13/24 );  
  insert into meeting_attendees   
    values ( 2, date'2020-06-17' + 13/24, date'2020-06-17' + 14/24 );  
  insert into meeting_attendees   
    values ( 2, date'2020-06-17' + 15/24, date'2020-06-17' + 17/24 );  
end; 
                        Statement processed.
alter session set nls_date_format = '  DD Mon YY HH24:MI  '
                        Statement processed.
select * from meeting_attendees  
order by start_date, end_date
                        | ATTENDEE_ID | START_DATE | END_DATE | 1 | 17 Jun 20 09:00 | 17 Jun 20 10:00 | 2 | 17 Jun 20 09:00 | 17 Jun 20 12:00 | 1 | 17 Jun 20 10:00 | 17 Jun 20 10:30 | 1 | 17 Jun 20 11:00 | 17 Jun 20 13:00 | 2 | 17 Jun 20 12:30 | 17 Jun 20 13:00 | 2 | 17 Jun 20 13:00 | 17 Jun 20 14:00 | 2 | 17 Jun 20 15:00 | 17 Jun 20 17:00 | 1 | 17 Jun 20 16:00 | 17 Jun 20 17:00 | 
|---|
alter session set nls_date_format = '  HH24:MI  '
                        Statement processed.
Find the Gaps
select *   
from   meeting_attendees match_recognize (  
    order by start_date, end_date  
    measures  
      max ( end_date ) start_gap,   
      next ( start_date ) end_gap,  
      classifier() as cls  
    all rows per match   
    pattern ( ( gap | {-no_gap-} )+ )  
    define gap as max ( end_date ) < next ( start_date )  
  )
                        | START_DATE | END_DATE | START_GAP | END_GAP | CLS | ATTENDEE_ID | 13:00 | 14:00 | 14:00 | 15:00 | GAP | 2 | 
|---|
Show the All the Rows
select cls, end_date, start_gap, end_gap   
from   meeting_attendees match_recognize (  
    order by start_date, end_date  
    measures  
      max ( end_date ) start_gap,   
      next ( start_date ) end_gap,  
      classifier() as cls  
    all rows per match   
    pattern ( ( gap | no_gap )+ )  
    define gap as max ( end_date ) < next ( start_date )  
  )
                        | CLS | END_DATE | START_GAP | END_GAP | NO_GAP | 10:00 | 10:00 | 09:00 | NO_GAP | 12:00 | 12:00 | 10:00 | NO_GAP | 10:30 | 12:00 | 11:00 | NO_GAP | 13:00 | 13:00 | 12:30 | NO_GAP | 13:00 | 13:00 | 13:00 | GAP | 14:00 | 14:00 | 15:00 | NO_GAP | 17:00 | 17:00 | 16:00 | NO_GAP | 17:00 | 17:00 | - | 
|---|
Make it Reusable with SQL Macros
create or replace function find_gaps (  
  tab            dbms_tf.table_t,   
  date_cols      dbms_tf.columns_t  
)  
  return varchar2   
  sql_macro   
as  
begin  
  return 'find_gaps.tab match_recognize (   
  order by ' || find_gaps.date_cols ( 1 ) || ', ' || find_gaps.date_cols ( 2 ) || '    
  measures     
    max ( ' || find_gaps.date_cols ( 2 ) || ' ) start_gap,   
    next ( ' || find_gaps.date_cols ( 1 ) || ' ) end_gap  
  all rows per match  
  pattern ( ( gap | {-no_gap-} )+ )    
  define     
    gap as max ( ' || find_gaps.date_cols ( 2 ) || ' ) < (   
      next ( ' || find_gaps.date_cols ( 1 ) || ' )  
    )    
)';  
end find_gaps; 
                        Function created.
Calling a SQL Macro
select start_gap, end_gap  
from   find_gaps (   
  meeting_attendees,   
  columns ( start_date, end_date )   
)
                        | START_GAP | END_GAP | 14:00 | 15:00 | 
|---|
View the Final SQL Query
declare  
  l_clob clob;  
begin  
  dbms_utility.expand_sql_text (  
    input_sql_text  => q'!select *   
from   find_gaps (   
  meeting_attendees,   
  columns ( start_date, end_date )   
)!',  
    output_sql_text => l_clob  );  
  dbms_output.put_line(l_clob);  
end; 
                        Statement processed.
SELECT "A1"."START_DATE" "START_DATE","A1"."END_DATE" "END_DATE","A1"."START_GAP" "START_GAP","A1"."END_GAP" "END_GAP","A1"."ATTENDEE_ID" "ATTENDEE_ID" FROM (SELECT "A3"."START_DATE" "START_DATE","A3"."END_DATE" "END_DATE","A3"."START_GAP" "START_GAP","A3"."END_GAP" "END_GAP","A3"."ATTENDEE_ID" "ATTENDEE_ID" FROM (SELECT * FROM (SELECT "A2"."ATTENDEE_ID" "ATTENDEE_ID","A2"."START_DATE" "START_DATE","A2"."END_DATE" "END_DATE" FROM "SQL_XYFAXMHMJFABNKUWUHJFVDJLY"."MEETING_ATTENDEES" "A2") "A4" MATCH_RECOGNIZE ( ORDER BY "START_DATE","END_DATE" MEASURES MAX("END_DATE") AS "START_GAP",NEXT("START_DATE") AS "END_GAP" ALL ROWS PER MATCH SHOW EMPTY MATCHES AFTER MATCH SKIP PAST LAST ROW PATTERN ( (("GAP" | {- "NO_GAP" -} ))+) DEFINE "GAP" AS MAX("END_DATE")