让我们以不同的方式看待这个问题。建筑物和租户的营业时间并没有本质上的不同。也就是说,价值可能会有所不同,但建筑物是否开放的想法与租户是否开放并没有本质上的不同。我创建了一个单独的模式“小时”来实现这个想法。
-- Essentially a supertype, but I couldn't think of a good noun to
-- describe it. So I named the table "x". It holds all the attributes
-- common to both buildings and tenants. (That is, not very much.)
--
create table hours.x (
x_id integer primary key,
x_name varchar(35) not null,
x_type char(1) check (x_type in ('b','t')),
unique (x_id, x_type)
);
insert into hours.x values
(1, 'First building', 'b'),
(2, 'Second building', 'b'),
(3, 'First tenant', 't'),
(4, 'Second tenant', 't'),
(5, 'Third tenant', 't');
create table hours.buildings (
bldg_id integer primary key,
x_type char(1) not null default 'b' check (x_type = 'b'),
foreign key (bldg_id, x_type) references hours.x (x_id, x_type),
other_columns char(1) not null default 'x'
);
insert into hours.buildings (bldg_id) values
(1), (2);
create table hours.tenants (
tenant_id integer primary key,
x_type char(1) not null default 't' check (x_type = 't'),
foreign key (tenant_id, x_type) references hours.x (x_id, x_type),
bldg_id integer not null references hours.buildings (bldg_id),
other_columns char(1) not null default 'x'
);
insert into hours.tenants (tenant_id, bldg_id) values
(3, 1), (4, 1), (5, 2);
-- Operating hours records a half open interval [opening_time, closing_time).
-- If a queried time matches the opening time, the building or tenant is open.
-- But if it matches the closing time, it's *not* open. Examples later.
create table hours.op_hours (
x_id integer not null references hours.x (x_id),
opening_time timestamp not null,
closing_time timestamp not null,
check (opening_time < closing_time),
check (
-- Open and close on the same date,
( opening_time::date = closing_time::date ) or
-- or close at midnight following the opening.
(
(opening_time::date + interval '1' day = closing_time::date) and
(cast(closing_time as time) = '00:00')
)
)
);
insert into hours.op_hours values
-- Bldg 1 is normally Mon-Sat, 9:00 to 5:00. Closed on Jan 1, holiday
(1, '2012-01-02 09:00', '2012-01-02 17:00'),
(1, '2012-01-03 09:00', '2012-01-03 17:00'),
(1, '2012-01-04 09:00', '2012-01-04 17:00'),
(1, '2012-01-05 09:00', '2012-01-05 17:00'),
(1, '2012-01-06 09:00', '2012-01-06 17:00'),
(1, '2012-01-07 09:00', '2012-01-07 17:00'),
-- Closed on Jan 8, a Sunday.
(1, '2012-01-09 09:00', '2012-01-09 17:00'),
-- Bldg 2 is normally Mon-Fri, 7:30 to midnight.
(2, '2012-01-02 07:30', '2012-01-03 00:00'),
(2, '2012-01-03 07:30', '2012-01-04 00:00'),
(2, '2012-01-04 07:30', '2012-01-05 00:00'),
(2, '2012-01-05 07:30', '2012-01-06 00:00'),
(2, '2012-01-06 07:30', '2012-01-07 00:00'),
-- Closed on Jan 7 and 8, weekend.
(2, '2012-01-09 07:30', '2012-01-10 00:00'),
-- "First" tenant is open 9:00 to noon and 1:00 to 4:00, Mon-Fri.
(3, '2012-01-02 09:00', '2012-01-02 12:00'),
(3, '2012-01-03 09:00', '2012-01-03 12:00'),
(3, '2012-01-04 09:00', '2012-01-04 12:00'),
(3, '2012-01-05 09:00', '2012-01-05 12:00'),
(3, '2012-01-06 09:00', '2012-01-06 12:00'),
(3, '2012-01-02 13:00', '2012-01-02 16:00'),
(3, '2012-01-03 13:00', '2012-01-03 16:00'),
(3, '2012-01-04 13:00', '2012-01-04 16:00'),
(3, '2012-01-05 13:00', '2012-01-05 16:00'),
(3, '2012-01-06 13:00', '2012-01-06 16:00'),
-- "Second" tenant is open when the building is open.
(4, '2012-01-02 09:00', '2012-01-02 17:00'),
(4, '2012-01-03 09:00', '2012-01-03 17:00'),
(4, '2012-01-04 09:00', '2012-01-04 17:00'),
(4, '2012-01-05 09:00', '2012-01-05 17:00'),
(4, '2012-01-06 09:00', '2012-01-06 17:00'),
(4, '2012-01-07 09:00', '2012-01-07 17:00'),
-- Closed on Jan 8, a Sunday.
(4, '2012-01-09 09:00', '2012-01-09 17:00'),
-- "Third" tenant is open Mon-Thu 7:30 to 9:30, Fri until midnight.
(5, '2012-01-02 07:30', '2012-01-02 21:30'),
(5, '2012-01-03 07:30', '2012-01-03 21:30'),
(5, '2012-01-04 07:30', '2012-01-04 21:30'),
(5, '2012-01-05 07:30', '2012-01-05 21:30'),
(5, '2012-01-06 07:30', '2012-01-07 00:00'),
-- Closed on Jan 7 and 8, weekend.
(5, '2012-01-09 07:30', '2012-01-09 21:30');
应该清楚的是,这种记录每个建筑物和每个租户每天的营业时间的结构将适应季节、假期、事件等的任何定义。可以使用相当简单的存储过程生成默认数据。而且所需的查询非常简单。(能够看到一些东西并看到它是正确的,这是很有价值的。)
-- Is building 1 open at 9:00 am on Jan 3? (Queries for tenants are essentially
-- identical. Returns the id number if it's open, but that could be massaged
-- into an "is_open" derived column with Boolean values.)
select x_id
from hours.op_hours
where x_id = 1
and opening_time <= '2012-01-03 09:00'
and '2012-01-03 09:00' < closing_time;
-- How about on Jan 1? (Returns an empty set if closed. See above.)
select x_id
from hours.op_hours
where x_id = 1
and opening_time <= '2012-01-01 09:00'
and '2012-01-01 09:00' < closing_time;
-- Which tenants, regardless of building, are open on Jan 4 at 9:00 am?
select op_hours.x_id
from hours.op_hours
inner join hours.tenants on hours.tenants.tenant_id = hours.op_hours.x_id
where opening_time <= '2012-01-04 09:00'
and '2012-01-04 09:00' < closing_time;
请注意,这些查询中的每一个都在半开区间上运行。您不能使用 BETWEEN 运算符,因为它在闭合区间上运行。
这种结构不能满足哪些要求?
- 租户营业时间必须是建筑物营业时间的一个子集。除非您的 dbms 支持断言,否则无法以声明方式完成。可以用数据库中的程序代码来完成。