如何使用 Oracle SQL 约束检查酒店房间是不是已预订

Posted

技术标签:

【中文标题】如何使用 Oracle SQL 约束检查酒店房间是不是已预订【英文标题】:How to check if hotel room is booked using Oracle SQL constraints如何使用 Oracle SQL 约束检查酒店房间是否已预订 【发布时间】:2020-04-08 02:47:31 【问题描述】:

当谈到 SQL 时,我是一个绝对的初学者,我正在尝试弄清楚如何进行简单的完整性检查。我正在建模一个包含四个表的酒店注册系统:Hotel、Room、Booking 和 Guest。我感兴趣的是预订表,它有属性hotelNo、guestNo、dateFrom、dateTo、roomNo,其中前三个是复合主键。那么问题来了,在现行制度下,两个人可能同时预定了同一个房间,这在现实生活中显然是个问题。我想一个解决方案可能开始看起来像

CREATE TABLE Booking(
-- All the attribute definitions go here...
    CONSTRAINT OneGuestAtATime
        CHECK (NOT EXISTS(SELECT(dateFrom FROM Booking ...))) -- I become unsure of what to do around here
);

请记住,虽然我是一名计算机工程专业的学生,​​但我以前从未使用过 SQL,因此我们将不胜感激:)

编辑:我认为我的问题将通过对效果的约束来解决

CASE 1(重叠):“如果我尝试插入的记录的 dateFrom 或 dateTo 属性介于表中先前给定记录的 dateFrom 和 dateTo 属性之间,则拒绝此插入,因为两者之间存在一些重叠两次预订。”

CASE 2(超集):“如果我试图插入记录 X,并且表中已经有一条名为 Y 的记录,使得它的 Y.dateFrom > X.dateFrom 和 Y.dateTo

不过,我不确定如何将其转换为 SQL。

编辑 2:表格

CREATE TABLE Hotel (
    hotelNo NUMBER NOT NULL,
    hotelName VARCHAR2(1024) NOT NULL,
    city VARCHAR2(1024) NOT NULL,
    --
    PRIMARY KEY (hotelNo)
);

CREATE TABLE Room (
    roomNo NUMBER(4,0) NOT NULL,
    hotelNo NUMBER(5,0) NOT NULL,
    type VARCHAR2(1024),
    price NUMBER(6,2) NOT NULL,
    --
    PRIMARY KEY (roomNo, hotelNo),
    FOREIGN KEY (hotelNo) REFERENCES Hotel
);

CREATE TABLE Guest(
    guestNo NUMBER(8,0) NOT NULL,
    guestName VARCHAR(1024) NOT NULL,
    guestAddress VARCHAR(1024) NOT NULL,
    --
    PRIMARY KEY (guestNo)
);

CREATE TABLE Booking(
    hotelNo NUMBER(8,0) NOT NULL,
    guestNo NUMBER(8,0) NOT NULL,
    dateFrom DATE NOT NULL,
    dateTo DATE NOT NULL,
    roomNo NUMBER(4,0) NOT NULL,
    --
    PRIMARY KEY (hotelNo, guestNo, dateFrom),
    FOREIGN KEY (hotelNo) REFERENCES Hotel,
    FOREIGN KEY (guestNo) REFERENCES Guest,
    FOREIGN KEY (hotelNo, roomNo) REFERENCES Room(hotelNo, roomNo),
    --
    CONSTRAINT DateIntegrity
        CHECK (dateFrom < dateTo)

【问题讨论】:

hotelNodateFromroomNo 上的唯一约束怎么样。那么您不能在同一日期多次预订同一个房间。 我目前对这三个有唯一约束(因为它们是我的复合主键),但这不能解决以下问题:客人 A 从 2020 年 1 月 1 日到 2020 年 2 月 1 日预订房间 100 ;客人 B 在 2020 年 1 月 2 日至 2020 年 2 月 2 日期间预订了 100 号房间。到目前为止,我没有违反限制条件,但显然客人 A 和 B 的预订存在重叠。 在您的问题中,您写道:预订表 ... 属性 hotelNo、guestNo、dateFrom ... 是复合主键 但在您的评论中,您声称 hotelNo , dateFrom 和 roomNo 组成主键。也许edit您的问题并发布表的详细信息,包括列名及其数据类型以及主键和外键。 我认为说 a,b,c 是复合主键 c 相当于说 a,b 和 c 组成主键 K。我不太明白其中的区别,因此如果造成混淆,我深表歉意。 这有帮助吗? Non-Overlapping Dates Constraint 【参考方案1】:

在您的模型中,HOTEL 和 GUEST 表是可以的(稍后它们可能需要更多列,但这不是问题)。对于 ROOM,您已决定使用复合 PK。但是,单列作为 ID 就足够了。在 BOOKING 中,引用 HOTEL 的外键是多余的。客人在特定日期预订房间(具有唯一 ID,并且已与酒店绑定)。

自动生成 ID 并为它们定义不同的“开始”值可能有助于(您的学习) - 在稍后阶段查询表格时,您将立即识别 ID,例如酒店 可以有 1000+,房间可以有 2000+ 等等(见下面的 DDL 代码)。

点击@Abra 提供的链接时,您已经看到触发器等可用于解决问题。下面的解决方案受到answer(也称为“选项4”here)的启发,并使用将预订分解为天(“插槽”)的想法,然后可以将其用于唯一(或PK)约束。请阅读 cmets,因为它们包含更多解释。

DDL 代码

create table hotels (
  id number generated always as identity start with 1000 primary key
, name_ varchar2( 100 )
) ;

create table rooms (
  id number generated always as identity start with 2000 primary key
, name_ varchar2( 100 )
, hotelid number references hotels( id )
) ;

create table guests (
  id number generated always as identity start with 3000 primary key
, last_name varchar2( 100 )
) ;

-- additional table, populated 500 days "into the future"
-- (no bookings _before_ the sysdate) due to FK constraint in bookings
create table days ( slot primary key )
as
select trunc( sysdate ) + level
from dual
connect by level <= 500 ;  -- Oracle only!

create table bookings (
  roomid number references rooms( id )
, slot date references days( slot ) not null
, guestid number references guests( id ) not null
, constraint bookings_pk primary key( roomid, slot )
) ;

型号

填充酒店、房间、客人

-- For populating HOTELS, ROOMS, and GUESTS, we are just using a little PL/SQL script.
-- You can also use single INSERTs.
begin
-- insert one hotel  
  insert into hotels ( name_ ) values ( 'Tiny Hotel' ) ;
-- insert 8 rooms  
  for r in 1 .. 8
  loop
    insert into rooms( name_, hotelid ) values ( 'room_' || to_char( r ), 1000 ) ;
  end loop ;
-- insert 9 guests
  for g in 1 .. 9
  loop
    insert into guests( last_name ) values ( 'guest_' || to_char( g ) ) ;
  end loop ;
  commit ;
end ;
/

酒店、客房、客人中的数据

SQL> select * from hotels ;
    ID NAME_        
  1000 Tiny Hotel   

SQL> select * from rooms ;
    ID NAME_      HOTELID 
  2000 room_1        1000 
  2001 room_2        1000 
  2002 room_3        1000 
  2003 room_4        1000 
  2004 room_5        1000 
  2005 room_6        1000 
  2006 room_7        1000 
  2007 room_8        1000 

SQL> select * from guests ;
    ID LAST_NAME   
  3000 guest_1     
  3001 guest_2     
  3002 guest_3     
  3003 guest_4     
  3004 guest_5     
  3005 guest_6     
  3006 guest_7     
  3007 guest_8     
  3008 guest_9 

测试

-- tests for bookings - unique (roomid, slot)
-- guest 3000 books room 2000, 2 days
-- these 2 inserts must succeed
insert into bookings ( roomid, guestid, slot ) 
  values ( 2000, 3000, date '2020-10-10' ) ;
insert into bookings ( roomid, guestid, slot ) 
  values ( 2000, 3000, date '2020-10-10' + 1 ) ;  -- + 1 here could be + i in a loop ...

-- INSERT must fail - guest 3000 cannot book room 2000 twice (on the same day)
insert into bookings ( roomid, guestid, slot ) 
  values ( 2000, 3000, date '2020-10-10' ) ;
--ERROR at line 1:
--ORA-00001: unique constraint (...BOOKINGS_PK) violated


-- this INSERT must fail
-- guest 3001 cannot have room 2000 on the same day as guest 3000
insert into bookings ( roomid, guestid, slot ) 
  values ( 2000, 3001, date '2020-10-10' + 1 ) ;
--ERROR at line 1:
--ORA-00001: unique constraint (...BOOKINGS_PK) violated


-- guest 3001 can have a different room at the same date, though
-- this insert must succeed
insert into bookings ( roomid, guestid, slot ) 
  values ( 2001, 3001, date '2020-10-10' + 1 ) ;
-- 1 row created.

您可以使用 PL/SQL 和编写循环代码插入更多日期进行测试:请参阅 dbfiddle。

简单查询

-- all current bookings
select
  H.name_
, R.name_
, G.last_name
, B.slot
from hotels H
  join rooms R on H.id = R.hotelid
  join bookings B on R.id = B.roomid
  join guests G on G.id = B.guestid
;

-- result
NAME_        NAME_    LAST_NAME   SLOT        
Tiny Hotel   room_1   guest_1     10-OCT-20   
Tiny Hotel   room_1   guest_1     11-OCT-20   
Tiny Hotel   room_1   guest_4     01-DEC-20   
Tiny Hotel   room_1   guest_4     02-DEC-20   
Tiny Hotel   room_1   guest_4     03-DEC-20  
...

查询更多测试数据(INSERTs:参见 dbfiddle)

-- query that returns
-- all current bookings with nights_booked etc
-- CAUTION: for recurring bookings (same ROOM and GUEST but different slots)
--   this query will give us misleading results
select
  H.name_
, R.name_
, G.last_name
, count( B.slot)     nights_booked
, min( B.slot )      arrival_date
, max( B.slot ) + 1  departure_date
from hotels H
  join rooms R on H.id = R.hotelid
  join bookings B on R.id = B.roomid
  join guests G on G.id = B.guestid
group by H.name_, R.name_, G.last_name
;

-- result
NAME_        NAME_    LAST_NAME     NIGHTS_BOOKED ARRIVAL_DATE   DEPARTURE_DATE   
Tiny Hotel   room_1   guest_1                   2 10-OCT-20      12-OCT-20        
Tiny Hotel   room_1   guest_4                  21 01-DEC-20      22-DEC-20        
Tiny Hotel   room_2   guest_2                   1 11-OCT-20      12-OCT-20 

由于您的问题更多是关于建模和约束,因此查询可能需要更多工作。

【讨论】:

以上是关于如何使用 Oracle SQL 约束检查酒店房间是不是已预订的主要内容,如果未能解决你的问题,请参考以下文章

基于Java SSM酒店信息管理系统(《精品毕设》源码+sql直接运行)主要功能:登录注册酒店信息浏览搜索酒店信息查看房间预定房间后台主要功能:部门/房间/楼层/入住/用户/员工/预定等

JavaWeb SSM酒店信息管理系统(源码+sql直接运行《精品毕设》)主要功能:登录注册酒店信息浏览搜索酒店信息查看房间预定房间后台主要功能:部门/房间/楼层/入住/用户/员工/预定等

JavaWeb SSM酒店信息管理系统(源码+sql直接运行《精品毕设》)主要功能:登录注册酒店信息浏览搜索酒店信息查看房间预定房间后台主要功能:部门/房间/楼层/入住/用户/员工/预定等

如何在 Django 中检查或设置房间可用性

Oracle SQL 检查存在约束

oracle 如何设置检查约束