9

私は自分のカレンダーを作成しようとしています。以下に示すイベントを入力するためのフォームがあります。

MySQL テーブルを設計する方法と、この情報を簡単に抽出できるようにログに記録する方法について行き詰まっています。

カレンダーイベントフォーム

以下は私の試みであり、動作しますが、本当に醜いので、改善できると確信しています:

CREATE TABLE events (
  event_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  title VARCHAR(100) NOT NULL,
  location VARCHAR(100) NOT NULL,
  body TEXT NOT NULL,
  start_ts DATETIME NOT NULL,
  end_ts DATETIME NOT NULL,
  valid ENUM('Y','N') DEFAULT 'Y',
  reoccurring ENUM('Y','N') DEFAULT 'N',
  every ENUM('day','week','month','year',''),
  bymonth ENUM('day','weekday',''),
  end_date DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',

  PRIMARY KEY (event_id)
);

CREATE TABLE events_byweek (
  event_id INTEGER UNSIGNED NOT NULL,
  weekday TINYINT UNSIGNED NOT NULL,

  FOREIGN KEY (event_id)
    REFERENCES events(event_id)
);

-- returns all dates, reoccurring or otherwise within specified time range
-- by month
SELECT * FROM events WHERE (:year = YEAR(start_ts) AND :month = MONTH(start_ts))
OR (reoccurring = 'Y' AND ((YEAR(end_date) >= :year AND MONTH(end_date) >= :month) OR end_date = '0000-00-00 00:00:00')
AND (every != 'year' OR MONTH(start_ts) = :month)) AND valid = 'Y'

-- by day
SELECT * FROM events WHERE DATE(start_ts) = :date
OR (reoccurring = 'Y' AND (DATE(end_date) >= :date OR end_date = '0000-00-00 00:00:00')
AND (every != 'year' OR MONTH(start_ts) = :month)) AND valid = 'Y'

-- by week
SELECT * FROM events_byweek WHERE event_id = :event_id

アドバイスをいただければ幸いです。

4

2 に答える 2

6

リレーション配列は必要ないと思います。

db テーブルは次のようになります。

event_id UINT(10) auto_increment NOT NULL
/* not important fields omitted */
start DATETIME not null
end DATETIME not null
reoccurring ENUM('NO', 'WEEKDAY', 'MONTHDAY', 'MONTH_REL', 'YEARLY') DEFAULT 'NO';
weekdays UINT(1) DEFAULT 0;
until TIMESTAMP DEFAULT NULL

クエリが簡単なので使用DATETIMEしましたが(以下を参照)、実際には問題ありません。お好みでお預かりできTIMESTAMPます。

: 日曜日、: 月曜日などweekdaysのビットでバイトを保持できます。したがって、イベントが毎日繰り返される場合は、そこに配置します。2^02^1127

の場合、イベントuntilNULL永遠に繰り返されます。

「その月の第3水曜日」が特定の範囲内にあるかどうかをSQLで見つけるのは非常に難しいため、ユーザー定義関数を使用しないと不可能ではないかと思いますが、非常に難しく、コードが明確ではないことをお勧めします。すべてのイベントを取得し、それらを php で取得して、そこでフィルター処理します。

必要なイベント (事前フィルタリング) のみをロードするクエリは次のようになります。

SELECT ... FROM events
  WHERE 
     /* We take all non-reoccuring events in period */
    ((reoccurring = 'NO') AND (start >= :start) AND (end <= :end))
  OR
     /* We take some part of reoccurring events */
    ((reoccuring <> 'NO') AND ((start <= :end) OR (end >= :start)) AND ((until >= :start) OR until IS NULL)
  ORDER BY start ASC;

したがって、フェッチ中に、レコードが次の基準を満たしているかどうかをテストできます。

  1. 再発しないのでそのままにしておく
  2. 特定の平日 (例: 毎週水曜日と毎週金曜日) に繰り返し発生する - 期間の間にそのような平日があるかどうかを確認し、少なくとも 1 つある場合はイベントを維持し、そうでない場合は破棄します
  3. 繰り返しが月の日にある場合 (例: 12 月 21 日ごと)、同じことを行います。
  4. 繰り返しが毎月 - 相対的な日 (第 3 水曜日など) の場合は、同じことを行います。

レコードが基準を満たしていない場合は、もちろんデータベースではなく配列から削除してください:-)。

一部のタスクは、SQL クエリに入れるのも簡単です。たとえば、特定の月日で繰り返されるイベントをフィルタリングできますSELECT ... WHERE ... OR ... (start LIKE '%-:month-:day %)(質問の写真に示されているように、イベントの開始と終了が同じであると仮定します)。DATETIMEこれは、文字列のように簡単に検索できるというフィールドの利点です。したがって、 %-12-21 %12 月と 21 日を含むすべてのレコードが検索されます (もちろん、これらは常に 2 桁の数字である必要があります)。TIMESTAMP(日付差等の計算がしやすいのがメリットです)

イベントが月日ごとに繰り返される場合は、... LIKE%-%-:day %` などを使用します。

booleanしたがって、最後に、 2 つのケースをチェックする2 つの関数を返す必要があります。

  1. 特定期間の平日
  2. 期間中の n 番目の平日です (最初の曜日が失敗した場合、2 番目の曜日を実行する必要はありません)。

foreachまたは何かを使用して、ブルートフォースでもコーディングできます。

また、フィールド値が 127 の場合は平日のチェックを実行する必要がないため、毎日発生します。

EDIT on 2013-03-29 - ビットを平日として使用する方法の説明

フィールドweekdaysでは、1 つの (符号なし) INT(1) 数値に 7 日と 8 ビットがあるため、日をビットとして保持できます。したがって、「occurs_monday」、「occurs_tuesday」などの列を追加する必要も、リレーションを使用する必要もありません。イベントが「毎週月曜日」と「毎週金曜日」に発生する可能性があると思うので、このように提案しました。そうでない場合は、そこに数値を保持します (0 = 日曜日、1 = 月曜日など)。

また、毎日発生するイベントは、週 7 日発生するイベントの特殊なケースでもあるため、列に別のENUM値は必要ありません。reoccurring

PHPでそれを使用する方法は?

特定の曜日のビットが設定されているかどうかをテストするだけです。これは、ビットAND演算子を使用して行うことができます。いくつかの定数を定義すると、さらに簡単になります。

define("WEEKDAY_SUNDAY",1); // 2^0
define("WEEKDAY_MONDAY",2); // 2^1
define("WEEKDAY_TUESDAY",4); // 2^2
// ....
define("WEEKDAY_SATURDAY",64); // 2^6

// Does the event occur on Friday, maybe also other weekdays?
if($row['weekdays'] & WEEKDAY_FRIDAY){

// Does the event occurs only on Friday, and no other day?
if($row['weekdays'] == WEEKDAY_FRIDAY){

// Let's make the event occur on Friday and the day(s) that it already occurs
$row['weekdays'] = $row['weekdays'] | WEEKDAY_FRIDAY;

// Make the event occur only on Friday and no other weekday
$row['weekdays'] = WEEKDAY_FRIDAY;

// Let's check if the event occurs today:
if(pow(2,date('w')) & $row['weekdays']){ //...

datew」パラメータを持つ関数は、0 から 6 までの曜日番号を返すので、そのように使用しました。

于 2013-03-25T18:46:14.270 に答える
2

私は以前にこの問題に取り組んだことがありますが、それは複雑です。しかし、私は理にかなっていると思う解決策を思いつきました(スタックオーバーフローの助けを借りて)。私がやったことは、イベントテーブルとイベント繰り返しテーブルを作成することです。イベントテーブルは、個々のイベントを保持する責任があります。イベント繰り返しテーブルは、繰り返しインスタンスを保持する役割を果たします。

次に、イベント繰り返しテーブルを使用して、イベント テーブルに入るイベントを生成します。これにより、イベントを簡単に表示できます。これは、繰り返しの計算がすでに行われているためです。繰り返しが作成されると、2 年分のイベントが生成されます。ユーザーがその 2 年の範囲外に移動した場合は、必要に応じてさらにイベントを生成します。

また、再発ごとに少なくとも 2 年間のイベントが生成されるように、毎晩実行される cron ジョブもあります。すべての繰り返しには、生成されるまでのフィールド、繰り返しの終了、繰り返しの種類、およびタイトル、説明、時間などの情報が必要です。

このルートに進むことに決め、コード サンプルが必要な場合は、必ずお知らせください。

于 2013-03-26T16:16:53.153 に答える