質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.12%

時間単位での予約システムのロジックが思いつかない

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 6,062
退会済みユーザー

退会済みユーザー

現在時間単位での予約システムを作っているのですが、そのロジックというか設計が思いつかないです。皆さんの知恵をお借りしたいと思って投稿させていただきました。

テーブル設計は以下のとおりです

  • ユーザテーブル
  • シフトテーブル(日毎にレコード作成、シフト開始時間終了時間を値として持つ)
  • date(シフトの日付) 
  • start(開始時刻)
  • end(終了時刻)
  • 予約テーブル(シフトテーブルに1:多として持たせる。何時から何時までの予約が入っているか値として持つ)

システムとしては以下の通りです。

ユーザを複数作成し、ここのユーザ毎にシフト時間を決めます。例えば20時~23時のような感じです。
他ユーザで例のユーザシフトの間に予約を入れます(なんの予約かは特に必要ないと思うので省略します)。20~23なので20~22の予約が入ったとします。

ここで、予約情報を管理するため、予約テーブルを使用し誰のどのシフトに対して予約が入ったのかをレコードとして保存します。

ここでまた他のユーザから予約を入れようとした時、20~22の間は予約できますが、すでに22時の予約が入っているので、22~23時までの予約しかできないようになります。この判定は予約テーブルとシフトの時間から抽出できるかと思います。

問題は日付を超える場合だと思います。シフトレコードは日毎に作成しているので、日をまたぐような予約だと、二つのシフトレコードを考慮しないといけないと思います。これだとあまりスマートな手法なのかなというのが引っかかっています。なので日を超える予約は不可にしようと思っています。

ですが24時周辺は予約が入りやすいので、一日の終わり時間を4時ごろにしようと思います。
4時にした場合やはり日は超えてしまうので、見かけ上だけ日を超えさせておいて、裏で指定時間-4といった風に組んでやればなんとか出来るかなと考えています。

説明が下手で申し訳ありません。訂正点などご指摘いただけると訂正いたしますので、回答よろしくお願いします。


2016年1月1日のシフトが25~27時だった場合、シフト上は1日のシフトですが、データとしては2016-01-02 1:00始まりとなると思います。
このような場合、どうやって1月1日のシフトとして認識すれば良いのでしょうか? 

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

checkベストアンサー

+10

シフトテーブルは日毎に作成している

ここがすべての問題の元凶でしょう。
日毎にテーブルが増えるなんてゆうのは、アンチパターンの代表格です。


date(シフトの日付) 
start(開始時刻)
end(終了時刻) 

これが、間違いの元。

シフトの日付は不要

datetime型で start, datetime型で end を持っていればすむことでは?


これで、イメージできるんじゃないかと思いますが。

データベース定義

スタッフテーブル

CREATE TABLE `Staff` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'PK',
  `name` varchar(32) DEFAULT NULL COMMENT 'スタッフ名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 id   name 
 1   スタッフ名 

シフトテーブル

CREATE TABLE `Shift` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'PK',
  `staff_id` int(11) DEFAULT NULL COMMENT 'スタッフID',
  `start` datetime DEFAULT NULL COMMENT '勤務開始日時',
  `end` datetime DEFAULT NULL COMMENT '勤務終了日時',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 id   staff_id   start   end 
 1   1   2017-01-01 22:00:00   2017-01-02 02:00:00 
 1   1   2017-01-02 22:00:00   2017-01-03 02:00:00 

予約テーブル

CREATE TABLE `Reserv` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'PK',
  `staff_id` int(11) DEFAULT NULL COMMENT 'スタッフID',
  `start` datetime DEFAULT NULL COMMENT '開始日時',
  `end` datetime DEFAULT NULL COMMENT '終了日時',
  `name` varchar(32) DEFAULT NULL COMMENT '予約氏名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 id   staff_id   start   end   name 
 1   1   2017-01-01 23:00:00   2017-01-02 01:00:00    お客さん 

以下のコメントに対する回答

取得はそれで大丈夫ですが、表示する場合です。1日は何時から何時まで, 2日は何時から何時まで、という風に。カレンダーのように表示するのもいいですが。
表示する場合は、先ほど行ったように、同じ日にシフトが二つ表示される場合などありませんか?

登録時に1日の25時から26時(UI)で登録したのに、いざ一覧表示を見てみると2日にシフトがある。実質は変わりないのですが、使用するユーザからするとおかしな挙動になりませんか?

SELECT 
    st.name AS 'スタッフ名'
    , DATE_FORMAT(sf.start, '%Y年%m月%d日') AS '勤務日'
    , TIME_FORMAT(sf.start, '%H:%i') AS '勤務開始時間'
    , IF(
        DATEDIFF(sf.end,sf.start)>0
        , concat(TIME_FORMAT(sf.end, '%H') + 24 * DATEDIFF(sf.end,sf.start), ':', TIME_FORMAT(sf.end, '%i'))
        , TIME_FORMAT(sf.end, '%H:%i')
    ) AS '勤務終了時間'
FROM Shift sf
INNER JOIN Staff st ON sf.staff_id = st.id


イメージ説明

ついでにおまけ

スタッフテーブルに「時給」を追加

CREATE TABLE `Staff` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'PK',
  `name` varchar(32) DEFAULT NULL COMMENT 'スタッフ名',
  `salery` int(11) DEFAULT NULL COMMENT '時給',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

残業時間、深夜勤務時間を算出し、日給を計算
(多分、MySQLでしか動かないだろうけど...)

SELECT 
    st.name AS 'スタッフ名'
    , DATE_FORMAT(sf.start, '%Y年%m月%d日') AS '勤務日'
    , sf.start AS '勤務開始日時'
    , sf.end AS '勤務終了日時'
    , TIME_FORMAT(sf.start, '%H:%i') AS '勤務開始時間'
    , IF(
        DATEDIFF(sf.end,sf.start)>0
        , concat(TIME_FORMAT(sf.end, '%H') + 24 * DATEDIFF(sf.end,sf.start), ':', TIME_FORMAT(sf.end, '%i'))
        , TIME_FORMAT(sf.end, '%H:%i')
    ) AS '勤務終了時間'
    , @hour:=HOUR(TIMEDIFF(sf.end,sf.start)) + MINUTE(TIMEDIFF(sf.end,sf.start)) / 60 AS '勤務時間(h)'
    , @exceed:=IF(
        HOUR(TIMEDIFF(sf.end,sf.start)) + MINUTE(TIMEDIFF(sf.end,sf.start)) / 60 - 8 > 0
        , HOUR(TIMEDIFF(sf.end,sf.start)) + MINUTE(TIMEDIFF(sf.end,sf.start)) / 60 - 8
        , 0
    ) AS '残業時間(h)'
    , @night:=HOUR(
        TIMEDIFF(
            LEAST(sf.end, DATE_FORMAT(ADDDATE(DATE(sf.start), INTERVAL +1 DAY), '%Y-%m-%d 07:00:00'))
            , GREATEST(sf.start, STR_TO_DATE(CONCAT(DATE(sf.start), '22:00:00'), '%Y-%m-%d %H:%i:%s'))
        )
    )
    +
    MINUTE(    
        TIMEDIFF(
            LEAST(sf.end, DATE_FORMAT(ADDDATE(DATE(sf.start), INTERVAL +1 DAY), '%Y-%m-%d 07:00:00'))
            , GREATEST(sf.start, STR_TO_DATE(CONCAT(DATE(sf.start), '22:00:00'), '%Y-%m-%d %H:%i:%s'))
        )
    ) AS '深夜勤務(h)'
    , st.salery AS '時給'
    , FLOOR(@hour * st.salery + @exceed * st.salery * 0.25 + @night * st.salery * 0.25) AS '給与'
FROM Shift sf
INNER JOIN Staff st ON sf.staff_id = st.id

イメージ説明

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/18 14:26

    ソースコードの保守性優先

    キャンセル

  • 2016/11/18 14:28

    ありがとうございます。
    とても参考になりました。

    長い間お付き合いありがとうございました!

    キャンセル

  • 2016/11/18 14:30

    お疲れさんでした。

    キャンセル

+3

シフトテーブルは日毎に作成しているので、

無駄に処理がややこしくなるのでやめた方が良いです。ご質問を読む限り、日毎にテーブルを分ける必然性はないように思います。

単純に一つのテーブルに日付時刻形式で管理すれば何の問題もないと思います。Rubyは使ったことがないのでよく判らないのですが、たぶん日付時刻形式のデータ型は用意されているでしょうし、DBMSでも同様のデータが扱えます。そうすれば、朝9時出社~翌日夜10時退社、などというブラック勤務にも対応できます(対応してはいけません……)。
UI等に表示する際は、開始日時に対して日付のみを指定(あるいは当該日付の00:00~23:59までと指定)して検索すれば、指定した日付から開始するシフトのレコードを抽出できます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/18 00:00

    すいません。テーブルではなくレコードの間違いです。

    キャンセル

  • 2016/11/18 09:23

    前提が変わってしまいますね……。とはいえ、やはり同じことです。日付時刻形式でシフト時間を管理すれば、日またぎなどの問題は起こりません。

    キャンセル

+2

シフトテーブルは日毎に作成している

何も得しないので、私も止めた方がいいと思います。


日を超える予約は不可

それでは、ユーザ側から見て使いにくいシステムです。
これがたとえば宿泊などの場合、長期予約のお客を逃して、ビジネス的にも痛いです。


一日の終わり時間を4時ごろにしようと思います。 
4時にした場合やはり日は超えてしまうので、見かけ上だけ日を超えさせておいて、
裏で指定時間-4といった風に組んでやればなんとか出来るかな

そうした独自の約束事を作ってしまうと、「年末は年をまたいでしまうので~」
などとルールがルールを呼び、「月割引の追加」など仕様変更のたびに複雑化します。
属人性が高く、長期間のメンテがしづらいシステムになってしまいそうです。


じゃあどうすればいいのかというと、(Railsでの拡張もありますが)
Rubyの時刻系ライブラリを使うと、時刻や日付を容易に扱えます。

23時に4時間足すと翌日の3時とか、日またぎの演算も使いこなせばスマートに実現できます。
これを使い、「24時=0時で翌日に変わる」など、常識的な時刻の概念と一致させます。
また、予約期間は開始日時と終了日時で決める、というのがシンプルでしょう。

そして、この組み込みライブラリをラップした日付APIを作り、
予約ロジックのレイヤーやDBアクセスのレイヤーから分離すると、
テストも変更もしやすいし、分かりやすい設計になると私は思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

「4時」を「28時」として扱えば解決しそうですが。
日またぎも「23~25時」の様に処理できる。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/17 02:16

    回答ありがとうございます。
    例えばシフトを23~26時にする場合など、シフトレコード二つのデータを記述しないといけないのではないでしょうか?当日のシフトレコードに23~24時、翌日のシフトレコードに0~2時のような感じで。

    キャンセル

  • 2016/11/17 02:27

    1日を24時までと決めればそうでしょうが、どう考えてもそれはこの状況においては不都合なわけですよね?だから1日の終わりを4時にするなら4時~28時という時間管理の仕方も十分考えられると思いますが。

    キャンセル

0

シフトテーブル(日毎にレコード作成、シフト開始時間終了時間を値として持つ) 

問題の根幹はここにあるように思います。
シフトテーブルが1レコード=1日となっている以上、どこかで「1日の区切り」は発生します。
例え1日の終りを4時に設定したとしても、「4時またぐ予約」が発生した場合にはやはり同様の問題に直面します。あくまで多いか少ないか、発生する可能性が高いか低いかの違い過ぎません。

解決策としては思いつくのは2つ。

  • シフトレコード及び予約レコードを、日をまたぐ場合は2つに分割
    ※この場合、同日のレコードが2つ以上発生することを念頭に置いて設計する必要があります。
  • 「日付」「開始時刻」「終了時刻」という形式で持っているのであれば、日付と時刻を統合して「開始日時」「終了日時」の形式で持つ

個人的には、「1つのシフト」「1つの予約」が1レコードとなる後者が好みです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる