0

建物とそのテナントの営業時間を表すデータベース スキーマの設計に苦労しています。詳細/要件は次のとおりです。

建物:

  • 原則として各館の通常営業時間は季節(夏季・冬季等)により異なります。
  • 建物ごとに時間帯が異なります。
  • 多数の休日、イベントなどにより、通常の営業時間が変更されます

テナント:

  • テナントは建物内で営業するため、建物の営業時間に制約を受ける必要があります。
  • 通常の営業時間も季節によって異なりますが、建物の季節営業時間とは異なります。
  • テナントごとに営業時間が異なります

理想的には、次のクエリを実行できるようにしたいと考えています。

  • 建物やテナントが現在開いているかどうか
  • 今日の建築時間は
  • 次の X 日間の建設時間は?

これまでの私の (未完成の) 作業はこれら 3 つのテーブルでしたが、まだ実用的なソリューションを作成するのに苦労しています。

[Season]
id
building_id
title
start_date
end_date

[Schedule]
id
season_id
day_of_week (0-6)
open_time
close_time

[Override]
id
schedule_id
date
is_closed
is_holiday

お時間をいただき、ご意見をお寄せいただきありがとうございます。すべての答えは、ソリューションを開発/改良します。個々の日付を格納するという Catcall のアイデアは、モデルと管理/管理インターフェイスを開発するのに最も簡単です。

4

3 に答える 3

1

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

テーブルのデザインは順調に進んでいます。

構築時間(これまでのモデル)

テーブル[Override]を調整して、データのメンテナンスとクエリを簡素化できます。

ビジネス ルールでオーバーライドが常に 1 日 (全体) であると仮定すると、スケジュール レベルではなく、シーズン レベルでオーバーライドする必要があります。これにより、オーバーライドが行われる曜日について心配する必要がなくなります。オーバーライド テーブルに start_date 列と end_date 列を含めることで、より簡単に一般化することもできます。また、フラグ ( is_closedis_holiday) を単一の列挙列 ( ) に簡略化することもできますclosed_reason。これにより、スキーマとクエリを変更することなく、将来的に新しい理由を追加できます。

テナント時間(次のステップ)

テナント スケジュール モデルは、建物の営業時間に似ている必要があります。テナントの営業時間が建物の営業時間から逸脱する頻度に応じて、テナント テーブルに を示すフラグを含めることができますuses_building_schedule。これは、テナント スケジュール エントリがないことを示します。

テナントが占有する建物とは異なるスケジュールを持つテナントについては、同じシーズン/スケジュール/オーバーライド構造を使用してください。

テナントの営業時間は建物の営業時間によって制限される必要があるというルールがあるため、このルールを適用するには手続き型のコードを追加する必要があります。データのメンテナンス時ではなく、クエリ時に行います。言い換えれば、テナントの営業時間を照会する場合、営業時間があるかどうかを確認し、これらの営業時間を建物の営業時間で制限します。

于 2012-05-19T12:03:43.263 に答える
1

それがあなたの要件にどの程度適合するかはわかりませんが、アイデアを提供させてください。

ここに画像の説明を入力

このモデルには次の特徴があります。

  • 建物またはテナントにはスケジュールがあります (他の建物/テナントと共有される場合と共有されない場合があります)。
  • スケジュールは一連の間隔です。
  • スケジュール内の各間隔には優先度があります。優先度の高い間隔は、優先度の低い間隔を「オーバーライド」します。SCHEDULE_ITEM がどのように制約されているかを見てください。間隔が同じスケジュールに 2 回属することはできず、同じスケジュールに属する他の間隔に対してあいまいな優先順位を持つことはできません (UNIQUE 制約U1)。
  • 間隔はスケジュール間で共有できるため、たとえば、年間イベントをカバーする単一の間隔を設定してから、それを多くのスケジュールで共有できます。イベントが変更された場合、1 か所で更新するだけで済みます。

このモデルの問題点は、処理が重く、DBMS ではデータ内の無意味なケースを回避できないことです (アプリケーション レベルで行う必要があります)。一方で、非常に柔軟で強力です。

特定の日の建物またはテナントの時間を見つけることは簡単ではありません。スケジュールの下のすべての間隔を優先度順に交差させる必要があります。特定のテナントの営業時間を見つけるには、まずテナントの建物の営業時間を見つけてから、それらを交差させる必要があります。

そんなスケジュール…

  • 年間を通して午前 9 時から午後 5 時まで、
  • 午前 8 時から午後 6 時までの 5 月から 10 月を除き、
  • 土曜日の午前 9 時から午後 1 時を除き、
  • 日曜日は営業時間なし
  • 1月1日、7月4日、12月25日は無休

...次のように表すことができます。

SCHEDULE_ID     PRIORITY    MONTH_START     MONTH_END       DAY_OF_MONTH_START      DAY_OF_MONTH_END        DAY_OF_WEEK_START       DAY_OF_WEEK_END     HOUR_START      HOUR_END
1               1           NULL            NULL            NULL                    NULL                    NULL                    NULL                9 AM            5 PM
1               2           5               10              NULL                    NULL                    NULL                    NULL                8 AM            6 PM
1               3           NULL            NULL            NULL                    NULL                    6                       6                   9 AM            1 PM
1               4           NULL            NULL            NULL                    NULL                    7                       7                   NULL            NULL
1               5           1               1               1                       1                       NULL                    NULL                NULL            NULL
1               6           7               7               4                       4                       NULL                    NULL                NULL            NULL
1               7           12              12              25                      25                      NULL                    NULL                NULL            NULL
于 2012-05-19T14:08:10.657 に答える