私はカスタムイベントシステムを構築しています。次のような繰り返しイベントがある場合:
イベントAは2011年3月3日から4日ごとに繰り返されます
または
イベントBは、2011年3月1日から火曜日に2週間ごとに繰り返されます
簡単に検索できるようにデータベースに保存するにはどうすればよいですか。多数のイベントがある場合はパフォーマンスの問題が発生したくないので、カレンダーをレンダリングするときにすべてのイベントを実行する必要があります。
私はカスタムイベントシステムを構築しています。次のような繰り返しイベントがある場合:
イベントAは2011年3月3日から4日ごとに繰り返されます
または
イベントBは、2011年3月1日から火曜日に2週間ごとに繰り返されます
簡単に検索できるようにデータベースに保存するにはどうすればよいですか。多数のイベントがある場合はパフォーマンスの問題が発生したくないので、カレンダーをレンダリングするときにすべてのイベントを実行する必要があります。
回答:
PHP / MySQLベースのカレンダーでは、繰り返し/繰り返しのイベント情報をできるだけ効率的に保存したいと考えていました。行数を多くしたくなかったので、特定の日付に発生するすべてのイベントを簡単に検索したいと思いました。
以下の方法は、毎日、n日ごと、毎週、毎月など、定期的に発生する繰り返し情報を格納するのに最適です。これには、毎週火曜日と木曜日のタイプのパターンも含まれます。毎週火曜日から始まり、毎週木曜日から始まります。
2つのテーブルがあるとすると、1つは次のevents
ように呼び出されます。
ID NAME
1 Sample Event
2 Another Event
そして、次のevents_meta
ようなテーブル:
ID event_id meta_key meta_value
1 1 repeat_start 1299132000
2 1 repeat_interval_1 432000
repeat_startはUNIXタイムスタンプとして時間のない日付であり、repeat_intervalは間隔(432000は5日)間の秒数です。
repeat_interval_1はID 1のrepeat_startと連動します。したがって、毎週火曜日と毎週木曜日に繰り返すイベントがある場合、repeat_intervalは604800(7日)になり、2つのrepeat_startsと2つのrepeat_intervalsがあります。テーブルは次のようになります。
ID event_id meta_key meta_value
1 1 repeat_start 1298959200 -- This is for the Tuesday repeat
2 1 repeat_interval_1 604800
3 1 repeat_start 1299132000 -- This is for the Thursday repeat
4 1 repeat_interval_3 604800
5 2 repeat_start 1299132000
6 2 repeat_interval_5 1 -- Using 1 as a value gives us an event that only happens once
次に、毎日ループするカレンダーがあり、その日のイベントを取得する場合、クエリは次のようになります。
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN `events_meta` EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
AND (
( CASE ( 1299132000 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1299132000 - EM1.`meta_value` )
END
) / EM2.`meta_value`
) = 1
LIMIT 0 , 30
{current_timestamp}
現在の日付のUNIXタイムスタンプに置き換えます(時間をマイナスするため、時間、分、秒の値は0に設定されます)。
うまくいけば、これは他の誰かにも役立つでしょう!
この方法は、次のような複雑なパターンを保存するのに適しています。
Event A repeats every month on the 3rd of the month starting on March 3, 2011
または
Event A repeats Friday of the 2nd week of the month starting on March 11, 2011
これを上記のシステムと組み合わせて、最も柔軟性を高めることをお勧めします。このためのテーブルは次のようになります。
ID NAME
1 Sample Event
2 Another Event
そして、次のevents_meta
ようなテーブル:
ID event_id meta_key meta_value
1 1 repeat_start 1299132000 -- March 3rd, 2011
2 1 repeat_year_1 *
3 1 repeat_month_1 *
4 1 repeat_week_im_1 2
5 1 repeat_weekday_1 6
repeat_week_im
現在の月の週を表します。1から5の間の可能性があります。repeat_weekday
曜日、1-7。
ここで、日/週をループしてカレンダーに月ビューを作成すると想定すると、次のようなクエリを作成できます。
SELECT EV . *
FROM `events` AS EV
JOIN `events_meta` EM1 ON EM1.event_id = EV.id
AND EM1.meta_key = 'repeat_start'
LEFT JOIN `events_meta` EM2 ON EM2.meta_key = CONCAT( 'repeat_year_', EM1.id )
LEFT JOIN `events_meta` EM3 ON EM3.meta_key = CONCAT( 'repeat_month_', EM1.id )
LEFT JOIN `events_meta` EM4 ON EM4.meta_key = CONCAT( 'repeat_week_im_', EM1.id )
LEFT JOIN `events_meta` EM5 ON EM5.meta_key = CONCAT( 'repeat_weekday_', EM1.id )
WHERE (
EM2.meta_value =2011
OR EM2.meta_value = '*'
)
AND (
EM3.meta_value =4
OR EM3.meta_value = '*'
)
AND (
EM4.meta_value =2
OR EM4.meta_value = '*'
)
AND (
EM5.meta_value =6
OR EM5.meta_value = '*'
)
AND EM1.meta_value >= {current_timestamp}
LIMIT 0 , 30
これを上記の方法と組み合わせると、ほとんどの繰り返し/繰り返しイベントパターンをカバーするように組み合わせることができます。何か見逃した場合はコメントを残してください。
AND ( ( CASE ( 1299132000 - EM1.meta_value ) WHEN 0 THEN 1 ELSE ( 1299132000 - EM1.meta_value) END ) / EM2.meta_value ) = 1
は/ EM2.meta_value
間違って配置されていますか?
86400
夏時間を考慮しないため、ハードコードされた値を繰り返し間隔(1日の秒数)に使用しないでください。それはその場で動的にこれらの事を計算し、代わりに格納することがより適切だinterval = daily
とinterval_count = 1
かinterval = monthly
とinterval_count = 1
。
現在受け入れられている答えは私にとって大きな助けでしたが、クエリを簡素化し、パフォーマンスを向上させるいくつかの有用な変更を共有したいと思いました。
次のような定期的に繰り返されるイベントを処理するには、
Repeat every other day
または
Repeat every week on Tuesday
次のevents
ように呼ばれる2つのテーブルを作成する必要があります。
ID NAME
1 Sample Event
2 Another Event
そして、次のevents_meta
ようなテーブル:
ID event_id repeat_start repeat_interval
1 1 1369008000 604800 -- Repeats every Monday after May 20th 2013
1 1 1369008000 604800 -- Also repeats every Friday after May 20th 2013
repeat_start
時間がない(5月20日2013から1369008000に相当)と、UNIXタイムスタンプの日付であること、およびrepeat_interval
間隔の秒量(604800 7日です)。
カレンダーで毎日ループすることにより、次の簡単なクエリを使用して繰り返しイベントを取得できます。
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE (( 1299736800 - repeat_start) % repeat_interval = 0 )
カレンダーの各日付をunix-timestamp(1299736800)に置き換えてください。
モジュロ(%記号)の使用に注意してください。この記号は通常の除算に似ていますが、商の代わりに「剰余」を返します。したがって、現在の日付がrepeat_startからのrepeat_intervalの正確な倍数である場合は常に0になります。
これは、以前に提案された「meta_keys」ベースの回答よりも大幅に高速です。
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN `events_meta` EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
AND (
( CASE ( 1299132000 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1299132000 - EM1.`meta_value` )
END
) / EM2.`meta_value`
) = 1
このクエリをEXPLAINして実行すると、結合バッファを使用する必要があることがわかります。
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| 1 | SIMPLE | EM1 | ALL | NULL | NULL | NULL | NULL | 2 | Using where |
| 1 | SIMPLE | EV | eq_ref | PRIMARY | PRIMARY | 4 | bcs.EM1.event_id | 1 | |
| 1 | SIMPLE | EM2 | ALL | NULL | NULL | NULL | NULL | 2 | Using where; Using join buffer |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
上記の結合が1つのソリューションでは、このようなバッファは必要ありません。
より複雑なタイプのサポートを追加して、これらのタイプの繰り返しルールをサポートできます。
Event A repeats every month on the 3rd of the month starting on March 3, 2011
または
Event A repeats second Friday of the month starting on March 11, 2011
イベントテーブルはまったく同じに見えます。
ID NAME
1 Sample Event
2 Another Event
次に、これらの複雑なルールのサポートを追加するには、次のevents_meta
ように列を追加します。
ID event_id repeat_start repeat_interval repeat_year repeat_month repeat_day repeat_week repeat_weekday
1 1 1369008000 604800 NULL NULL NULL NULL NULL -- Repeats every Monday after May 20, 2013
1 1 1368144000 604800 NULL NULL NULL NULL NULL -- Repeats every Friday after May 10, 2013
2 2 1369008000 NULL 2013 * * 2 5 -- Repeats on Friday of the 2nd week in every month
あなたは、単に指定のいずれかに必要なことに注意してくださいrepeat_interval
かのセットをrepeat_year
、repeat_month
、repeat_day
、repeat_week
、およびrepeat_weekday
データ。
これにより、両方のタイプを同時に選択することが非常に簡単になります。毎日ループし、正しい値を入力します(2013年6月7日の場合は1370563200、次に年、月、日、週番号、および曜日)。
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )
OR (
(repeat_year = 2013 OR repeat_year = '*' )
AND
(repeat_month = 6 OR repeat_month = '*' )
AND
(repeat_day = 7 OR repeat_day = '*' )
AND
(repeat_week = 2 OR repeat_week = '*' )
AND
(repeat_weekday = 5 OR repeat_weekday = '*' )
AND repeat_start <= 1370563200
)
これは、第2週の金曜日に繰り返されるすべてのイベントと、毎週金曜日に繰り返されるすべてのイベントを返すため、イベントID 1と2の両方を返します。
ID NAME
1 Sample Event
2 Another Event
*上記のSQLの補足説明では、PHP Dateのデフォルトの平日のインデックスを使用したため、金曜日は「5」
これが元の答えが私を助けたのと同じくらい他の人を助けてくれることを願っています!
repeat_interval
列を削除して後続の列(つまりrepeat_year
、など)で表すことができます。最初の行では、2013年5月20日以降の毎週月曜日を繰り返す状況は、1をrepeat_weekday
と*
他の列に。
*
。したがって、「毎月3日」の場合は3に設定repeat_day
し、残りのrepeat
フィールドを*(repeat_interval
nullのまま)に設定して、2011年3月3日のUNIXタイムコードにrepeat_startをアンカー日付に設定します。
後にahoffnerによって洗練された、受け入れられた回答に対する小さな拡張として-タイムスタンプではなく日付形式を使用することが可能です。利点は次のとおりです。
これを行うには、DB repeat_start
を「日付」タイプとして格納されるように変更し、repeat_interval
秒ではなく日を保持するようにします。つまり、7日間の繰り返しの場合は7。
sql行を変更します。
WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )
に:
WHERE ( DATEDIFF( '2013-6-7', repeat_start ) % repeat_interval = 0)
他はすべて同じままです。シンプル!
私はこのガイドに従います:https : //github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md
また、iCal形式を使用して、ホイールを再発明せず、ルール#0を覚えておいて ください。個々の定期的なイベントインスタンスをデータベースの行として保存しないでください。
attendedEvent
をbaseInstanceId
持ちますinstanceStartDate
- そしてそれはたとえば、定期的なルールのカレンダービューを作成し、開始日を使用してその特定のインスタンスに関する情報を指定する基本イベントである場合、このエンティティも以下のようなもの持っているattendedListId
の別のテーブルへのリードをid
、attendedUserId
これに興味のあるすべての人にとって、今すぐコピーして貼り付けるだけで数分で開始できます。私はできる限りコメントでアドバイスを受けました。何か不足している場合はお知らせください。
「複雑なバージョン」:
イベント
+ ---------- + ---------------- + | ID | NAME | + ---------- + ---------------- + | 1 | サンプルイベント1 | | 2 | 2番目のイベント| | 3 | 3番目のイベント| + ---------- + ---------------- +
events_meta
+ ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- + | ID | event_id | repeat_start | repeat_interval | repeat_year | repeat_month | repeat_day | repeat_week | repeat_weekday | + ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- + | 1 | 1 | 2014-07-04 | 7 | NULL | NULL | NULL | NULL | NULL | | 2 | 2 | 2014-06-26 | NULL | 2014 | * | * | 2 | 5 | | 3 | 3 | 2014-07-04 | NULL | * | * | * | * | 5 | + ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- +
SQLコード:
CREATE TABLE IF NOT EXISTS `events` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`NAME` varchar(255) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
--
-- Dumping data for table `events`
--
INSERT INTO `events` (`ID`, `NAME`) VALUES
(1, 'Sample event'),
(2, 'Another event'),
(3, 'Third event...');
CREATE TABLE IF NOT EXISTS `events_meta` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`event_id` int(11) NOT NULL,
`repeat_start` date NOT NULL,
`repeat_interval` varchar(255) NOT NULL,
`repeat_year` varchar(255) NOT NULL,
`repeat_month` varchar(255) NOT NULL,
`repeat_day` varchar(255) NOT NULL,
`repeat_week` varchar(255) NOT NULL,
`repeat_weekday` varchar(255) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;
--
-- Dumping data for table `events_meta`
--
INSERT INTO `events_meta` (`ID`, `event_id`, `repeat_start`, `repeat_interval`, `repeat_year`, `repeat_month`, `repeat_day`, `repeat_week`, `repeat_weekday`) VALUES
(1, 1, '2014-07-04', '7', 'NULL', 'NULL', 'NULL', 'NULL', 'NULL'),
(2, 2, '2014-06-26', 'NULL', '2014', '*', '*', '2', '5'),
(3, 3, '2014-07-04', 'NULL', '*', '*', '*', '*', '1');
MySQLエクスポートとしても利用可能(アクセスを容易にするため)
PHPのサンプルコードindex.php:
<?php
require 'connect.php';
$now = strtotime("yesterday");
$pushToFirst = -11;
for($i = $pushToFirst; $i < $pushToFirst+30; $i++)
{
$now = strtotime("+".$i." day");
$year = date("Y", $now);
$month = date("m", $now);
$day = date("d", $now);
$nowString = $year . "-" . $month . "-" . $day;
$week = (int) ((date('d', $now) - 1) / 7) + 1;
$weekday = date("N", $now);
echo $nowString . "<br />";
echo $week . " " . $weekday . "<br />";
$sql = "SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE ( DATEDIFF( '$nowString', repeat_start ) % repeat_interval = 0 )
OR (
(repeat_year = $year OR repeat_year = '*' )
AND
(repeat_month = $month OR repeat_month = '*' )
AND
(repeat_day = $day OR repeat_day = '*' )
AND
(repeat_week = $week OR repeat_week = '*' )
AND
(repeat_weekday = $weekday OR repeat_weekday = '*' )
AND repeat_start <= DATE('$nowString')
)";
foreach ($dbConnect->query($sql) as $row) {
print $row['ID'] . "\t";
print $row['NAME'] . "<br />";
}
echo "<br /><br /><br />";
}
?>
PHPのサンプルコードconnect.php:
<?
// ----------------------------------------------------------------------------------------------------
// Connecting to database
// ----------------------------------------------------------------------------------------------------
// Database variables
$username = "";
$password = "";
$hostname = "";
$database = "";
// Try to connect to database and set charset to UTF8
try {
$dbConnect = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $username, $password);
$dbConnect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
// ----------------------------------------------------------------------------------------------------
// / Connecting to database
// ----------------------------------------------------------------------------------------------------
?>
また、phpコードはここにあります(読みやすくするため):
index.php
と
connect.php
これを設定すると、数分かかるはずです。時間ではありません。:)
提案されたソリューションが機能している間、私はフルカレンダーを使用して実装しようとしており、ビューごとに90以上のデータベース呼び出しが必要になります(現在、前、および次の月が読み込まれるため)。
再帰ライブラリを見つけましたhttps://github.com/tplaner/Whenデータベースにルールを保存し、1つのクエリで関連するすべてのルールをプルする場合。
私が良い解決策を見つけるために何時間も費やしたので、うまくいけば、これは他の誰かを助けるでしょう。
編集:このライブラリはPHP用です
When
すべての定期的な日付をデータベースに保存するか、すべての定期的なイベントを取得して、データベースのphp noで日付を生成する必要があります。私は正しいですか?
When
保存し、それを使用してすべての日付を生成します-これは、最初に保存された日付/ルールから読み込まれます。
Apache cronジョブと同様のメカニズムを使用しないのはなぜですか?http://en.wikipedia.org/wiki/Cron
calendar \ schedulingの場合、[ビット]に少し異なる値を使用して、標準のカレンダーの繰り返しイベントに対応します-[曜日(0-7)、月(1-12)、日(1-31)、時間(0-23)、分(0-59)]
-[年(N年ごとに繰り返す)、月(1-12)、日(1-31)、月の週(1-5)、曜日(0-7)などを使用します]
お役に立てれば。
私はこのケースのためだけに難解なプログラミング言語を開発しました。それについての最も良い部分は、それがスキーマレスでプラットフォームに依存しないことです。あなたはあなたのスケジュールのためにセレクタプログラムを書く必要があります、その構文はここで説明されているルールのセットによって制約されています-
https://github.com/tusharmath/sheql/wiki/Rules
ルールは拡張可能であり、スキーマの移行などを心配することなく、実行する繰り返しロジックの種類に基づいてあらゆる種類のカスタマイズを追加できます。
これは完全に異なるアプローチであり、独自の欠点があるかもしれません。
システムテーブルに格納されているMySQLイベントとよく似ています。構造を見て、不要な列を特定できます。
EVENT_CATALOG: NULL
EVENT_SCHEMA: myschema
EVENT_NAME: e_store_ts
DEFINER: jon@ghidora
EVENT_BODY: SQL
EVENT_DEFINITION: INSERT INTO myschema.mytable VALUES (UNIX_TIMESTAMP())
EVENT_TYPE: RECURRING
EXECUTE_AT: NULL
INTERVAL_VALUE: 5
INTERVAL_FIELD: SECOND
SQL_MODE: NULL
STARTS: 0000-00-00 00:00:00
ENDS: 0000-00-00 00:00:00
STATUS: ENABLED
ON_COMPLETION: NOT PRESERVE
CREATED: 2006-02-09 22:36:06
LAST_ALTERED: 2006-02-09 22:36:06
LAST_EXECUTED: NULL
EVENT_COMMENT:
RRULE標準は、まさにこの要件、つまり繰り返しの保存と理解のために構築されています。Microsoftとgoogleはどちらもカレンダーのイベントで使用しています。詳細については、このドキュメントをご覧ください。 https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html
@Rogue Coder
これは素晴らしい!
最後に単純にmodulo操作(mysqlではMODまたは%)を使用して、コードを単純にすることができます。
の代わりに:
AND (
( CASE ( 1299132000 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1299132000 - EM1.`meta_value` )
END
) / EM2.`meta_value`
) = 1
行う:
$current_timestamp = 1299132000 ;
AND ( ('$current_timestamp' - EM1.`meta_value` ) MOD EM2.`meta_value`) = 1")
これをさらに進めるために、永遠に再発しないイベントを含めることができます。
最後の「repeat_interval_1」の日付を示す「repeat_interval_1_end」などを追加できます。ただし、これによりクエリがより複雑になり、これを行う方法を実際に理解できません...
多分誰かが助けることができます!
あなたが与えた2つの例は非常に単純です。それらは単純な間隔として表すことができます(最初は4日、2番目は14日です)。これをどのようにモデル化するかは、再発の複雑さに完全に依存します。上記が本当に単純な場合は、開始日と繰り返し間隔の日数を保存します。
ただし、次のようなことをサポートする必要がある場合
イベントAは、2011年3月3日から毎月3日に繰り返されます
または
イベントAは、2011年3月11日から始まる月の第2金曜日に繰り返されます
次に、それははるかに複雑なパターンです。
1299132000
ハードコーディングされているのですか?指定された終了日の発生日とユーザーを取得する必要がある場合、これはどうなりますか?