問題を別の方法で見てみましょう。ビルとテナントの営業時間は基本的に変わりません。つまり、値は異なる場合がありますが、建物が開いているかどうかは、テナントが開いているかどうかと本質的に異なるわけではありません。このアイデアを試すために、別のスキーマ「hours」を作成しました。
-- 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 がアサーションをサポートしない限り、宣言的に行うことはできません。データベース内の手続き型コードで実行できます。