web-dev-qa-db-ja.com

予約可能なエンティティごとに毎日異なるタイムテーブルがあるタイムスロットに基づいて、予約システムを構築およびクエリする方法は?

私は、弁護士が予約システムを開発しています。このシステムでは、人が特定の日の特定の時間(次の弁護士が利用できる日)に予約を予約できます。

弁護士向けのZocDocであるとしましょう。時間に基づくアポイントメントを持つ同じ構造: http://goo.gl/djUZb

MySQLとPHPを使用しています


テーブルスキーマ:

CREATE TABLE `laywer_appointments` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `lawyer_id` INT unsigned,
  `day_of_week` tinyint(3) unsigned DEFAULT '1',
  `slot_date` date DEFAULT NULL,
  `slot_time` time DEFAULT NULL,
  `status` tinyint(4) NOT NULL DEFAULT '0',
  `client_id` int(11) DEFAULT NULL, -- client_id = NULL means free slot
);

ポイント1)

各弁護士には、曜日に基づいたデフォルトの時間枠があります(ステータス= 0は利用可能を意味します)。デフォルトのスロットを挿入する場合、日付を指定せず、day_of_weekのみを指定します。データの例:

+-----------+-------------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | status    |
+-----------+-------------+-----------+-----------+
| 1         | 1           | 08:00     | 0         |
| 1         | 1           | 08:30     | 0         |
| 1         | 1           | 09:00     | 0         |
| 1         | 1           | 10:30     | 0         |
| 1         | 4           | 14:30     | 0         |
| 1         | 4           | 16:40     | 0         |
| 2         | 1           | 10:20     | 0         |
| 2         | 1           | 14:00     | 0         |
| 2         | 3           | 15:50     | 0         |
+-----------+-------------+-----------+-----------+

ポイント2)

弁護士は次のことができます特定の日にタイムスロットを追加この日が彼のデフォルトスロットとは異なる曜日からであってもおよびロックすることもできますstatus = -1)特定の日のデフォルトスロットの1つ(つまり、彼は会議に参加しているか、病気です):

+-----------+-------------+-----------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | slot_date | status    |
+-----------+-------------+-----------+-----------+-----------+
| 1         | 1           | 16:00     | 12/03/13  | 0         |
| 1         | 6           | 11:00     | 26/04/13  | 0         |
| 1         | 6           | 12:00     | 26/04/13  | 0         |
| 2         | 1           | 10:00     | 01/01/13  | -1        |
+-----------+-------------+-----------+-----------+-----------+

ポイント3)

その後、予約された予定があります。この場合、slot_dateとclient_idを入力します。

+-----------+-------------+-----------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | slot_date | client_id |
+-----------+-------------+-----------+-----------+-----------+
| 1         | 1           | 10:30     | 12/03/13  | 10        |
+-----------+-------------+-----------+-----------+-----------+

例として、上記の予約で、同じ日(12/03/13)の6:30であると仮定すると、印刷する必要がある空きスロットは次のようになります。

8:00 - default slot
8:30 - default slot
9:00 - default slot
16:00 - Specific slot inserted in point 2 for 12/03/13

問題:

私は次に利用可能な日付と関連する空き時間(デフォルトのもの、特定のものからロックされたものと予約されたものを差し引いたもの)を返さなければなりません。 「月曜日からの帰りの時間、10/10/13」とは言えません。

検索結果ページで、[すべての弁護士とそれぞれの利用可能時間の表を一覧表示します。つまり、検索が行われるたびに、各弁護士は異なるタイムテーブルを持つことになります。

"SELECT time FROM [bunch of joins] WHERE date = today"とは言えません。

ロックされている(status = -1)または予約されている(client_idがnullではない)スロットを無視するこのクエリを使用しましたが、もちろん、最も近い日の空き時間(および今日から)を返しません。

SELECT p.day_of_week, p.slot_date, p.slot_time
FROM laywer_appointments p
WHERE p.client_id IS NULL AND p.status = 0
     AND p.slot_time NOT IN (
              SELECT s.slot_time FROM laywer_appointments s
              WHERE (s.slot_date IS NOT NULL AND s.client_id IS NOT NULL 
              OR s.status = -1) AND s.day_of_week = p.day_of_week
     )
GROUP BY p.day_of_week, p.slot_date, p.slot_time
ORDER BY p.day_of_week ASC, p.slot_time ASC;

別の問題:今日がday_of_week = 5であるが、特定の弁護士が次に利用できるday_of_weekが2の場合、どうすればクエリできますか?

次の最も近く利用可能なday_of_weekを返し、すべての日ではなく、この日の時間だけを返すように集計する方法は?

1つの可能な解決策

私が付いてきたのは、1つではなく3つのテーブルを作成することでした。

  • default_slots:3列:lawyer_id、day_of_week、time
  • スロット:laywer_id、day_of_week、時間、日付、ステータス
  • 予定:予約済みの予定に関するすべての情報

その後、すべての弁護士のスロットテーブルに、1年までの実際の日付の毎日のすべての空き時間スロットを格納します。(default_slotsから取得した時間スロット)。

+-----------+-------------+-----------+-----------+-----------+
| lawyer_id | day_of_week | slot_time | slot_date | status    |
+-----------+-------------+-----------+-----------+-----------+
| 1         | 1           | 16:00     | 12/03/13  | 0         |
| 1         | 1           | 16:00     | 12/03/13  | 0         |
| 1         | 2           | 08:00     | 13/03/13  | 0         |
| 1         | 2           | 09:00     | 13/03/13  | 0         |
... next week
| 1         | 1           | 16:00     | 19/03/13  | 0         |
| 1         | 1           | 16:00     | 19/03/13  | 0         |
| 1         | 2           | 08:00     | 20/03/13  | 0         |
| 1         | 2           | 09:00     | 20/03/13  | 0         |
... up to an year
| 1         | 1           | 16:00     | 20/03/14  | 0         |
| 1         | 1           | 16:00     | 20/03/14  | 0         |
| 1         | 2           | 08:00     | 21/03/14  | 0         |
| 1         | 2           | 09:00     | 21/03/14  | 0         |
+-----------+-------------+-----------+-----------+-----------+

また、毎週実行されるいくつかのcronジョブを使用して、テーブルスロットに1週間分の空きスロットレコードを追加し、過去のレコードを削除して、テーブルサイズと未使用のデータを減らします。

弁護士は時間をスロットに直接ロックすることも、特定の時間を追加することもできます(ポイント2)。

リストの場合、すべての日付のすべての時刻に行があるため、今日と同じかそれ以上の日付のスロットに空き時間を取得することが問題になります

このソリューションへの影響:1)初日には、2500人の弁護士がいます(2か月目は約6000人)。 8つの可能なスロット/日X作業日20日/月X 12か月を想定= =弁護士あたり1920スロットのレコード。

2500層x 1920レコード= 1日目の48万レコード。(2か月目まで約1200万)

これらのレコードは常に更新、挿入、削除されます。 スロットテーブルにはいくつかのインデックスがあるため、書き込み操作が12M +レコードといくつかのインデックスを持つテーブルで絶えず行われているとは想像できません。毎秒更新されるインデックスはスマートに見えません私に。

合理的でスケーラブルなソリューションを実際に利用することはできません。テーブルが1つしかない私の解決策は機能しましたが、それをクエリする方法はまったく考えられません。そして、非正規化スロットテーブルは巨大になりますが、一定の書き込み操作が必要になります。

任意のヒント?

23
AlfredBaudisch

私はあなたがやろうとしていることと同じようなことをしたので、それがどれほど複雑かを理解しています:)

これはMSSQLで行われたため、MySqlに変換する必要があります。

example of appointments

これらは私たちが終わったテーブルです:

TimeSlots:

このテーブルには、各スタッフメンバーのデフォルトのタイムスロットと変更されたタイムスロットの両方が格納されています(このテーブルには「SlotType」という列があります。SlotType1 = DEFAULT TIMESLOTS&SlotType 2 = MODIFIED TIMESLOTS)。上の画像の "火30/04/13"を見ると、この特定のスタッフメンバーの午前9時の予定のみを表示するようにその日のタイムスロットが変更されていることがわかります。

ClosedDays:

これは休業日のリストです。たとえば、スタッフは誕生日とクリスマスに勤務できません。

予定:

これは予約された(または予約の確認を待っている)予約のリストです。

利用可能な予定を取得するSQLクエリ:

予定を確認するために、ストアドプロシージャで次のSQLを使用しました。指定された日付の1人のスタッフメンバーの予定をチェックします。すべての予定を取得するために、曜日ごとにページ上の各スタッフメンバーを介してループを使用している最後のストアドプロシージャ。このクエリを使用して、次の7日間で10人のスタッフメンバーの予定を取得する=合計70クエリで、各テーブルに100万のレコードがあり、約300ミリ秒かかります。私たちはajaxを介して予定を読み込んでいるので、300msが許容範囲内にあり、将来的にパフォーマンスをさらに向上させるためにajaxを介して各スタッフメンバーの予定を個別に取得するように変更されます(一度に7つのクエリ)。

DECLARE @MyDate date, @MyDayName nvarchar(10);
IF @StartDate IS NULL
    SET @StartDate = GETDATE();
SET @MyDate = CAST(@StartDate AS date);
SET @MyDayName = DATENAME(dw, @MyDate );

--NOTES:
--@SlotType = 1 (DEFAULT TIMESLOTS), 2 (MODIFIED TIMESLOTS)

    --***CHECK TO SEE IF DOCTOR IS CLOSED TODAY***
    IF NOT EXISTS (SELECT [ClosedDays].[ID] FROM [ClosedDays] WHERE [ClosedDays].[StaffID] = @StaffID AND [ClosedDays].[BusinessID] = @BusinessID AND [ClosedDays].[Active] = 1 AND @MyDate BETWEEN [ClosedDays].[StartDate] AND [ClosedDays].[EndDate])
    BEGIN
        --***THE DOCTOR IS NOT CLOSED TODAY SO GET THE AVAILABLE TIMESLOTS***
        --***CHECK TO SEE IF DOCTOR IS HAS MODIED TIMESLOTS TODAY***
        IF NOT EXISTS (SELECT [TimeSlots].[ID], @MyDate AS SlotDate FROM [TimeSlots] WHERE  [TimeSlots].[StaffID] = @StaffID AND [TimeSlots].[BusinessID] = @BusinessID AND [TimeSlots].[Active] = 1 AND [TimeSlots].[SlotType] = 2 AND [TimeSlots].[SlotDay] = @MyDayName AND @MyDate BETWEEN [TimeSlots].[StartDate] AND [TimeSlots].[EndDate] AND [TimeSlots].[ID] NOT IN (SELECT [Appointments].[TimeSlotID] FROM [Appointments]) )
            BEGIN
                --***THE DOCTOR HAS NO MODIFIED TIMESLOTS FOR TODAY USE THE DEFAULT ONES***
                SELECT [TimeSlots].[ID] AS SlotID, [TimeSlots].[StaffID], [TimeSlots].[BusinessID], CONVERT(nvarchar(10), @MyDate, 103) AS SlotDate, [TimeSlots].[SlotDay], LTRIM(RIGHT(CONVERT(nvarchar(10), [TimeSlots].[SlotTime], 100), 7))AS SlotTime FROM [TimeSlots]  
                WHERE  [TimeSlots].[StaffID] = @StaffID AND [TimeSlots].[BusinessID] = @BusinessID AND [TimeSlots].[Active] = 1 AND [TimeSlots].[SlotType] = 1 AND [TimeSlots].[SlotDay] = @MyDayName AND @MyDate BETWEEN [TimeSlots].[StartDate] AND [TimeSlots].[EndDate] AND NOT EXISTS (SELECT [Appointments].[TimeSlotID] FROM [Appointments] WHERE [Appointments].[TimeSlotID] = [TimeSlots].[ID])
            END
            ELSE
            BEGIN
                --***THE DOCTOR HAS MODIFIED TODAYS TIMESLOTS SO USE THE MODIFIED TIMESLOTS***
                SELECT [TimeSlots].[ID] AS SlotID, [TimeSlots].[StaffID], [TimeSlots].[BusinessID], CONVERT(nvarchar(10), @MyDate, 103) AS SlotDate, [TimeSlots].[SlotDay], LTRIM(RIGHT(CONVERT(nvarchar(10), [TimeSlots].[SlotTime], 100), 7))AS SlotTime FROM [TimeSlots]  
                WHERE  [TimeSlots].[StaffID] = @StaffID AND [TimeSlots].[BusinessID] = @BusinessID AND [TimeSlots].[Active] = 1 AND [TimeSlots].[SlotType] = 2 AND [TimeSlots].[SlotDay] = @MyDayName AND @MyDate BETWEEN [TimeSlots].[StartDate] AND [TimeSlots].[EndDate] AND NOT EXISTS (SELECT [Appointments].[TimeSlotID] FROM [Appointments] WHERE [Appointments].[TimeSlotID] = [TimeSlots].[ID])
            END
    END
    ELSE
    BEGIN
            --***NO APPOINTMENTS WERE FOUND***
            --***DUMMY QUERY TO RETURN NO RECORDS***
            SELECT [TimeSlots].[ID] AS SlotID, [TimeSlots].[StaffID], [TimeSlots].[BusinessID], CONVERT(nvarchar(10), @MyDate, 103) AS SlotDate, [TimeSlots].[SlotDay], LTRIM(RIGHT(CONVERT(nvarchar(10), [TimeSlots].[SlotTime], 100), 7))AS SlotTime FROM [TimeSlots]  
            WHERE  [TimeSlots].[ID] = -0
    END

これが理にかなっていることを願っています&他の誰かがこれをより最適化する方法についてのアイデアを持っているなら、私に知らせてください!

20
MWD

あなたは大きなテーブルを持っていることは正しいです。しかし、結果としてアプリケーションが失敗するかどうかは明らかではありません。 MySQL(およびすべてのDBMSソフトウェア)は、大きなテーブルにすばやくアクセスできるように作られています。

優れた専用MySQLサーバーハードウェア(64ビットOS、2つまたは4つの高速プロセッサ、十分なRAM、優れたファイルI/O-SASインターフェースの高速ディスクを備えています)と適切に構成されたサーバーソフトウェアがこのワークロードを処理します。

検索を容易にするためにインデックスを付けることができる、slot_timeとslot_dateを1つのDATETIMEまたはTIMESTAMPフィールドにマージすることができます。 TIMESTAMPデータ項目を使用することを選択した場合、適切な処理を行うと、タイムゾーン処理にいくつかのメリットがあります。

1か月または1週間が過ぎたときに、1か月または1週間にさえ相当するデータをオフラインにすることができるスキームを使用して、大きなテーブルをパーティション分割する方法を考えたい場合があります。

2,500人の弁護士があなたのシステムを使用しているので、あなたはこれを正しくしたいと思うでしょう。まともなデータベース管理者にお金をかけませんか?彼らはほとんどの弁護士よりも時間あたりの費用がかかりません。 Sheeri Cabralは、それを見つける方法の良い要約を書きました。 http://www.sheeri.org/how-to-find-a-dba/

3
O. Jones