กิจกรรมที่เกิดซ้ำ / ทำซ้ำปฏิทิน - วิธีการเก็บข้อมูลที่ดีที่สุด


311

ฉันกำลังสร้างระบบเหตุการณ์ที่กำหนดเองและหากคุณมีเหตุการณ์ซ้ำที่มีลักษณะเช่นนี้:

กิจกรรม A ซ้ำทุก 4 วันเริ่มตั้งแต่ 3 มีนาคม 2554

หรือ

กิจกรรม B ซ้ำทุก 2 สัปดาห์ในวันอังคารเริ่มตั้งแต่วันที่ 1 มีนาคม 2011

ฉันจะเก็บสิ่งนั้นไว้ในฐานข้อมูลในลักษณะที่จะทำให้ง่ายต่อการค้นหา ฉันไม่ต้องการปัญหาเกี่ยวกับประสิทธิภาพหากมีเหตุการณ์จำนวนมากและฉันต้องผ่านแต่ละเหตุการณ์เมื่อเรนเดอร์ปฏิทิน


คุณช่วยอธิบาย1299132000 ได้ไหมว่าทำไมฮาร์ดโค้ดมาถึง จะทำอย่างไรถ้าฉันต้องการรับวันเกิดและผู้ใช้สำหรับวันที่สิ้นสุดที่กำหนด
Murali Murugesan

@Murali Boy นี่เก่า แต่ฉันค่อนข้างมั่นใจว่า 1299132000 น่าจะเป็นวันที่ปัจจุบัน
Brandon Wamboldt

@ BrandonWamboldt ฉันลองความคิดของคุณกับ SQL Server stackoverflow.com/questions/20286332/display-next-event-date ฉันต้องการค้นหารายการถัดไปทั้งหมดเช่นc # version
Billa

คำตอบ:


211

การจัดเก็บรูปแบบการทำซ้ำ "ง่าย"

สำหรับปฏิทินที่ใช้ PHP / MySQL ของฉันฉันต้องการจัดเก็บข้อมูลเหตุการณ์ที่เกิดซ้ำ / ซ้ำ ๆ อย่างมีประสิทธิภาพเท่าที่จะเป็นไปได้ ฉันไม่ต้องการมีแถวจำนวนมากและฉันต้องการค้นหากิจกรรมทั้งหมดที่จะเกิดขึ้นในวันที่ระบุ

วิธีการดังต่อไปนี้เป็นที่ดีในการจัดเก็บข้อมูลที่เกิดขึ้นในช่วงเวลาปกติเช่นทุกวันทำซ้ำทุกnวันทุกสัปดาห์ทุกเดือนทุกปี ฯลฯ เป็นต้นซึ่งรวมถึงทุกวันอังคารและวันพฤหัสบดีพิมพ์รูปแบบเช่นกันเพราะพวกเขาจะถูกเก็บไว้ แยกเป็นทุกสัปดาห์เริ่มในวันอังคารและทุกสัปดาห์เริ่มในวันพฤหัสบดี

สมมติว่าฉันมีสองตารางหนึ่งเรียกว่า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 timestamp และ repeat_interval เป็นวินาทีในช่วงเวลาระหว่างช่วงเวลา (432000 คือ 5 วัน)

repeat_interval_1 จะไปพร้อมกับ repeat_start ของ ID 1 ดังนั้นถ้าฉันมีเหตุการณ์ที่เกิดขึ้นซ้ำทุกวันอังคารและทุกวันพฤหัสบดี, 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

การรวมกับวิธีการด้านบนสามารถรวมกันเพื่อครอบคลุมรูปแบบเหตุการณ์ที่เกิดซ้ำ / ซ้ำ ๆ หากฉันไม่ได้รับอะไรเลยโปรดแสดงความคิดเห็น


1
ฉันพยายามจัดเก็บรูปแบบการทำซ้ำ "แบบง่าย" ของคุณ หากฉันต้องการให้ทำซ้ำทุกสัปดาห์ในวันอังคารฉันต้องแก้ไข repeat_start หรือสร้างระเบียนใหม่ด้วยวันที่ล่าสุด หรือมีวิธีให้ทำซ้ำทุกสัปดาห์โดยใช้ repeat_start แรก ???
loo

1
คำตอบของคุณคือความช่วยเหลือที่ยอดเยี่ยม @roguecoder แต่มันก็ไม่ได้ผลมากนัก ... ฉันพบคำตอบของฉันหลังจากโพสต์นี้: stackoverflow.com/questions/10545869/ …
เบ็นซินแคลร์

1
ในAND ( ( CASE ( 1299132000 - EM1.meta_value ) WHEN 0 THEN 1 ELSE ( 1299132000 - EM1.meta_value) END ) / EM2.meta_value ) = 1นี้คือ/ EM2.meta_valueวางผิด?
Murali Murugesan

1
นี่คือความช่วยเหลือที่ดี คุณจะแนะนำให้เข้าถึงข้อมูลเหล่านี้เป็นบันทึกส่วนตัวอย่างไรถ้าคุณต้องการมีความคิดเห็นหรือเช็คอินในแต่ละเหตุการณ์
johnrees

26
เป็นที่น่าสังเกตว่าคุณไม่ควรใช้ค่าที่เข้ารหัสแบบฮาร์ทสำหรับช่วงเวลาซ้ำ ๆ เช่น86400วินาทีในหนึ่งวันเพราะมันไม่ได้คำนึงถึงเวลาออมแสง มันเป็นเรื่องที่เหมาะสมมากขึ้นในการคำนวณสิ่งเหล่านี้แบบไดนามิกในการบินและแทนที่จะเก็บinterval = dailyและinterval_count = 1หรือและinterval = monthly interval_count = 1
Corey Ballou

185

ในขณะที่คำตอบที่ได้รับการยอมรับในขณะนี้เป็นสิ่งที่ช่วยฉันได้มากฉันต้องการแบ่งปันการดัดแปลงที่มีประโยชน์ซึ่งทำให้การสืบค้นง่ายขึ้นและเพิ่มประสิทธิภาพการทำงาน


ทำซ้ำเหตุการณ์ "ธรรมดา"

เมื่อต้องการจัดการเหตุการณ์ที่เกิดขึ้นในช่วงเวลาปกติเช่น:

Repeat every other day 

หรือ

Repeat every week on Tuesday 

คุณควรสร้างสองตารางหนึ่งที่เรียกว่าevents:

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การเป็นวันที่ประทับเวลา unix โดยไม่มีเวลา (1369008000 ตรงกับวันที่ 20 พฤษภาคม 2013) และ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 )

เพียงแทนที่ในยูนิกซ์ - เวลา (1299736800) สำหรับแต่ละวันในปฏิทินของคุณ

สังเกตการใช้งานโมดูโล (เครื่องหมาย%) สัญลักษณ์นี้เป็นเหมือนส่วนปกติ แต่ส่งกลับ '' เศษเหลือ '' แทนความฉลาดและเป็น 0 เมื่อใดก็ตามที่วันที่ปัจจุบันเป็นพหุคูณที่แน่นอนของ repeat_interval จาก repeat_start

การเปรียบเทียบประสิทธิภาพ

สิ่งนี้เร็วกว่าคำตอบ "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

หากคุณเรียกใช้อธิบายแบบสอบถามนี้คุณจะทราบว่าจำเป็นต้องใช้บัฟเฟอร์การเข้าร่วม:

+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| 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ข้อมูล

ทำให้การเลือกทั้งสองประเภทพร้อมกันนั้นง่ายมาก เพียงวนซ้ำในแต่ละวันและกรอกค่าที่ถูกต้อง (1370563200 สำหรับวันที่ 7 มิถุนายน 2013 จากนั้นปีเดือนวันสัปดาห์และวันทำงานดังต่อไปนี้):

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 รวมถึงกิจกรรมใด ๆ ที่ทำซ้ำทุกวันศุกร์ดังนั้นจึงส่งคืนทั้งรหัสเหตุการณ์ 1 และ 2:

ID    NAME
1     Sample Event
2     Another Event

* Sidenote ใน SQL ข้างต้นฉันใช้ดัชนีวันธรรมดาเริ่มต้นของ PHP Dateดังนั้น "5" สำหรับวันศุกร์


หวังว่านี่จะช่วยผู้อื่นได้มากเท่ากับคำตอบดั้งเดิมที่ช่วยฉัน!


6
นี่มันสุดยอดมากขอบคุณ! คุณมีความคิดใด ๆ ว่าคุณเข้ารหัส "ทุก 2 เดือนในวันจันทร์แรก" หรือ "ทุก ๆ 3 เดือนในวันจันทร์แรก" เป็นต้น
Jordan Lev

6
ฉันเห็นว่ามันน่าทึ่ง ฉันวิ่งเข้าไปในภาวะวิกฤติเช่นเดียวกับที่ Jordan Lev ทำได้ ฟิลด์ repeat_interval ไม่ดีสำหรับการทำซ้ำหลายเดือนเพราะบางเดือนมีความยาวมากกว่าเดือนอื่น ๆ นอกจากนี้คุณจะ จำกัด ระยะเวลาของกิจกรรมที่เกิดซ้ำได้อย่างไร คือทุก 2 เดือนในวันจันทร์แรกเป็นเวลา 8 เดือน ตารางควรมีวันที่สิ้นสุดบางอย่าง
Abinadi

11
นี่คือคำตอบที่ยอดเยี่ยม ฉันทำวันที่ซ้ำและเพิ่มวันที่ซ้ำแล้วซ้ำอีก แต่คำตอบนี้ช่วยได้อย่างมาก
เลนคอลลินส์

3
เคล็ดลับ: สำหรับรูปแบบที่ซับซ้อนใครสามารถกำจัดrepeat_intervalคอลัมน์และแสดงในคอลัมน์ที่ตามมา (เช่นrepeat_yearฯลฯ ) สำหรับแถวแรกสถานการณ์ของการทำซ้ำทุกวันจันทร์หลังจากวันที่ 20 พฤษภาคม 2013 สามารถแสดงโดยวาง 1 ในrepeat_weekdayและ*ในคอลัมน์อื่น ๆ
musubi

3
@OlivierMATROT @milos *ความคิดคือการตั้งค่าเขตข้อมูลที่คุณต้องการที่จะได้รับการแก้ไขอย่างชัดเจนและส่วนที่เหลือจะตัวแทน ดังนั้นสำหรับ "ทุกเดือนในวันที่ 3" คุณเพิ่งตั้งค่าrepeat_dayเป็น 3 ส่วนที่เหลือของrepeatฟิลด์เป็น * (ปล่อยrepeat_intervalว่างไว้) และตั้งค่า repeat_start เป็น unix timecode สำหรับ 3 มีนาคม 2011 เพื่อเป็นวันที่ยึดเหนี่ยวของคุณ
ahoffner

28

การปรับปรุง: แทนที่การประทับเวลาด้วยวันที่

เพื่อเป็นการปรับปรุงเล็กน้อยสำหรับคำตอบที่ยอมรับซึ่งได้รับการปรับปรุงโดย ahoffner ในภายหลัง - เป็นไปได้ที่จะใช้รูปแบบวันที่แทนการประทับเวลา ข้อดีคือ:

  1. วันที่อ่านได้ในฐานข้อมูล
  2. ไม่มีปัญหากับปี> 2038 และการประทับเวลา
  3. การลบจำเป็นต้องระวังด้วยการประทับเวลาที่ยึดตามวันที่ปรับตามฤดูกาลเช่นในสหราชอาณาจักร 28 มิถุนายนเริ่มหนึ่งชั่วโมงก่อนหน้านี้กว่า 28 ธันวาคมดังนั้นการได้รับการประทับเวลาจากวันที่สามารถทำลายอัลกอริทึมการเรียกซ้ำ

ในการทำเช่นนี้ให้เปลี่ยนฐานข้อมูลrepeat_startที่จะจัดเก็บเป็นประเภท 'วันที่' และrepeat_intervalตอนนี้ถือวันแทนที่จะเป็นวินาที ie 7 สำหรับการทำซ้ำ 7 วัน

เปลี่ยนสาย sql:

WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )

ถึง:

WHERE ( DATEDIFF( '2013-6-7', repeat_start ) % repeat_interval = 0)

ทุกอย่างอื่นยังคงเหมือนเดิม Simples!


แล้วถ้าฉันต้องการให้งานของฉันซ้ำทุกปี repeat_interval ควรเก็บ 365 วันหรือไม่ เกิดอะไรขึ้นถ้าพวกเขาปีมี 366 วัน?
TGeorge

3
@ George02 หากเหตุการณ์เป็นรายปีคุณปล่อยให้ repeat_interval NULL และ repeat_year เป็น * จากนั้นขึ้นอยู่กับว่าคุณสามารถตั้งค่าซ้ำเป็นซ้ำและเดือนที่ซ้ำเช่นวันที่ 11 มีนาคมหรือ repeat_month
jerrygarciuh

25

ฉันจะทำตามคำแนะนำนี้: https://github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md

นอกจากนี้ตรวจสอบให้แน่ใจว่าคุณใช้รูปแบบ iCal เพื่อไม่ให้นำกลับมาใช้ใหม่และจำกฎ # 0: อย่าเก็บอินสแตนซ์ของเหตุการณ์ที่เกิดซ้ำเป็นแถวในฐานข้อมูลของคุณ!


2
คุณจะจำลองผู้ใช้การติดตามที่ได้เข้าร่วมอินสแตนซ์ที่เฉพาะเจาะจงอย่างไร มันสมเหตุสมผลไหมที่จะแยกจาก Rule # 0 ในกรณีนี้?
Danny Sullivan

2
@DannySullivan จากด้านบนของหัวของฉันฉันจะมีเอนทิตีอื่นattendedEventด้วยbaseInstanceIdและinstanceStartDate- นั่นคือตัวอย่างเหตุการณ์พื้นฐานที่คุณสร้างมุมมองปฏิทินกฎการเกิดซ้ำและใช้วันที่เริ่มต้นเพื่อระบุข้อมูลในอินสแตนซ์เฉพาะนั้น มีสิ่งที่ต้องการattendedListIdซึ่งจะนำไปสู่โต๊ะของผู้อื่นid,attendedUserId
Gal Bracha

@DannySullivan ฉันรู้ว่ามันเป็นเวลานานแล้วตั้งแต่ที่คุณถาม แต่นอกเหนือจากความคิดเห็นก่อนหน้าคุณสามารถค้นหาแบบย้อนกลับเพื่อดูว่าผู้ใช้นั้นเป็นส่วนหนึ่งของรูปแบบการเกิดซ้ำของเหตุการณ์หรือไม่ นั่นจะบอกคุณว่าพวกเขามีกำหนดการอย่างน้อยสำหรับกิจกรรม ไม่ว่าพวกเขาจะเข้าร่วมจริงหรือไม่เป็นเรื่องที่แตกต่างกันซึ่งจะมีมากขึ้นตามแนวความคิดเห็นของ DannySullivan
BRogers

24

สำหรับคุณทุกคนที่มีความสนใจในตอนนี้คุณสามารถคัดลอกและวางเพื่อเริ่มต้นภายในไม่กี่นาที ฉันได้รับคำแนะนำในความคิดเห็นเท่าที่จะทำได้ แจ้งให้เราทราบหากฉันทำอะไรหาย

"รุ่นที่ซับซ้อน":

เหตุการณ์ที่เกิดขึ้น

+ + ---------- ---------------- +
| ID | NAME |
+ + ---------- ---------------- +
| 1 | ตัวอย่างเหตุการณ์ 1 |
| 2 | เหตุการณ์ที่สอง
| 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 export (เพื่อให้เข้าถึงได้ง่าย)

โค้ดตัวอย่าง 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
การตั้งค่านี้ควรใช้เวลาไม่กี่นาที ไม่ใช่ชั่วโมง :)


2
ฉันจะสอบถามเพื่อให้ได้รับกิจกรรมที่ทำซ้ำทั้งหมดภายในช่วงวันที่ได้นั่นคือเพื่อรับกิจกรรมที่ทำซ้ำทั้งหมดระหว่าง 2014-10-01 ถึง 2014-12-30 ขอบคุณสำหรับโพสต์ของคุณ
Well Wisher

@Wellwisher - ทำซ้ำ ... จนกว่าจะถึงเวลาและstackoverflow.com/questions/34407833//
แบรดเคนต์

1
@Alex ฉันจะลบหนึ่งอินสแตนซ์ที่เกิดขึ้นจากเหตุการณ์ซ้ำได้อย่างไร
Pugazhenthi

1
ฉันรู้ว่านี่เป็นเธรดเก่า แต่ทำไมประเภท varchar ในคอลัมน์ repeat_ * คุณไม่สามารถใช้จำนวนเต็มและค่าลบแทน '*'
Olivier MATROT

1
ขอบคุณสำหรับรหัส อย่างไรก็ตามฉันต้องสังเกตว่าการติดตั้ง db / query ของคุณค่อนข้างน่ารำคาญและไม่มีประสิทธิภาพมาก ตัวอย่างเช่นเหตุใดจึงใช้ varchar (255) สำหรับคอลัมน์อย่างง่าย (ตามที่ @OlivierMATROT พูดถึงคุณสามารถใช้จำนวนเต็มและแม้ว่าจะไม่ใช่ทำไมถึง 255?) และถ้าคุณทำแบบสอบถามซ้ำ 30 ครั้งทำไมไม่ใช้คำสั่งหรือขั้นตอน? เพียงแค่พูดเพื่อประโยชน์ถ้ามีใครกำลังจะใช้สิ่งนี้
Rony

15

ในขณะที่โซลูชันที่นำเสนอทำงานฉันพยายามใช้กับปฏิทินแบบเต็มและจะต้องใช้การเรียกฐานข้อมูลมากกว่า 90 ครั้งสำหรับแต่ละมุมมอง (เนื่องจากโหลดปัจจุบันเดือนก่อนหน้าและเดือนถัดไป) ซึ่งฉันไม่ได้ตื่นเต้นเกินไป

ฉันพบคลังข้อมูลเรียกซ้ำhttps://github.com/tplaner/เมื่อคุณเก็บกฎไว้ในฐานข้อมูลและค้นหาหนึ่งรายการเพื่อดึงกฎที่เกี่ยวข้องทั้งหมด

หวังว่านี่จะช่วยคนอื่นได้เพราะฉันใช้เวลาหลายชั่วโมงในการหาทางออกที่ดี

แก้ไข: ห้องสมุดนี้มีไว้สำหรับ PHP


ฉันต้องการใช้ปฏิทินแบบเต็ม ห้องสมุดจะช่วยฉันได้อย่างไร วิธีการแยกกิจกรรม propoer?
piernik

@piernik - ฉันจะตั้งค่าไลบรารีเช่นเดียวกับในเอกสารประกอบและหากคุณพบปัญหาเฉพาะให้เปิดคำถามใหม่ใน stackoverflow ด้วยรหัสที่คุณได้ติดตั้งและปัญหาที่คุณมี ฉันแน่ใจว่าถ้าคุณใช้ความพยายามอย่างมากในสมาชิกบางคนจะช่วยคุณได้
Tim Ramsey

ฉันหมายถึงการใช้Whenคุณต้องเก็บวันที่ reccuring ทั้งหมดในฐานข้อมูลหรือรับ reccuring เหตุการณ์ทั้งหมดและสร้างวันที่ใน php no ในฐานข้อมูล ฉันถูกไหม?
piernik

@piernik คุณจะเก็บวันที่เริ่มต้นและกฎ / s ในฐานข้อมูลและใช้Whenในการสร้างวันที่ทั้งหมด - ซึ่งจะถูกบรรจุจากวันที่ / กฎเริ่มต้นที่เก็บไว้
Tim Ramsey

มันไม่ดีเช่นกัน - คุณไม่สามารถใช้คำสั่ง mysql ในการแก้ไขเหตุการณ์ได้ - คุณต้องใช้ PHP ในการทำเช่นนั้น ขอบคุณอยู่แล้ว
piernik

14

ทำไมไม่ใช้กลไกที่คล้ายกับงาน Apache cron http://en.wikipedia.org/wiki/Cron

สำหรับปฏิทิน \ scheduling ฉันจะใช้ค่าที่แตกต่างกันเล็กน้อยสำหรับ "บิต" เพื่อรองรับเหตุการณ์ reoccurence ปฏิทินมาตรฐาน - แทน [วันของสัปดาห์ (0 - 7), เดือน (1 - 12), วันที่ของเดือน (1 - 31), ชั่วโมง (0 - 23), ขั้นต่ำ (0 - 59)]

- ฉันจะใช้บางอย่างเช่น [ปี (ทำซ้ำทุก ๆ ปี N) เดือน (1 - 12) วันเดือน (1 - 31) สัปดาห์ของเดือน (1-5) วันของสัปดาห์ (0 - 7) ]

หวังว่านี่จะช่วยได้


6
ฉันคิดว่ามันเป็นตัวเลือกวันในสัปดาห์ที่มากเกินไป ดูเหมือนว่าทั้ง 1-7 หรือ 0-6 นั้นแม่นยำยิ่งขึ้น
Abinadi

2
มันดีที่จะใช้ cron ในการจัดเก็บซ้ำ แต่ปัญหาคือการค้นหายากมาก
Stony

@Vladimir คุณเก็บเป็นอย่างไรทุกสองวันอังคาร (ทุกสองสัปดาห์ในวันอังคาร)
julestruong

@julestruong เว็บไซต์นี้ดูเหมือนว่าจะมีคำตอบ: coderwall.com/p/yzzu5a/running-a-cron-job-every-other-week
Ashton Wiersdorf

cron มีการแสดงออกที่ จำกัด เนื่องจากมันไร้สัญชาติ (เพียงเปรียบเทียบวันที่ปัจจุบัน / วันที่ / เวลาที่มีรูปแบบ) ซึ่งไม่สามารถแสดงรูปแบบธุรกิจ / มนุษย์ทั่วไปบางอย่างเช่น "ทุก ๆ สามวัน" หรือ "ทุก 7 ชั่วโมง" ซึ่ง ต้องมีการจดจำเหตุการณ์ล่าสุด เรื่องนี้ไม่ชัดเจน คุณอาจคิดว่าคุณเพิ่งพูดวัน / 3 หรือชั่วโมง / 7 ใน crontab แต่เมื่อสิ้นเดือน / วันคุณมี "เหลือ" วัน / ชั่วโมงซึ่งน้อยกว่า 3 หรือ 7; ด้วยผลลัพธ์ที่เป็นหายนะ
Jaime Guerrero

5

ฉันพัฒนาภาษาการเขียนโปรแกรมลึกลับสำหรับกรณีนี้ ส่วนที่ดีที่สุดเกี่ยวกับมันคือมันเป็นคีน้อยและแพลตฟอร์มอิสระ คุณเพียงแค่ต้องเขียนโปรแกรมตัวเลือกสำหรับกำหนดการของคุณไวยากรณ์ซึ่งถูก จำกัด โดยชุดของกฎที่อธิบายไว้ที่นี่ -

https://github.com/tusharmath/sheql/wiki/Rules

กฎสามารถขยายได้และคุณสามารถเพิ่มการปรับแต่งใด ๆ ตามชนิดของตรรกะการทำซ้ำที่คุณต้องการดำเนินการโดยไม่ต้องกังวลเกี่ยวกับการย้ายสคีมา ฯลฯ

นี่เป็นวิธีที่แตกต่างอย่างสิ้นเชิงและอาจมีข้อเสียของตัวเอง


4

ฟังดูคล้ายกับเหตุการณ์ 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:

4

มาตรฐาน RRULE ถูกสร้างขึ้นเพื่อตรงตามข้อกำหนดนี้เช่นการบันทึกและการทำความเข้าใจการเกิดซ้ำ Microsoft และ google ทั้งคู่ใช้มันในกิจกรรมในปฏิทิน โปรดอ่านรายละเอียดเพิ่มเติมจากเอกสารนี้ https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html


3

@Rogue Coder

มันเยี่ยมมาก!

คุณสามารถใช้การดำเนินการ modulo (MOD หรือ% ใน mysql) เพื่อทำให้โค้ดของคุณง่ายขึ้นในตอนท้าย:

แทน:

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_end" เพื่อแสดงถึงวันที่ที่สามารถเพิ่ม "repeat_interval_1" ล่าสุดได้ อย่างไรก็ตามนี่ทำให้คิวรีมีความซับซ้อนมากขึ้นและฉันก็ไม่สามารถหาวิธีที่จะทำ ...

อาจมีคนช่วยได้!


1

ตัวอย่างสองตัวอย่างที่คุณให้นั้นง่ายมาก พวกเขาสามารถแสดงเป็นช่วงเวลาที่เรียบง่าย (คนแรกเป็นสี่วันที่สองคือ 14 วัน) วิธีที่คุณสร้างแบบจำลองนี้จะขึ้นอยู่กับความซับซ้อนของการเกิดซ้ำของคุณทั้งหมด หากสิ่งที่คุณมีข้างต้นนั้นง่ายมากให้เก็บวันที่เริ่มต้นและจำนวนวันในช่วงการทำซ้ำ

อย่างไรก็ตามหากคุณจำเป็นต้องสนับสนุนสิ่งต่าง ๆ เช่น

กิจกรรม A ซ้ำทุกเดือนในวันที่ 3 ของเดือนเริ่มตั้งแต่วันที่ 3 มีนาคม 2011

หรือ

กิจกรรม A จะเกิดขึ้นในวันศุกร์ที่สองของเดือนนับตั้งแต่วันที่ 11 มีนาคม 2554

จากนั้นเป็นรูปแบบที่ซับซ้อนมากขึ้น


1
ฉันได้เพิ่มกฎที่ซับซ้อนยิ่งขึ้นที่คุณเพิ่งระบุไว้ในภายหลัง แต่ไม่ใช่ตอนนี้ ฉันจะสร้างแบบจำลองแบบสอบถาม SQL เพื่อรับเหตุการณ์ในวันที่ 7 มีนาคม 2011 เพื่อให้เกิดเหตุการณ์ที่เกิดขึ้นซ้ำได้อย่างไร
Brandon Wamboldt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.