2012-02-14 56 views
6

Soy consciente de que las preguntas sobre eventos recurrentes son comunes, pero no he podido encontrar ninguna respuesta a esta pregunta específica sobre eventos recurrentes que no sean los relacionados con las aplicaciones de calendario. La principal diferencia es que los eventos en nuestra aplicación. solo se verá en los informes o por sí mismos en lugar de en un formato de calendario, aunque en muchos aspectos son muy similares, quizás solo con menos equipaje asociado con los calendarios.Eventos recurrentes, consulta SQL

De forma similar a una aplicación de calendario. los eventos pueden ocurrir de manera puntual o pueden ser recurrentes, por ej. todos los jueves o primeros lunes de cada mes, hasta algún momento preestablecido en el futuro.

Los eventos se almacenan en una tabla de eventos que contiene las fechas de inicio y final y la 'id del tipo de recurrencia'. Si el 'tipo de recurrencia' es 'Ninguno', las fechas de inicio y finalización serán las mismas. La tabla de eventos contiene una identificación en una tabla separada que contiene el nombre del tipo de evento, por ej. 'Reunión' o 'Informe semanal'

Hay otra tabla que contiene la lista de 'tipos de recurrencia' P. ej. 'Sin repetición', 'Todos los lunes', 'Primer lunes de mes' y 'Último sábado de mes'.

Para facilitar las búsquedas, otra tabla contiene una lista de fechas de 1960 a 2060 junto con información relevante sobre cada fecha, por ejemplo si es un lunes y qué ocurrencia de lunes es en el mes.

Esto permite una búsqueda, tal como:

 
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; 

¿Qué es exactamente lo que se requiere para encontrar eventos recurrentes, dando una salida como:

 
+-----------+---------------+-------------------+-----------+------------+-------------------------------+ 
| 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     | 

para conseguir ninguno acontecimientos que se repiten una consulta simple se puede utilizar:

 
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; 
Esto da salida muy similar a la primera consulta pero, crucialmente, las fechas son de la tabla de eventos en lugar de la tabla de fechas.

Mi pregunta es: ¿Cómo puedo combinar estas consultas para llegar a una lista que contiene todos los eventos, tanto recurrentes como no recurrentes en el orden de las fechas?


Estas son las tablas y selecciones abreviadas de ellas, algunas columnas y todos los índices se han retirado por razones de brevedad :). La tabla de 'nombres' no se ha incluido por el mismo motivo.

 
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 | 
+------------+----------+------+-------+-----+-----+------------+--------+ 


No nos fijamos en absoluto sobre el uso del código por encima o por disposición de la tabla, las soluciones de trabajo serían bien recibidos. Por favor, no me señale hacia:

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?

o

http://tools.ietf.org/html/rfc5545

los he comprobado y fueron muy útiles, pero no hacer el lo mismo que tenemos la intención.

TIA

+0

¿Qué hace 'islast'? ¿O 'ocurrencia' en la tabla' fechas'? – Naltharial

+0

Si establece banderas 'islast' la última ocurrencia de un día en un mes (por ejemplo, 'el último lunes del mes), la ocurrencia es el recuento de las ocurrencias de un día en un mes (por ejemplo,' primer lunes del mes ',' segundo lunes en mes ') – blankabout

Respuesta

2

A menos que me falta algo la respuesta es sorprendentemente simple. No me había dado cuenta de que los UNION se pueden clasificar en columnas comunes mediante el uso de un alias, incluso si esas columnas pertenecen a tablas diferentes. Por lo que la consulta completa sería:

 
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; 

Se ha señalado que el uso de una mesa para buscar fechas es un riesgo porque las fechas se agote, lo cual es cierto, pero para calcular si una fecha es, por ejemplo, el primer lunes de cada mes (o el segundo, o cuarto, o quizás el cuarto y el último), parece un código SQL más complejo de lo que quiero entrar en este momento.

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; 
+0

¡Brillante, casi! Eso es genial y está prácticamente ahí, pero repite las filas incluso cuando events.rectypeid = 1 (No recurrence), tal vez porque las tablas de eventos y de tipo recurrente no están sincronizadas. Sospecho que puede haber un "Y" perdido después de tu "O", pero no puedo pensar cómo debería ser. – blankabout

+0

@blankabout: La condición es menor o igual, pero quizás esto resuelva la consulta: O (e.rectypeid <= 1 AND e.eventid IS NOT NULL). De lo contrario, probaría e.rectypeid <1? ¿O puede agregar e.eventid IS NOT NULL a la cláusula WHERE? Es más barato. – Bytemain

+0

Lamentablemente, ninguna de esas opciones ayudó. Me pregunto si un enfoque diferente podría ayudar, tal vez algún tipo de unión de las dos consultas, aunque mi experiencia (limitada) de los sindicatos sugiere que son solo dos consultas completamente separadas y soldadas entre sí de una manera no particularmente útil. – blankabout

Cuestiones relacionadas