28

私は弁護士予約システムを開発しています。このシステムでは、特定の日の特定の時間(次の弁護士の対応可能日)に予約を入れることができます。

弁護士向けの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)

弁護士は、特定の日にタイムスロットを追加でき(この日がデフォルトのスロットとは異なる曜日であっても) 、特定の日のデフォルトのスロットの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        |
+-----------+-------------+-----------+-----------+-----------+

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

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」とは言えません。

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

「SELECTtimeFROM[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つは、1つではなく3つのテーブルを作成することでした。

  • default_slots:3列:lawyer_id、day_of_week、time
  • スロット:laywer_id、day_of_week、time、date、status
  • アポイントメント:予約されたアポイントメントに関するすべての情報

次に、実際の日付から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つの可能なスロット/日X20日間の作業/月X12か月=弁護士あたり1920スロットのレコードを想定しています。

2500 laywersx1920レコード=初日で480万レコード。(2か月目は約12M)

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

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

任意のヒント?

4

2 に答える 2

22

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

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

予定の例

これらは私たちが最終的に得たテーブルです:

タイムスロット:

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

ClosedDays:

これは休業日のリストです。たとえば、スタッフが誕生日とクリスマスの日に仕事をしていません。

予定:

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

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

次に、予定を確認するために、ストアドプロシージャで次のSQLを使用しました。指定された日付の1人のスタッフの予定をチェックします。使用している最終的なストアドプロシージャは、すべての予定を取得するために、曜日ごとにページ上の各スタッフをループします。このクエリを使用して、次の7日間で10人のスタッフの予定を取得する=合計70のクエリで、各テーブルに100万レコードを含む約300ミリ秒かかります。アポイントメントはajax経由でロードしているため、300ミリ秒で使用できます。今後、パフォーマンスをさらに向上させるために、各スタッフのアポイントメントを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

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

于 2013-04-27T23:37:26.680 に答える
3

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

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

slot_timeとslot_dateを単一のDATETIMEまたはTIMESTAMPフィールドにマージして、検索を容易にするためにインデックスを付けることができます。TIMESTAMPデータ項目を使用することを選択した場合、正しく実行すれば、タイムゾーンを処理するための優れた利点が得られます。

その月または週が過ぎたときに、1か月分、または1週間分のデータをオフラインにするスキームを使用して、大きなテーブルを分割する方法を検討することをお勧めします。

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

于 2013-03-26T01:27:05.777 に答える