如何使用 Oracle SQL 约束检查酒店房间是否已预订
How to check if hotel room is booked using Oracle SQL constraints
在 SQL 方面,我绝对是个初学者,我正在尝试弄清楚如何进行简单的完整性检查。我正在为一个包含四个 table 的酒店注册系统建模:Hotel、Room、Booking 和 Guest。我感兴趣的是预订 table,它具有属性 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,因此,如果能手把手地介绍一下,我们将不胜感激:)
编辑:我想我的问题可以通过
的约束来解决
案例 1(重叠):"If the dateFrom or dateTo attributes of the record I am trying to insert falls between the dateFrom and dateTo attributes of a given record previously in the table, reject this insertion since there is some overlap between the two bookings."
案例 2(超集):"If I am attempting to insert record X and there is a record already in the table named Y such that its Y.dateFrom > X.dateFrom and Y.dateTo < X.dateTo, then X is a superset of Y and should be rejected."
不过我不确定如何将其翻译成 SQL。
编辑 2:tables
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)
在您的模型中,HOTEL 和 GUEST 表没有问题(它们稍后可能需要更多列,但这不是问题)。对于ROOM,你决定使用复合PK。但是,作为 ID 的单个列就足够了。在 BOOKING 中,引用 HOTEL 的外键是多余的。客人在特定日期预订房间(具有唯一 ID,并且已经绑定到酒店)。
自动生成 ID 并为它们定义不同的 "start" 值可能有助于(您的学习)- 在稍后阶段查询表时,您将立即识别 ID,例如 HOTEL
可能有 1000+,房间可能有 2000+ 等等(见下面的 DDL 代码)。
按照@Abra 提供的link,您已经看到可以使用触发器等来解决问题。下面的解决方案受到此 answer (also mentioned as "option 4" here) 的启发,并使用将预订分解为天数 ("slots") 的想法,然后可将其用于唯一(或 PK)约束。请阅读评论,因为它们包含更多解释。
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 ;
/
HOTELS、ROOMS、GUESTS 中的数据
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
...
查询更多测试数据(插入:参见 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
由于您的问题更多是关于建模和约束,查询可能需要更多工作。
在 SQL 方面,我绝对是个初学者,我正在尝试弄清楚如何进行简单的完整性检查。我正在为一个包含四个 table 的酒店注册系统建模:Hotel、Room、Booking 和 Guest。我感兴趣的是预订 table,它具有属性 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,因此,如果能手把手地介绍一下,我们将不胜感激:)
编辑:我想我的问题可以通过
的约束来解决案例 1(重叠):"If the dateFrom or dateTo attributes of the record I am trying to insert falls between the dateFrom and dateTo attributes of a given record previously in the table, reject this insertion since there is some overlap between the two bookings."
案例 2(超集):"If I am attempting to insert record X and there is a record already in the table named Y such that its Y.dateFrom > X.dateFrom and Y.dateTo < X.dateTo, then X is a superset of Y and should be rejected."
不过我不确定如何将其翻译成 SQL。
编辑 2:tables
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)
在您的模型中,HOTEL 和 GUEST 表没有问题(它们稍后可能需要更多列,但这不是问题)。对于ROOM,你决定使用复合PK。但是,作为 ID 的单个列就足够了。在 BOOKING 中,引用 HOTEL 的外键是多余的。客人在特定日期预订房间(具有唯一 ID,并且已经绑定到酒店)。
自动生成 ID 并为它们定义不同的 "start" 值可能有助于(您的学习)- 在稍后阶段查询表时,您将立即识别 ID,例如 HOTEL 可能有 1000+,房间可能有 2000+ 等等(见下面的 DDL 代码)。
按照@Abra 提供的link,您已经看到可以使用触发器等来解决问题。下面的解决方案受到此 answer (also mentioned as "option 4" here) 的启发,并使用将预订分解为天数 ("slots") 的想法,然后可将其用于唯一(或 PK)约束。请阅读评论,因为它们包含更多解释。
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 ;
/
HOTELS、ROOMS、GUESTS 中的数据
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
...
查询更多测试数据(插入:参见 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
由于您的问题更多是关于建模和约束,查询可能需要更多工作。