Los campos de fecha en MySQL, la búsqueda de todas las filas que no se superpongan y que regresan sólo la diferencia

StackOverflow https://stackoverflow.com/questions/1333340

Pregunta

Así que esta fue una de mis primeras preguntas aquí, pero tengo una ligera variación:

Así que tengo dos personas cuyos horarios se encuentran en una base de datos. Los horarios simplemente registrar la hora de inicio, hora de finalización, y la descripción de los diversos eventos / citas para los usuarios.

PERSONA quiere cambiar las citas con PersonB. Quiero una consulta MySQL que devolverá todos los tiempos de ellos que PersonB y Persona pueden intercambiar.

Al principio, los parámetros de la consulta fueron a tirar cualquier cita de PersonB donde hay un solapamiento con el nombramiento de Persona y de PersonB tenido que ser la misma longitud exacta de la cita PERSONA quiere intercambiar. Tengo buenos consejos sobre la aritmética de tiempo / geometría que me ayudó a obtener los resultados que necesitaba.

Ahora quiero cambiar el parámetro 1-a-1, de modo que las citas no tienen que tener la misma longitud. Así que si PERSONA quiere intercambiar su nombramiento lunes por la mañana (10:00 - 11:30), la consulta:

  • No incluir ninguna de las citas del PersonB que están en una de las citas con el personaje
  • incluye ninguna de las citas del PersonB que están fuera de las citas con el personaje
  • Incluir las partes de las citas del PersonB que son mientras Persona es libre, pero sólo muestra la parte libre.

Así que si PERSONA quiere cambiar la cita anterior (de nuevo, Lunes 10 a.m.-11:30 AM), y Persona tiene una cita el martes 13:00-15:00 y PersonB tiene una cita el martes 24:00-16:00, la consulta devolvería:

Possible_Swaps
==============
userID  | Start             | End             | Description
PersonB | Tuesday, 12:00 PM | Tuesday 1:00 PM | Cooking
PersonB | Tuesday,  4:00 PM | Tuesday 5:00 PM | Cooking

Además de cualquier otras posibilidades. ¿Es esto demasiado a esperar de la base de datos? Si es así, cualquier sugerencia sobre cómo conseguir al menos los cambios que se superponen, sino que tenga las veces que pesan sobre ambos lados de manera que un script PHP podría tratar con ellos?


según la petición del searlea, aquí hay un poco más de contexto:

Seguí citas que dicen, pero creo que en realidad quería decir "trabajos" como en "turnos de trabajo". Persona y PersonB trabajan en la misma oficina. En vCalendar, turnos de trabajo se refieren generalmente como "Eventos", pero en ocasiones "Citas" y yo fuimos con este último, ya que se parece menos a las dos personas que van a una feria.

Así personaje tiene un desplazamiento de lavaplatos el lunes 10:00-11:30 AM. PersonB está cocinando el martes 24:00-17:00. PERSONA realmente quiere ver a su hermano antes de salir de la ciudad el lunes. Él prefiere obtener todos los lunes por la mañana, pero él se conformaría con conseguir una hora de desplazamiento fuera.

Así que en mi viejo modelo (criado en mi primera pregunta aquí), que estaba buscando para cualquier cambio donde no había solapamiento y donde los cambios son iguales en el tiempo. Pero que tiene dos problemas:

  1. Si necesito a alguien para cubrir mi turno de 2 horas el martes y trabajo durante 4 horas el jueves y Joe funciona durante 8 horas el jueves, pude intercambiar dos de sus horas y podría dejar un poco temprano y me puedo quedar un poco más tarde.

  2. Si tengo un turno de dos horas, pero con mucho gusto cambiaría de una hora de ella sólo para llegar a tiempo al aeropuerto, me gustaría saber si tal o cual viene en una hora antes de mí más tarde en la semana para que pueda tomar esa parte de su turno de trabajo.

Es una larga historia corta (demasiado tarde), quiero que al parecer se conoce como el < strong> complemento relativa de los cambios de personaje a PersonB (básicamente todas las veces que PersonB está trabajando y su personalidad no es así, es independientemente de si los cambios se superponen en algún otro momento.)

Idealmente, me gustaría tener un conjunto de resultados que incluye los bits que PersonB estaba trabajando y Persona no fue (los dos 1 de turno horas se mencionó anteriormente), así como todo el turno (con una etiqueta especial para indicar que no es disponibles en su conjunto) para que PERSONA vería que estaba cubriendo la part de un cambio y no se confunden y piensan que PersonB acaba de pasar a estar trabajando dos turnos de una hora.

Esto es todo empezando a sonar un poco complicado. Básicamente quiero turnos de PersonB sean ser azul, los cambios de personaje a ser de color amarillo, y quiero que la base de datos para regresar todas las partes que no son verdes.

¿Fue útil?

Solución

SELECT * 
  FROM schedule AS s1
WHERE
  s1.user = 'Ondra'
AND
NOT EXISTS ( 
  SELECT * FROM schedule AS s2 
  WHERE
    s2.user = 'Zizka'
    AND (
      s2.start BETWEEN s1.start AND s1.end 
      OR
      s2.end BETWEEN s1.start AND s1.end 
      OR 
      s1.start > s2.start AND s1.end < s2.end 
    )
)

Esto selecciona los eventos de Ondra, que pueden caber en un hueco en el diario de Zizka.

Editado:. Originalmente se trataba de una intersección, pero si quieres el complemento relativo, esto es suficiente

Otros consejos

Que $shift_id ser el ID del turno de que el usuario quiere cambiar.

select swappable.shift_id, swappable.user_id, swappable.description,
    FROM_UNIXTIME(swappable.shiftstart) as start,
    FROM_UNIXTIME(swappable.shiftend) as end,
    (swappable.shiftend - swappable.shiftstart) -
        sum(coalesce(least(conflict.shiftend, swappable.shiftend) -
            greatest(conflict.shiftstart, swappable.shiftstart), 0))
        as swaptime,
    group_concat(conflict.shift_id) as conflicts,
    group_concat(concat(FROM_UNIXTIME(conflict.shiftstart), ' - ',
        FROM_UNIXTIME(conflict.shiftend))) as conflict_times
from shifts as problem
join shifts as swappable on swappable.user_id != problem.user_id
left join shifts as conflict on conflict.user_id = problem.user_id
    and conflict.shiftstart < swappable.shiftend
    and conflict.shiftend > swappable.shiftstart
where problem.shift_id = 1
group by swappable.shift_id
having swaptime > 0;

Probado con:

CREATE TABLE `shifts` (
  `shift_id` int(10) unsigned NOT NULL auto_increment,
  `user_id` varchar(20) NOT NULL,
  `shiftstart` int unsigned NOT NULL,
  `shiftend` int unsigned NOT NULL,
  `description` varchar(32) default NULL,
  PRIMARY KEY  (`shift_id`)
);

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (1,'april', UNIX_TIMESTAMP('2009-04-04 10:00:00'),UNIX_TIMESTAMP('2009-04-04 12:00:00'),'Needs to be swapped');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (2,'bill',  UNIX_TIMESTAMP('2009-04-04 10:30:00'),UNIX_TIMESTAMP('2009-04-04 11:30:00'),'Inside today');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (3,'casey', UNIX_TIMESTAMP('2009-04-04 12:00:00'),UNIX_TIMESTAMP('2009-04-04 14:00:00'),'Immediately after today');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (4,'casey', UNIX_TIMESTAMP('2009-04-04 08:00:00'),UNIX_TIMESTAMP('2009-04-04 10:00:00'),'Immediately before today');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (5,'david', UNIX_TIMESTAMP('2009-04-04 11:00:00'),UNIX_TIMESTAMP('2009-04-04 15:00:00'),'Partly after today');

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (6,'april', UNIX_TIMESTAMP('2009-04-05 10:00:00'),UNIX_TIMESTAMP('2009-04-05 12:00:00'),'Tommorow');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (7,'bill',  UNIX_TIMESTAMP('2009-04-05 09:00:00'),UNIX_TIMESTAMP('2009-04-05 11:00:00'),'Partly before tomorrow');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (8,'casey', UNIX_TIMESTAMP('2009-04-05 10:00:00'),UNIX_TIMESTAMP('2009-04-05 12:00:00'),'Equals tomorrow');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (9,'david', UNIX_TIMESTAMP('2009-04-05 10:30:00'),UNIX_TIMESTAMP('2009-04-05 11:30:00'),'Inside tomorrow');

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (10,'april',UNIX_TIMESTAMP('2009-04-11 10:00:00'),UNIX_TIMESTAMP('2009-04-11 12:00:00'),'Next week');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (11,'april',UNIX_TIMESTAMP('2009-04-11 12:00:00'),UNIX_TIMESTAMP('2009-04-11 14:00:00'),'Second shift');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (12,'bill', UNIX_TIMESTAMP('2009-04-11 11:00:00'),UNIX_TIMESTAMP('2009-04-11 13:00:00'),'Overlaps two');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (13,'casey',UNIX_TIMESTAMP('2009-04-11 17:00:00'),UNIX_TIMESTAMP('2009-04-11 19:00:00'),'No conflict');

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (14,'april',UNIX_TIMESTAMP('2009-05-04 10:00:00'),UNIX_TIMESTAMP('2009-05-04 12:00:00'),'Next month');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (15,'april',UNIX_TIMESTAMP('2009-05-04 13:00:00'),UNIX_TIMESTAMP('2009-05-04 15:00:00'),'After break');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (16,'bill', UNIX_TIMESTAMP('2009-05-04 11:00:00'),UNIX_TIMESTAMP('2009-05-04 14:00:00'),'Middle okay');

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (17,'april',UNIX_TIMESTAMP('2010-04-04 10:00:00'),UNIX_TIMESTAMP('2010-04-04 11:00:00'),'Next year');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (18,'april',UNIX_TIMESTAMP('2010-04-04 11:30:00'),UNIX_TIMESTAMP('2010-04-04 12:00:00'),'After break');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (19,'april',UNIX_TIMESTAMP('2010-04-04 12:30:00'),UNIX_TIMESTAMP('2010-04-04 13:30:00'),'Third part');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (20,'bill', UNIX_TIMESTAMP('2010-04-04 10:30:00'),UNIX_TIMESTAMP('2010-04-04 13:00:00'),'Two parts okay');

Resultados:

'shift_id', 'user_id', 'description',              'start',               'end',                 'swaptime', 'conflicts', 'conflict_times'
 '3',       'casey',   'Immediately after today',  '2009-04-04 12:00:00', '2009-04-04 14:00:00', '7200',       NULL,       NULL
 '4',       'casey',   'Immediately before today', '2009-04-04 08:00:00', '2009-04-04 10:00:00', '7200',       NULL,       NULL
 '5',       'david',   'Partly after today',       '2009-04-04 11:00:00', '2009-04-04 15:00:00', '10800',     '1',        '2009-04-04 10:00:00 - 2009-04-04 12:00:00'
 '7',       'bill',    'Partly before tomorrow',   '2009-04-05 09:00:00', '2009-04-05 11:00:00', '3600',      '6',        '2009-04-05 10:00:00 - 2009-04-05 12:00:00'
'13',       'casey',   'No conflict',              '2009-04-11 17:00:00', '2009-04-11 19:00:00', '7200',       NULL,       NULL
'16',       'bill',    'Middle okay',              '2009-05-04 11:00:00', '2009-05-04 14:00:00', '3600',      '15,14',    '2009-05-04 13:00:00 - 2009-05-04 15:00:00,2009-05-04 10:00:00 - 2009-05-04 12:00:00'
'20',       'bill',    'Two parts okay',           '2010-04-04 10:30:00', '2010-04-04 13:00:00', '3600',      '19,18,17', '2010-04-04 12:30:00 - 2010-04-04 13:30:00,2010-04-04 11:30:00 - 2010-04-04 12:00:00,2010-04-04 10:00:00 - 2010-04-04 11:00:00'

Esto muestra todos los turnos para los que cualquier parte (s) se pueden intercambiar, incluyendo cómo mucho tiempo total (en segundos) es intercambiable. La columna final, conflict_times, muestra los tiempos para el que ya se ha programado el usuario swapping a trabajar. Debe ser fácil para la aplicación para extraer los tiempos disponibles de eso; es posible, pero muy difícil, en MySQL.

Tarea

Vuelta todos los intervalos de dos usuarios diferentes partes excepto donde se superponen.

Tabla y de prueba de datos

CREATE TABLE IF NOT EXISTS `shifts` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(1) NOT NULL,
  `start` datetime NOT NULL,
  `end` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ;

INSERT INTO `shifts` (`id`, `name`, `start`, `end`) VALUES
(1, 'a', '2000-01-01 01:00:00', '2000-01-01 03:00:00'),
(2, 'a', '2000-01-01 06:00:00', '2000-01-01 07:30:00'),
(3, 'b', '2000-01-01 02:00:00', '2000-01-01 04:00:00'),
(4, 'b', '2000-01-01 05:00:00', '2000-01-01 07:00:00'),
(5, 'a', '2000-01-01 08:00:00', '2000-01-01 11:00:00'),
(6, 'b', '2000-01-01 09:00:00', '2000-01-01 10:00:00'),
(7, 'a', '2000-01-01 12:00:00', '2000-01-01 13:00:00'),
(8, 'b', '2000-01-01 14:00:00', '2000-01-01 14:30:00'),
(9, 'a', '2000-01-01 16:00:00', '2000-01-01 18:00:00'),
(10, 'a', '2000-01-01 19:00:00', '2000-01-01 21:00:00'),
(11, 'b', '2000-01-01 17:00:00', '2000-01-01 20:00:00');
Resultados

test

        id  name    start           end
        1   a   2000-01-01 01:00:00 2000-01-01 02:00:00
        3   b   2000-01-01 03:00:00 2000-01-01 04:00:00
        4   b   2000-01-01 05:00:00 2000-01-01 06:00:00
        2   a   2000-01-01 07:00:00 2000-01-01 07:30:00
        5   a   2000-01-01 10:00:00 2000-01-01 11:00:00
        7   a   2000-01-01 12:00:00 2000-01-01 13:00:00
        8   b   2000-01-01 14:00:00 2000-01-01 14:30:00
        9   a   2000-01-01 16:00:00 2000-01-01 17:00:00
        11  b   2000-01-01 18:00:00 2000-01-01 19:00:00
        10  a   2000-01-01 20:00:00 2000-01-01 21:00:00

Solución

He utilizado las características de MySQL llamadas variables definidas por el usuario para lograr el objetivo con la siguiente consulta:

SET @inA=0, @inB=0, @lastAstart = 0, @lastBstart = 0, @lastAend = 0, @lastBend = 0;
SELECT id,name,start,end FROM (
    SELECT 
        id,name,
        IF(name='a',
          IF(UNIX_TIMESTAMP(start) > @lastBend, start, FROM_UNIXTIME(@lastBend)),
          IF(UNIX_TIMESTAMP(start) > @lastAend, start, FROM_UNIXTIME(@lastAend))
        ) as start,
        IF(name='a',
          IF(@inB,FROM_UNIXTIME(@lastBstart),end),
          IF(@inA,FROM_UNIXTIME(@lastAstart),end)
        )  as end,
        IF(name='a',
          IF(@inB AND (@lastBstart < @lastAstart), 1, 0),
          IF(@inA AND (@lastAstart < @lastBstart), 1, 0)
        ) as fullyEnclosed,
          isStart,
          IF(name='a',@inA:=isStart,0), 
          IF(name='b',@inB:=isStart,0), 
          IF(name='a',IF(isStart,@lastAstart:=t,@lastAend:=t),0), 
          IF(name='b',IF(isStart,@lastBstart:=t,@lastBend:=t),0)
    FROM (
            (SELECT *, UNIX_TIMESTAMP(start) as t, 1 as isStart FROM `shifts` WHERE name IN ('a', 'b'))
        UNION ALL 
            (SELECT *, UNIX_TIMESTAMP(end) as t, 0 as isStart FROM `shifts` WHERE name IN ('a', 'b'))
        ORDER BY t
    ) as sae
) AS final WHERE NOT isStart AND NOT fullyEnclosed;

La idea básica es hacer una lista de la mesa dos veces, ordenada por tiempo de modo que cada registro aparece dos veces. Una vez para la hora de inicio y después de la hora de finalización. Entonces estoy usando las variables definidas por el usuario para realizar un seguimiento del estado al atravesar los registros y devolver sólo los registros de los tiempos finales 'con la hora de inicio y hora de finalización ajustado por intervalos de superposición.

Supuestos

Sólo supuesto es que hay un intervalo de x persona se solapa con otro intervalo de la misma persona.

Comportamiento

Son pocos los casos, y sus resultados:

<  (   >   )
<  >   (   )

( < )  ( > )
( ) <  > ( )

<  (   )   >    // for this and similar cases only last part of interval is returned
       <   >

(   <  )   (   )  (  )  (   >   )  // like so
(   )                <  >   (   )

Advertencias

Debo haber utilizado UNIX marca de tiempo ya que mi servidor MySQL no pudo hacer la comparación entre DATETIME mantuvo en la variable definida por el usuario y algo más.

Pros y Contras

Se hace su trabajo en un solo paso y sin mediar une por lo que debe tomarse un tiempo O (N). No puede recuperar todas las partes del intervalo de persona a Corte por intervalos cerrados de la persona B. Utiliza la funcionalidad específica de MySQL.

En la referencia un código cortó que he utilizado recientemente. Se puede utilizar para comprobar la superposición de los intervalos de fechas. Está escrito en Ruby on Rails, pero la idea (la instrucción SQL) puede ser fácilmente traducido a otros idiomas)

  class Absence
    named_scope :overlaps, lambda { |start, ende| { 
      :conditions =>
          ["   absences.start_date BETWEEN :start AND :end " +
           "OR absences.end_date   BETWEEN :start AND :end " +
           "OR :start BETWEEN absences.start_date AND absences.end_date " +
           "OR :end BETWEEN absences.start_date AND absences.end_date ",
              {:start => start, :end => ende } ]
      }}
  end

Como es habitual en los ámbitos denominaron a este ámbito puede ser reutilizado en combinación con otros alcances.

user = User.find(...)
today = Date.today
confirmed_absences = user.absences.confirmed.overlaps(today.beginning_of_month, today.end_of_month).count
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top