重复事件、SQL 查询

Posted

技术标签:

【中文标题】重复事件、SQL 查询【英文标题】:Recurring Events, SQL Query 【发布时间】:2012-03-05 21:02:41 【问题描述】:

我知道有关定期事件的问题很常见,但除了与日历应用程序有关的问题之外,我还没有找到一个可以回答这个关于定期事件的特定问题的答案。主要区别在于我们应用程序中的事件。只会在报告中或单独出现,而不是在日历格式中,尽管在许多方面它们非常相似,可能只是与日历相关的包更少。

类似于日历应用程序。事件可以一次性发生,也可以重复发生。每个星期四或每个月的第一个星期一,直到将来某个预先设定的时间。

事件存储在一个事件表中,其中包含开始日期和结束日期以及“循环类型 ID”。如果“recurrency type”为“None”,则开始日期和结束日期将相同。事件表将 id 保存到一个单独的表中,该表保存事件类型名称,例如“会议”或“每周报告”

还有一个包含“循环类型”列表的表格,例如“无重复”、“每周一”、“每月的第一个星期一”和“每月的最后一个星期六”。

为了便于查找,另一个表包含从 1960 年到 2060 年的日期列表以及每个日期的相关信息,例如是否是星期一,以及该月中的哪个星期一。

这允许进行如下查找:

SELECT DISTINCT(e.eventid),n.nameid,n.firstname,n.lastname,d.dt,r.recurring
FROM dates d
LEFT JOIN recurringtypes r
/* if event recurring every week E.g. 'Every Monday' */
ON (r.rectypeid BETWEEN 2 AND 8 AND r.day = d.dow)
/* if event recurring every month E.g. 'First Monday, every month' */
OR ((r.rectypeid BETWEEN 9 AND 36) AND r.day = d.dow AND r.occurrence = d.occurrence)
/* if event recurring every last week of month E.g. 'Last Monday, every month' */
OR (r.rectypeid >= 37 AND r.day = d.dow and r.islast = d.islast)
LEFT JOIN events e on e.rectypeid = r.rectypeid
LEFT JOIN eventtypes t ON e.eventtypeid = t.eventtypeid
LEFT JOIN names n ON e.namesid = n.namesid
WHERE (d.dt BETWEEN '2012/02/01' AND '2012/05/01')
ORDER BY d.dt;

这正是查找重复事件所需要的,输出如下:

+-----------+---------------+-------------------+-----------+------------+-------------------------------+
| eventid   | nameid        | lastname          | firstname | dt         | recurring                     |
+-----------+---------------+-------------------+-----------+------------+-------------------------------+
|   3291788 |       1728449 | smith             | zoe       | 2012-02-02 | First Thursday, every month   |
|   3291797 |       1765432 |                   |           | 2012-02-05 | First Sunday, every month     |
|   3291798 |       1730147 |                   |           | 2012-02-05 | First Sunday, every month     |
|   3291803 |       1790061 | Carpenter         | Richie    | 2012-02-06 | Every Monday                  |
|   3291805 |       1790061 | Carpenter         | Richie    | 2012-02-08 | Second Wednesday, every month |
|   3291803 |       1790061 | Carpenter         | Richie    | 2012-02-13 | Every Monday                  |
|   3291799 |       1790061 | Carpenter         | Richie    | 2012-02-15 | Third Wednesday, every month  |
|   3291803 |       1790061 | Carpenter         | Richie    | 2012-02-20 | Every Monday                  |

要获得无重复事件,可以使用更简单的查询:

SELECT n.nameid,n.lastname,n.firstname,e.firstdate,e.eventid,'No' as Recurring
FROM events e
LEFT JOIN names n ON n.names = e.namesid
AND e.rectypeid <= 1
AND e.firstdate BETWEEN '2012/02/01' AND '2012/05/01'
AND e.eventid IS NOT NULL ORDER BY e.firstdate;
这给出的输出与第一个查询非常相似,但至关重要的是,日期来自事件表而不是日期表。

我的问题是:如何将这些查询组合起来,得出一个包含所有事件的列表,包括按日期顺序重复和不重复的事件?


这些是表格和它们的缩短选择,为简洁起见,一些列和所有索引已被删除:)。出于同样的原因,未包含“名称”表。

CREATE TABLE events (
eventid int(11) NOT NULL AUTO_INCREMENT,
eventtypeid int(11) DEFAULT '0',
firstdate date DEFAULT '1960-01-01' COMMENT 'First event',
lastdate date DEFAULT '1960-01-01' COMMENT 'Last event',
rectypeid int(11) DEFAULT '1'
);
+---------+-------------+------------+------------+-----------+
| eventid | eventtypeid | firstdate  | lastdate   | rectypeid |
+---------+-------------+------------+------------+-----------+
| 3291803 |          16 | 2012-02-03 | 2012-04-11 |         3 |
| 3291797 |           8 | 2012-02-12 | 2012-02-22 |         9 |
| 3291798 |           5 | 2012-02-12 | 2012-02-12 |         9 |
| 3291788 |           8 | 2012-05-24 | 2015-01-16 |        13 |
| 3291805 |          10 | 2012-01-04 | 2012-02-14 |        19 |
| 3291799 |          16 | 2012-02-07 | 2012-10-24 |        26 |
| 3291804 |           5 | 2012-02-03 | 2012-08-22 |        41 |
+---------+-------------+------------+------------+-----------+
CREATE TABLE cmseventtypes (
eventtypeid int(11) NOT NULL AUTO_INCREMENT,
eventtype varchar(50) DEFAULT '' COMMENT 'Event type AKA name'
);
+-------------+----------------------+
| eventtypeid | eventype             |
+-------------+----------------------+
|           1 | Follow up meeting    |
|           2 | Reminder email due   |
|           3 | Monthly meeting      |
|           4 | Weekly report        |
|           5 | Golf practice        |
+------------------------------------+
CREATE TABLE recurringtypes (
rectypeid int(11) NOT NULL AUTO_INCREMENT,
recurring varchar(40) DEFAULT '',
day tinyint(4) DEFAULT '0',
occurrence tinyint(4) DEFAULT '0',
islast tinyint(4) DEFAULT '0'
);
+-----------+---------------------------+------+------------+--------+
| rectypeid | recurring                 | day  | occurrence | islast |
+-----------+---------------------------+------+------------+--------+
|         1 | No                        |    0 |          0 |      0 |
|         2 | Every Sunday              |    1 |          0 |      0 |
|         3 | Every Monday              |    2 |          0 |      0 |
|         4 | Every Tuesday             |    3 |          0 |      0 |
|         5 | Every Wednesday           |    4 |          0 |      0 |
|         6 | Every Thursday            |    5 |          0 |      0 |
|         7 | Every Friday              |    6 |          0 |      0 |
|         8 | Every Saturday            |    7 |          0 |      0 |
|         9 | First Sunday, every month |    1 |          1 |      0 |
|        10 | First Monday, every month |    2 |          1 |      0 |
+-----------+---------------------------+------+------------+--------+
CREATE TABLE dates (
dt date NOT NULL COMMENT 'Date',
daycount mediumint(9) NOT NULL DEFAULT '1',
year smallint(6) NOT NULL DEFAULT '1970',
month tinyint(4) NOT NULL DEFAULT '1',
dom tinyint(4) NOT NULL DEFAULT '1',
dow tinyint(4) NOT NULL DEFAULT '1',
occurrence tinyint(4) NOT NULL DEFAULT '0',
islast tinyint(1) NOT NULL DEFAULT '0'
);
+------------+----------+------+-------+-----+-----+------------+--------+
| dt         | daycount | year | month | dom | dow | occurrence | islast |
+------------+----------+------+-------+-----+-----+------------+--------+
| 2012-02-02 |   734900 | 2012 |     2 |   2 |   5 |          1 |      0 |
| 2012-02-03 |   734901 | 2012 |     2 |   3 |   6 |          1 |      0 |
| 2012-02-04 |   734902 | 2012 |     2 |   4 |   7 |          1 |      0 |
| 2012-02-05 |   734903 | 2012 |     2 |   5 |   1 |          1 |      0 |
| 2012-02-06 |   734904 | 2012 |     2 |   6 |   2 |          1 |      0 |
| 2012-02-07 |   734905 | 2012 |     2 |   7 |   3 |          1 |      0 |
| 2012-02-08 |   734906 | 2012 |     2 |   8 |   4 |          2 |      0 |
| 2012-02-09 |   734907 | 2012 |     2 |   9 |   5 |          2 |      0 |
+------------+----------+------+-------+-----+-----+------------+--------+


我们并非绝对使用上述代码或表格布局,欢迎任何可行的解决方案。请不要将我指向:

How would you store possibly recurring times?

What's the best way to model recurring events in a calendar application?

Should I store dates or recurrence rules in my database when building a calendar app?

https://www.rfc-editor.org/rfc/rfc5545

我已经检查了它们,它们非常有用,但没有达到我们的预期。

TIA

【问题讨论】:

islast 是做什么的?还是occurencedates 表中? 如果设置 'islast' 标记一个月中最后一天的出现(例如 ' 一个月中的最后一个星期一),则发生是一个月中一天的出现次数(例如 'first Monday in月', '一个月的第二个星期一') 【参考方案1】:

除非我遗漏了什么,否则答案非常简单。我没有意识到 UNION 可以通过使用别名对公共列进行排序,即使这些列来自不同的表。所以完整的查询将是:

SELECT DISTINCT(e.eventid),n.nameid,n.firstname,n.lastname,d.dt AS dait,r.recurring
FROM dates d 
LEFT JOIN recurringtypes r
/* if event recurring every week E.g. 'Every Monday' */
ON (r.rectypeid BETWEEN 2 AND 8 AND r.day = d.dow) 
/* if event recurring every month E.g. 'First Monday, every month' */
OR ((r.rectypeid BETWEEN 9 AND 36) AND r.day = d.dow AND r.occurrence = d.occurrence) 
/* if event recurring every last week of month E.g. 'Last Monday, every month' */
OR (r.rectypeid >= 37 AND r.day = d.dow and r.islast = d.islast)
LEFT JOIN events e on e.rectypeid = r.rectypeid
LEFT JOIN eventtypes t ON e.eventtypeid = t.eventtypeid
LEFT JOIN names n ON e.namesid = n.namesid
WHERE (d.dt BETWEEN '2012/02/01' AND '2012/05/01')
UNION
SELECT e.eventid,n.nameid,n.lastname,n.firstname,e.firstdate AS dait,'No' as Recurring
FROM events e
LEFT JOIN names n ON n.names = e.namesid 
AND e.rectypeid <= 1 
WHERE e.firstdate BETWEEN '2012/02/01' AND '2012/05/01' 
ORDER BY dait;

有人指出,使用表格来查找日期是有风险的,因为日期最终会用完,这是真的,但是计算日期是否是一个月中的第一个星期一(或第二个星期一) ,或者第四个,或者可能是第四个和最后一个),似乎比我现在想要的更复杂的 SQL 代码。

【讨论】:

【参考方案2】:
SELECT DISTINCT(e.eventid),n.nameid,n.firstname,n.lastname,d.dt,r.recurring
FROM dates d 
LEFT JOIN recurringtypes r
/* if event recurring every week E.g. 'Every Monday' */
ON (r.rectypeid BETWEEN 2 AND 8 AND r.day = d.dow) 
/* if event recurring every month E.g. 'First Monday, every month' */
OR ((r.rectypeid BETWEEN 9 AND 36) AND r.day = d.dow AND r.occurrence = d.occurrence) 
/* if event recurring every last week of month E.g. 'Last Monday, every month' */
OR (r.rectypeid >= 37 AND r.day = d.dow and r.islast = d.islast)
LEFT JOIN events e on e.rectypeid = r.rectypeid OR (e.rectypeid <= 1 AND e.eventid IS NOT NULL) 
LEFT JOIN eventtypes t ON e.eventtypeid = t.eventtypeid
LEFT JOIN names n ON e.namesid = n.namesid
WHERE (d.dt BETWEEN '2012/02/01' AND '2012/05/01')
ORDER BY d.dt;

【讨论】:

太棒了,几乎!这很好,几乎就在那里,但即使 events.rectypeid = 1(无重复),它也会重复行,可能是因为 event 和 recurringtype 表不同步?我怀疑您的“或”之后可能缺少“与”,但我想不出应该是什么。 @blankabout:条件小于或等于,但也许这可以解决查询:OR (e.rectypeid 遗憾的是,这些选项都没有帮助。我想知道一种不同的方法是否会有所帮助,也许是两个查询的某种联合,尽管我(有限的)联合经验表明它们只是两个完全独立的查询,以一种不是特别有用的方式焊接在一起。 @blankabout:这很好。 WHERE 子句中的 e.rectypeid > 1 怎么样? e.rectypeid = r.rectypeid OR (e.rectypeid=r.rectypeid AND e.rectypeid 不幸的是,那里没有欢乐。我认为问题的一部分是 recurringtypes 表没有在 rectype

以上是关于重复事件、SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

sql查询去掉重复记录

sql查询消除重复记录

sql关联查询去除重复项

SQL查询语句,怎样查询重复数据

sql查询去掉重复记录

sql查询两表中不重复记录