How to Prevent Overlapping Bookings in Oracle APEX

Check new bookings against saved ones with a validation, allow back-to-back slots, and lock the room row so two users saving at once cannot double-book.

Any app that books a shared resource has to stop double bookings. A meeting room, a vehicle or a consultant can be used by only one booking at a time. If two bookings for the same room overlap by even a few minutes, someone arrives to find the room taken.

A check constraint cannot compare one row with the other rows of the table, so the rule needs a validation. This guide builds that validation in an Oracle APEX form.

It also closes the gap most overlap checks have: two users saving at the same moment. A row lock on the room makes the second save wait and then fail cleanly.

This guide uses meeting rooms as the example; the same steps work for any table with a start and an end time. How to Add a From/To Date Range with Validation in Oracle APEX covers search filters with a From and a To date. Here, a new record is checked against all saved records of the same room.

Step 1: Create the Tables

The example needs two tables. MEETING_ROOMS lists the rooms. MEETING_BOOKINGS holds the bookings, each with a room, a subject and a START_TIME and END_TIME. The DATE columns store the time of day as well.

Create the tables, the index and the sample data:

Example:

set lines 200 pages 100 feedback on
create table meeting_rooms (
  room_id      number generated by default on null as identity constraint meeting_rooms_pk primary key,
  room_name    varchar2(50) not null,
  capacity     number(3)    not null,
  constraint meeting_rooms_name_uk unique (room_name),
  constraint meeting_rooms_capacity_ck check (capacity > 0)
);

create table meeting_bookings (
  booking_id   number generated by default on null as identity constraint meeting_bookings_pk primary key,
  room_id      number        not null,
  subject      varchar2(100) not null,
  start_time   date          not null,
  end_time     date          not null,
  booked_by    varchar2(255) default on null
                 coalesce(sys_context('APEX$SESSION', 'APP_USER'), user) not null,
  constraint meeting_bookings_room_fk foreign key (room_id) references meeting_rooms (room_id),
  constraint meeting_bookings_times_ck check (end_time > start_time)
);

create index meeting_bookings_room_ix on meeting_bookings (room_id, start_time, end_time);

insert into meeting_rooms (room_name, capacity) values ('Atlas', 6);
insert into meeting_rooms (room_name, capacity) values ('Boreal', 10);
insert into meeting_rooms (room_name, capacity) values ('Cedar', 4);

insert into meeting_bookings (room_id, subject, start_time, end_time, booked_by)
values (1, 'Sprint Planning', to_date('2026-11-16 09:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-16 10:30', 'yyyy-mm-dd hh24:mi'), 'DEMO_DEV');
insert into meeting_bookings (room_id, subject, start_time, end_time, booked_by)
values (1, 'Design Review', to_date('2026-11-16 13:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-16 14:00', 'yyyy-mm-dd hh24:mi'), 'SALES_MANAGER');
insert into meeting_bookings (room_id, subject, start_time, end_time, booked_by)
values (2, 'Quarterly Review', to_date('2026-11-16 10:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-16 12:00', 'yyyy-mm-dd hh24:mi'), 'SALES_MANAGER');
insert into meeting_bookings (room_id, subject, start_time, end_time, booked_by)
values (3, 'Interview', to_date('2026-11-17 11:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-17 11:45', 'yyyy-mm-dd hh24:mi'), 'DEMO_DEV');

commit;

column room_name format a8
column subject format a18
column booked_by format a14
select b.booking_id, r.room_name, b.subject,
       to_char(b.start_time, 'DD-MON-YYYY HH24:MI') as start_time,
       to_char(b.end_time, 'HH24:MI') as end_time, b.booked_by
  from meeting_bookings b
  join meeting_rooms r on r.room_id = b.room_id
 order by b.booking_id;

Output:

Table MEETING_ROOMS created.

Table MEETING_BOOKINGS created.

Index MEETING_BOOKINGS_ROOM_IX created.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

Commit complete.

   BOOKING_ID ROOM_NAME    SUBJECT             START_TIME           END_TIME    BOOKED_BY
_____________ ____________ ___________________ ____________________ ___________ ________________
            1 Atlas        Sprint Planning     16-NOV-2026 09:00    10:30       DEMO_DEV
            2 Atlas        Design Review       16-NOV-2026 13:00    14:00       SALES_MANAGER
            3 Boreal       Quarterly Review    16-NOV-2026 10:00    12:00       SALES_MANAGER
            4 Cedar        Interview           17-NOV-2026 11:00    11:45       DEMO_DEV

4 rows selected.
  • MEETING_BOOKINGS_ROOM_FK allows only rooms that exist in MEETING_ROOMS.
  • MEETING_BOOKINGS_TIMES_CK makes sure a booking ends after it starts. This rule needs only the values of one row, so a check constraint can enforce it.
  • MEETING_BOOKINGS_ROOM_IX on ROOM_ID, START_TIME and END_TIME indexes the foreign key and supports the overlap query of the next step.
  • BOOKED_BY is filled by DEFAULT ON NULL with the APEX user, or the database user outside APEX, so no trigger is needed.

Step 2: Write the Overlap Rule

Two time ranges overlap when the existing booking starts before the new one ends, and ends after the new one starts. In SQL, that is two comparisons: START_TIME is less than the new end, and END_TIME is greater than the new start.

The comparisons are strict, so a booking may start at the exact minute the previous one ends. The query below tests four candidate slots against the sample data:

Example:

set lines 200 pages 100 feedback on
column slot format a24
with candidate (slot, room_id, start_time, end_time) as (
  select 'Atlas 10:00-11:00', 1, to_date('2026-11-16 10:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-16 11:00', 'yyyy-mm-dd hh24:mi') from dual union all
  select 'Atlas 10:30-13:00', 1, to_date('2026-11-16 10:30', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-16 13:00', 'yyyy-mm-dd hh24:mi') from dual union all
  select 'Atlas 08:00-15:00', 1, to_date('2026-11-16 08:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-16 15:00', 'yyyy-mm-dd hh24:mi') from dual union all
  select 'Cedar 10:00-11:00', 3, to_date('2026-11-16 10:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-16 11:00', 'yyyy-mm-dd hh24:mi') from dual
)
select c.slot,
       (select count(*)
          from meeting_bookings b
         where b.room_id = c.room_id
           and b.start_time < c.end_time
           and b.end_time > c.start_time) as clashes
  from candidate c;

Output:

SLOT                    CLASHES
____________________ __________
Atlas 10:00-11:00             1
Atlas 10:30-13:00             0
Atlas 08:00-15:00             2
Cedar 10:00-11:00             0

4 rows selected.
  • Atlas 10:00-11:00 clashes with Sprint Planning (09:00-10:30).
  • Atlas 10:30-13:00 has no clash. It starts when Sprint Planning ends and ends when Design Review starts.
  • Atlas 08:00-15:00 covers both Atlas bookings, so it finds two clashes.
  • Cedar 10:00-11:00 is free, because the room is different.

Step 3: See Why the Check Needs a Lock

A check-then-insert has a weak spot. If two sessions run it at the same moment, neither can see the other's uncommitted row. Both find no clash, and both insert. The block below checks room Boreal for 17 November, 09:00 to 10:00, inserts the booking, and waits 8 seconds before the commit:

Example:

set serveroutput on feedback off
declare
  l_room    number;
  l_clashes number;
begin

  select count(*) into l_clashes
    from meeting_bookings
   where room_id = 2
     and start_time < to_date('2026-11-17 10:00', 'yyyy-mm-dd hh24:mi')
     and end_time > to_date('2026-11-17 09:00', 'yyyy-mm-dd hh24:mi');
  dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' clashes found: ' || l_clashes);
  if l_clashes = 0 then
    insert into meeting_bookings (room_id, subject, start_time, end_time)
    values (2, 'Budget Call', to_date('2026-11-17 09:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-17 10:00', 'yyyy-mm-dd hh24:mi'));
    dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' booking inserted');
    dbms_session.sleep(8);
  end if;
  commit;
  dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' committed');
end;
/

Session B ran the same block 5 seconds later, with the subject Vendor Demo and without the wait. The times come from the database clock.

Output (session A):

17:08:39 clashes found: 0
17:08:39 booking inserted
17:08:47 committed

Output (session B):

17:08:44 clashes found: 0
17:08:44 booking inserted
17:08:44 committed

Both sessions found 0 clashes. The room now has two bookings for the same hour:

Example:

set lines 200 pages 100 feedback on
column subject format a12
select booking_id, subject, to_char(start_time, 'DD-MON-YYYY HH24:MI') as start_time,
       to_char(end_time, 'HH24:MI') as end_time
  from meeting_bookings
 where room_id = 2
   and start_time >= date '2026-11-17'
 order by booking_id;

Output:

   BOOKING_ID SUBJECT        START_TIME           END_TIME
_____________ ______________ ____________________ ___________
           41 Budget Call    17-NOV-2026 09:00    10:00
           42 Vendor Demo    17-NOV-2026 09:00    10:00

2 rows selected.

Delete the two test rows:

Example:

set feedback on
delete from meeting_bookings where room_id = 2 and start_time >= date '2026-11-17';
commit;

Output:

1 row deleted.

Commit complete.

The fix is to lock the room row with SELECT ... FOR UPDATE before the check. Only one session can hold that lock, so the second session waits until the first one commits. Its check then runs after the wait and sees the committed booking:

Example:

set serveroutput on feedback off
declare
  l_room    number;
  l_clashes number;
begin
  dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' started');
  select room_id into l_room from meeting_rooms where room_id = 2 for update;
  dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' room locked');
  select count(*) into l_clashes
    from meeting_bookings
   where room_id = 2
     and start_time < to_date('2026-11-17 10:00', 'yyyy-mm-dd hh24:mi')
     and end_time > to_date('2026-11-17 09:00', 'yyyy-mm-dd hh24:mi');
  dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' clashes found: ' || l_clashes);
  if l_clashes = 0 then
    insert into meeting_bookings (room_id, subject, start_time, end_time)
    values (2, 'Budget Call', to_date('2026-11-17 09:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-17 10:00', 'yyyy-mm-dd hh24:mi'));
    dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' booking inserted');
    dbms_session.sleep(8);
  end if;
  commit;
  dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' committed');
end;
/

Output (session A):

17:09:31 started
17:09:31 room locked
17:09:31 clashes found: 0
17:09:31 booking inserted
17:09:39 committed

Session B again ran the same block 5 seconds later, with the subject Vendor Demo and without the wait.

Output (session B):

17:09:36 started
17:09:39 room locked
17:09:39 clashes found: 1
17:09:39 committed

Session B started at 17:09:36 but got the lock only at 17:09:39, the moment session A committed. It then found 1 clash and inserted nothing:

Example:

set lines 200 pages 100 feedback on
column subject format a12
select booking_id, subject, to_char(start_time, 'DD-MON-YYYY HH24:MI') as start_time,
       to_char(end_time, 'HH24:MI') as end_time
  from meeting_bookings
 where room_id = 2
   and start_time >= date '2026-11-17'
 order by booking_id;

Output:

   BOOKING_ID SUBJECT        START_TIME           END_TIME
_____________ ______________ ____________________ ___________
           44 Budget Call    17-NOV-2026 09:00    10:00

1 row selected.

The lock covers one row of MEETING_ROOMS, so only saves for the same room wait for each other.

Step 4: Create the Report and Form Pages

In App Builder, open the application and click Create Page. On the Component tab, click Interactive Report:

Create a Page dialog with the Interactive Report page type marked with a red box
The Interactive Report page type.

Enter Page Number 600 and Name Meeting Bookings. Turn on Include Form Page, enter Form Page Number 601 and Form Page Name Meeting Booking, and keep Form Page Mode Modal Dialog:

Create Interactive Report wizard with page 600 Meeting Bookings and form page 601 Meeting Booking marked with red boxes
One wizard creates the report page and the form page.

Under Data Source, keep Local Database and Source Type Table, and enter Table / View Name MEETING_BOOKINGS:

Data Source section with Table / View Name MEETING_BOOKINGS marked with a red box
The pages are based on the MEETING_BOOKINGS table.

Keep Use Breadcrumb and Use Navigation on, so the report gets a menu entry, and click Next:

Navigation section of the Create Interactive Report wizard with breadcrumb and navigation menu settings
The Navigation settings of the wizard.

On the Primary Key screen, select BOOKING_ID (Number) as Primary Key Column 1 and click Create Page:

Primary Key screen of the wizard with BOOKING_ID (Number) selected and marked with a red box
BOOKING_ID identifies each row.

APEX creates page 600 with the report and page 601 with the modal form. Because ROOM_ID has a foreign key, the wizard also created a shared list of values named MEETING_ROOMS.ROOM_NAME. It shows the room name in the report and in the form instead of the ID.

Step 5: Show the Time in the Date Pickers

The wizard creates a Date Picker for START_TIME and END_TIME, but without a time part. Open page 601 in Page Designer and select P601_START_TIME. Set these properties:

  • Settings > Show Time: On
  • Appearance > Format Mask: DD-MON-YYYY HH24:MI
Page Designer with P601_START_TIME selected and Show Time and Format Mask DD-MON-YYYY HH24:MI marked with red boxes
The start time picker shows hours and minutes.

Select P601_END_TIME and set the same two properties:

Page Designer with P601_END_TIME selected and Show Time and Format Mask DD-MON-YYYY HH24:MI marked with red boxes
The end time picker uses the same format.

The validations of Step 8 convert the item values with this exact format mask, so keep the two in sync.

Step 6: Adjust the Room and Booked By Items

Select P601_ROOM_ID and change Label > Label to Room. The wizard already made it a Select List with List of Values Type Shared Component and the list MEETING_ROOMS.ROOM_NAME:

P601_ROOM_ID with Label Room and the List of Values Type Shared Component and MEETING_ROOMS.ROOM_NAME marked with red boxes
The room is picked by name.

The wizard made P601_BOOKED_BY a required text field, because the column is NOT NULL. The column fills itself, so users should only see it. Select P601_BOOKED_BY and set:

  • Identification > Type: Display Only
  • Appearance > Template: Optional - Floating
P601_BOOKED_BY with Type Display Only and Template Optional - Floating marked with red boxes
Booked By becomes a read-only field.

Scroll down to the Source group and turn on Query Only. The form then never writes the column, and DEFAULT ON NULL stores the APEX user on insert:

Source group of P601_BOOKED_BY with Query Only turned on and marked with a red box
Query Only leaves BOOKED_BY to the column default.

Step 7: Format the Report Columns

Open page 600. In the Rendering tree, expand Meeting Bookings > Columns and select START_TIME. Enter Appearance > Format Mask DD-MON-YYYY HH24:MI:

Page 600 with the START_TIME report column selected and Format Mask DD-MON-YYYY HH24:MI marked with a red box
The report shows the booking time, not just the date.

Select END_TIME and enter the same format mask. Click Save.

Step 8: Add the Validations

The first validation gives a friendly message for a reversed time range, before the check constraint would raise a raw error. Back on page 601, right-click P601_END_TIME and choose Create Validation. Set:

  • Identification > Name: End After Start
  • Validation > Type: Expression
  • Validation > Language: PL/SQL
  • Error > Error Message: The end time must be after the start time.
  • Error > Associated Item: P601_END_TIME

Enter this PL/SQL Expression:

Example:

to_date(:P601_END_TIME, 'DD-MON-YYYY HH24:MI') > to_date(:P601_START_TIME, 'DD-MON-YYYY HH24:MI')
Validation End After Start with Name, Type Expression, Language PL/SQL, the expression, Error Message and Associated Item P601_END_TIME marked with red boxes
The end time must come after the start time.

Item values reach PL/SQL as text, so the expression converts both with TO_DATE and the format mask of Step 5 before it compares them.

The second validation checks for clashes. Right-click P601_START_TIME and choose Create Validation:

Page Designer context menu of P601_START_TIME with Create Validation marked with a red box
Create a validation for the start time item.

Set Name Room Is Free and Type Function Body (returning Error Text), keep Language PL/SQL, and enter this function body:

Example:

declare
  l_room_id meeting_rooms.room_id%type;
begin
  select room_id
    into l_room_id
    from meeting_rooms
   where room_id = :P601_ROOM_ID
     for update;

  for c in (select subject, start_time, end_time
              from meeting_bookings
             where room_id = :P601_ROOM_ID
               and (:P601_BOOKING_ID is null or booking_id <> :P601_BOOKING_ID)
               and start_time < to_date(:P601_END_TIME, 'DD-MON-YYYY HH24:MI')
               and end_time > to_date(:P601_START_TIME, 'DD-MON-YYYY HH24:MI')
             order by start_time
             fetch first 1 row only)
  loop
    return 'This room is already booked from '
        || to_char(c.start_time, 'DD-MON-YYYY HH24:MI') || ' to '
        || to_char(c.end_time, 'HH24:MI') || ' (' || c.subject || ').';
  end loop;

  return null;
end;

Check that Error > Associated Item is P601_START_TIME. This validation type has no Error Message property, because the function returns the message:

Validation Room Is Free with Name, Type Function Body returning Error Text, Language PL/SQL, the function body and Associated Item P601_START_TIME marked with red boxes
The validation locks the room and looks for a clash.

How the function works:

  • SELECT ... FOR UPDATE locks the chosen room row, as in Step 3. APEX commits at the end of the page request, after the form process, so the booking is saved while the room is still locked.
  • The loop reads the first booking of the same room that overlaps the entered times. The condition on P601_BOOKING_ID skips the booking itself when an existing record is edited.
  • If a clash is found, the function returns a message with its times and subject. If nothing is found, it returns NULL and the validation passes.

Click Save. The Delete button created by the wizard has Execute Validations turned off, so deleting a booking does not run these checks.

Step 9: Test the Form

Run page 600. The report lists the sample bookings with their times:

Meeting Bookings interactive report with five bookings for rooms Atlas, Boreal and Cedar
The Meeting Bookings report.

Click Create. Choose Room Atlas, enter Subject Client Call, Start Time 16-NOV-2026 10:00 and End Time 16-NOV-2026 11:00, and click Create. The form stays open with the clash message:

Meeting Booking dialog with the error This room is already booked from 16-NOV-2026 09:00 to 10:30 (Sprint Planning) below Start Time, marked with a red box
An overlapping booking is rejected.

The message names the clashing booking, so the user knows which slot to avoid. Change the Start Time to 16-NOV-2026 10:30 and the End Time to 16-NOV-2026 13:00. This slot fits exactly between Sprint Planning and Design Review:

Meeting Booking dialog with Start Time 16-NOV-2026 10:30 and End Time 16-NOV-2026 13:00 marked with a red box
A slot that touches the neighbouring bookings.

Click Create. The dialog closes and the new booking is in the report:

Meeting Bookings report with the new Client Call booking for Atlas from 10:30 to 13:00 marked with a red box
Back-to-back bookings are allowed.

Next, test a reversed range. Click Create, choose Room Cedar, enter Subject Team Sync, Start Time 18-NOV-2026 15:00 and End Time 18-NOV-2026 14:00, and click Create:

Meeting Booking dialog with the error The end time must be after the start time below End Time, marked with a red box
A booking that ends before it starts is rejected.

Close the dialog with Cancel. Finally, edit an existing booking. Click the edit icon of Sprint Planning, change the Subject to Sprint Planning (Team A) and click Apply Changes:

Meeting Booking dialog for Sprint Planning with Subject Sprint Planning (Team A) marked with a red box and Booked By DEMO_DEV
Editing a booking does not clash with itself.

The change is saved. The booking still covers 09:00 to 10:30, but the validation skips its own row. Booked By shows DEMO_DEV, filled by the column default when the record was created.

Step 10: Test Two Saves at the Same Time

The last test repeats the race of Step 3 against the APEX form. A SQL session plays the second user. It locks room Cedar, inserts a booking for 18 November, 09:00 to 10:00, and waits 20 seconds before it commits:

Example:

set serveroutput on feedback off
declare
  l_room_id number;
begin
  select room_id into l_room_id from meeting_rooms where room_id = 3 for update;
  insert into meeting_bookings (room_id, subject, start_time, end_time)
  values (3, 'Phone Screen', to_date('2026-11-18 09:00', 'yyyy-mm-dd hh24:mi'), to_date('2026-11-18 10:00', 'yyyy-mm-dd hh24:mi'));
  dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' room locked, booking inserted');
  dbms_session.sleep(20);
  commit;
  dbms_output.put_line(to_char(systimestamp, 'HH24:MI:SS') || ' committed');
end;
/

Output:

17:15:32 room locked, booking inserted
17:15:52 committed

While that session waited, the form submitted a booking for Cedar from 09:30 to 10:30 with the subject Candidate Interview. The request did not fail at once. It waited 16 seconds, until the SQL session committed at 17:15:52, and then returned the clash message:

Meeting Booking dialog with the error This room is already booked from 18-NOV-2026 09:00 to 10:00 (Phone Screen) below Start Time, marked with a red box
The parallel booking is caught after the lock wait.

Without the lock, the validation would have run at once, missed the uncommitted Phone Screen row, and saved a second booking, as both sessions did in Step 3.

A rejected request must not keep the room locked. After the failed submit, a NOWAIT lock request on room Cedar succeeded at once:

Example:

set serveroutput on feedback off
declare
  l_room_id number;
begin
  select room_id into l_room_id from meeting_rooms where room_id = 3 for update nowait;
  dbms_output.put_line('Room 3 is not locked by any session.');
  rollback;
end;
/

Output:

Room 3 is not locked by any session.

The bookings after all tests, without any overlap in a room:

Example:

set lines 200 pages 100 feedback on
column room_name format a8
column subject format a26
column booked_by format a14
select r.room_name, b.subject,
       to_char(b.start_time, 'DD-MON-YYYY HH24:MI') as start_time,
       to_char(b.end_time, 'HH24:MI') as end_time, b.booked_by
  from meeting_bookings b
  join meeting_rooms r on r.room_id = b.room_id
 order by r.room_name, b.start_time;

Output:

ROOM_NAME    SUBJECT                     START_TIME           END_TIME    BOOKED_BY
____________ ___________________________ ____________________ ___________ ________________
Atlas        Sprint Planning (Team A)    16-NOV-2026 09:00    10:30       DEMO_DEV
Atlas        Client Call                 16-NOV-2026 10:30    13:00       DEMO_DEV
Atlas        Design Review               16-NOV-2026 13:00    14:00       SALES_MANAGER
Boreal       Quarterly Review            16-NOV-2026 10:00    12:00       SALES_MANAGER
Boreal       Budget Call                 17-NOV-2026 09:00    10:00       APXSOL
Cedar        Interview                   17-NOV-2026 11:00    11:45       DEMO_DEV
Cedar        Phone Screen                18-NOV-2026 09:00    10:00       APXSOL

7 rows selected.

Things to Know

  • The overlap test uses strict comparisons (< and >), so back-to-back bookings are allowed. Use <= and >= if a gap between bookings is required.
  • Lock a parent row that every booking of the resource shares, such as the room. Locking the clashing booking rows would not help, because a new booking has no row to lock yet.
  • A second user waits only while the first request runs, which is usually a fraction of a second. The 16-second wait in Step 10 came from the SQL session's deliberate pause.
  • The validation guards the APEX form only. Other programs that insert bookings must lock the room and check for clashes the same way, for example through a shared PL/SQL procedure.
  • Keep the format mask of the date pickers and the TO_DATE calls in the validations identical, or the conversion fails.
  • The check constraint MEETING_BOOKINGS_TIMES_CK still protects the table from reversed ranges entered outside the form.

Related Guides

Conclusion

Preventing overlapping bookings in Oracle APEX takes one validation of the type Function Body (returning Error Text). It looks for a booking of the same room that starts before the new one ends and ends after it starts, and it names the clash in the error message.

The row lock on the room is what makes the check safe for many users. Parallel saves for the same room run one after the other, so the second one always sees the first. A simple expression validation and a check constraint keep the time range itself valid.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted