スタッフが休日にいるときに保存したいとし(FromDate
、ToDate
)、2つの指定された日付(QFromDate
、QToDate
)。
次に、そのようなレコードがたくさんあり(サーバーのRAMに収まらないほど)、このクエリを頻繁に実行する必要があると仮定します。
ここで、sick_leave
テーブル、shift_pattern
テーブル、pay_rate
テーブルなど–重複する日付に基づいてそれらを結合する必要があるFromDate
およびToDate
を持つすべてのテーブル。
日付範囲をどのように保存し、クエリを高速に実行するように書き込むことができますか?
(RDBMSの選択は固定されていませんが、「標準」RDBMSで実行できることは、大きな悪影響がない限り価値があります。)
検討した回答をいくつか投稿しましたが、気に入りません!しかし、彼らは他の人々を助けるかもしれません。
たとえば、日付範囲(特定の休暇など)に含まれるすべての年と週のペアの行を持つ、カスタマイズされたインデックステーブルを生成できます。次に、そのインデックステーブルを介して日付範囲を結合できます。それは大きくなりますが、内部結合として、別の休暇と共通の週があるすべての休暇を一覧表示できるため、大規模なスキャンを回避できます。
疑似例:
create table rangeindex (
vacation_id bigint,
year int,
week int,
primary key (year,week,vacation_id),
index (vacation_id))
select v2.*
from vacation v1
join rangeindex r1 on r1.vacation_id = v1.vacation_id
join rangeindex r2 on r2.year = r1.year and r2.week = r1.week
join vacation v2 on v2.vacation_id = r2.vacation_id
where v1.vacation_user = ?
-- and the usual start/end comparisons to filter unwanted pseudo hits
線形座標をグリッドセルに分割するなど、空間座標系のものなど、同様のものが使用されます。
更新:主キーを修正
したがって、重複する日付範囲を見つけることができます
_WHERE FromDate <= QToDate
AND ToDate >= QFromDate
_
部分的または完全に重複する範囲ごとに1行が返されます。たとえば、朝、午後、夕方のシフトに別々の給与があり、誰かが3つすべて働いていた場合、3つの行が返されます。
列が日付、時刻、日時、または曜日など、同じ基本パターンが適用されます。列では適切なDATEPART()
計算を使用する必要があります。
オープンインターバルとクローズドインターバルのどちらが必要かを確認してください。つまり、比較に「<」または「<=」を使用する必要があります。間違った判断をすると、見逃したり、数え直したりすることがあります。
ストレージについては、明らかにニーズに合ったタイプを使用する必要があります。祝日はdate
だけで開催できます。シフトはtime
であると推測します。なぜなら、彼らが働いているどの曜日でも、または(曜日、時間)それがあなたのシステムが対応しなければならない場合は同じだからです。休日?ほとんどの雇用主は、休日の端数を認めているため、datetime
が適切な場合があります。
"from"だけではなく、各行の "from"と "to"の両方の値を格納し、値がテーブルの他の行で終了したときに解決することをお勧めします。いくつかのマジック値またはいくつかのNULLが必要になる場合がありますが、SQLは全体的に単純になります。
インデックスにTo列とFrom列を含めることにより、パフォーマンスが向上します。キーの順序でそれらが表示される場所は、各テーブルの主キーと使用パターンによって異なります。 DBMSは、関数にラップされた列のインデックスの使用を拒否する場合があることに注意してください。それらは、専門用語では「検索不可」になります。クエリの述語を前処理して、列のタイプと一致させるのではなく、逆に処理する必要がある場合があります。
テンポラルデータベースに関する研究分野があります。それらが顧客の快適ゾーンの外にある場合でも、これらの問題が他の場所でどのように解決されたかから洞察を得るでしょう。
FromDate/ToDate形式の問題は、クエリがインデックススキャンを生成することです。
たとえば、スタッフメンバーが休暇を取って2週間後に戻ってきたとします。
ID FromDate ToDate
15 2011-01-03 2011-01-17
多くのスタッフが、そのような職歴が20年または30年ある場合があり、年に数回、一度に1日か2日しかかかりません。
select ...
from ...
where StaffID = :Id
and FromDate >= :AsOf
and ToDate <= :AsOf;
最初に見つかったヒットが唯一の結果になることがわかっている場合でも、指定されたスタッフのすべてのレコードを調べる必要があります。一部のDBMSでは、「最初に見つかったレコードで停止」を指定できますが、これはプラットフォームに大きく依存し、すべてのプラットフォームで使用できるわけではありません。また、インデックスの「間違った」終わりから開始すると、インデックススキャンが実行される可能性があります。どちらの場合も、その時点で休暇中でなかったため「現在」の日付が範囲内にない場合は、すべてのエントリも調べる必要があります。
さらに、わずかな問題が1つあります。空の結果が、スタッフメンバーが休暇中でなかったか、クエリがなんらかの理由で失敗したことを意味する可能性があるという、小さいながらも存在する疑念。
プラットフォームに依存せず、インデックスシークを使用して結果を取得できる方法は、スタッフの休暇ステータスが変更された日付を1つだけ変更したときにレコードを入力することです。
たとえば、最初に雇用されたとき、スタッフは「休暇中ではありません」から開始します。これは「勤務中」に「O」を指定できます。
ID Status EffDate
15 O 2010-01-04
1年後、彼は休暇を取り、2週間後に戻ります。
ID Status EffDate
15 O 2010-01-04
15 V 2011-01-03
15 O 2011-01-17
これで日付があり、その時点でスタッフ#15が勤務しているか休暇中であったかを知りたいとします。
select sv1.ID, sv1.Status
from StaffVacation sv1
where sv1.StaffID = :Id
and sv1.EffDate =(
select Max( sv2.EffDate )
from StaffVacation sv2
where sv2.StaffID = sv1.StaffID
and sv2.EffDate <= :AsOf );
サブクエリは時間を浪費する複雑なもののように見えるかもしれませんが、(StaffID、EffDate)にインデックスがあると仮定すると、インデックスシークで1つの結果が見つかります。直接比較を実行するためにその値を返すと、外側のwhere
句もインデックスシークを実行しますが、サブクエリで検出された同じレコードをシークするため、それほど遠くまでシークする必要はありません。そのレコードはまだどこかにキャッシュされています。
「現在」の日付が現在のタイムスタンプの場合、スタッフの現在のステータスが表示されます。 where
句のスタッフIDを省略すると、すべてのスタッフの現在のステータスを取得できます。
また、日付がスタッフメンバーの雇用期間のどこかにある限り、クエリは常に「O」または「V」のいずれかを返すという(確かに小さい)利点もあります。インデックスも一意として定義されている場合(どのスタッフでも2つの日付を同じにすることはできません-明らかな要件です)、ステータス変更の間にギャップやオーバーラップを設けることはできません。
利点としてカウントされる場合とされない場合がある1つの追加機能。休暇時間が事前に計画されている場合(ほとんどの場合)、「休暇中」と「勤務に戻る」のエントリを事前に入力できます。スタッフの休暇申請が承認されるとすぐにエントリが作成され、フォローアップは必要ありません-休暇が計画どおりに進んでいる限り...
カレンダーテーブル(グーグル可能)を生成し、他のテーブルをそれに結合します。これにより、オーバーラップなどを効率的に行うことが容易になります。
明確でない場合は、これが標準的な方法です。ここでの回答には複雑すぎるため、使用例には触れていません。 「カレンダーテーブル」を検索すると、多くの例と作業の完全な説明が表示されます。
Tablescanの問題は、両方の日付フィールドにインデックスを付けることですでにカバーされているため、解決する必要はありません。