campi Data in MySQL, trovare tutte le righe che non si sovrappongono e che ritornano solo la differenza

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

Domanda

Quindi questa è stata una delle mie primissime domande qui, ma ho una leggera variazione:

Così ho due persone i cui orari sono in un database. Gli orari registrano semplicemente l'ora di inizio, ora di fine, e la descrizione dei vari eventi / appuntamenti sia per gli utenti.

Persona vuole commerciare appuntamenti con PersonB. Voglio una query MySQL che restituirà tutti loro tempi che PersonB e Persona possono scambiare.

In origine i parametri della query erano di buttare via gli appuntamenti di PersonB dove c'era sovrapposizione con Persona e PersonB di appuntamento dovuto essere l'esatto stessa lunghezza la nomina Persona vuole scambiare. Ho avuto qualche consiglio grande in tempo aritmetica / geometria che mi ha aiutato ottenere i risultati di cui avevo bisogno.

Ora voglio cambiare il parametro 1-a-1 in modo che le nomine non devono essere uguali in lunghezza. Quindi, se PERSONA vuole scambiare la sua mattina appuntamento Lunedi (10:00-11:30), la query:

  • Escludere qualsiasi appuntamenti del PersonB che durante uno degli appuntamenti della Persona
  • includere uno dei appuntamenti del PersonB che sono al di fuori degli appuntamenti della Persona
  • Include le parti di appuntamenti di PersonB che sono pur Persona è libera, ma mostrano solo la parte libera.

Quindi, se persona vuole scambiare la nomina di cui sopra (di nuovo, Lunedi 10:00-11:30), e persona ha un appuntamento il Martedì 13:00-03:00 e PersonB ha un appuntamento il Martedì 12:00-04:00, la query restituirebbe:

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

In aggiunta a tutte le altre possibilità. È questo troppo aspettarsi dal database? Se è così, qualche suggerimento su come ottenere almeno quei cambiamenti che si sovrappongono, ma hanno tempi che incombe su entrambi i lati in modo che uno script PHP potrebbe trattare con loro?


secondo la richiesta del searlea, ecco un po 'più di contesto:

Ho continuato dicendo appuntamenti ma penso che in realtà significava "posti di lavoro" come in "turni di lavoro". Persona e PersonB lavorano nello stesso ufficio. In vCalendar, turni di lavoro sono solitamente denominati "Events", ma in alcune occasioni "Appuntamenti" e sono andato con quest'ultimo come sembra meno come le due persone stanno andando a una fiera.

Quindi persona ha uno spostamento lavastoviglie su Lunedi 10:00-11:30. PersonB sta cucinando il Martedì 12:00-05:00. Persona vuole davvero vedere suo fratello, prima di lasciare la città il Lunedi. Aveva piuttosto ottenere tutte Lunedi mattina, ma lui accontenterei di ottenere un'ora di spostamento fuori.

Quindi, a mio vecchio modello (cresciuto in mia prima domanda qui), che cercavo per eventuali turni dove non c'era sovrapposizione e dove gli spostamenti sono stati pari nel tempo. Ma che ha due problemi:

  1. Se ho bisogno di qualcuno per coprire il mio turno due ore il Martedì e io lavoro per 4 ore il Giovedi, e Joe lavora per 8 ore il Giovedi, ho potuto scambiare due delle sue ore e lui potrebbe lasciare un po 'presto e posso stare un po 'più tardi.

  2. Se ho un turno di due ore, ma mi piacerebbe molto commercio un'ora di esso solo per rendere l'aeroporto in tempo, voglio sapere se questo e questo viene in un'ora prima di me in seguito nel corso della settimana in modo da poter prendere quella parte del suo turno di lavoro.

Per farla breve (troppo tardi), voglio ciò che è apparentemente conosciuta come la < strong> relativo complemento dei turni di persona per PersonB (in pratica qualsiasi volte che PersonB sta lavorando e persona non, indipendentemente dal fatto che i turni si sovrappongono in qualche altro punto.)

Idealmente, vorrei avere una serie di risultati che ha incluso i bit che PersonB stava lavorando e Persona non è stato (i due 1 ore turni di cui sopra), così come l'intero turno (con un tag speciale per indicare che non è disponibili nel suo complesso) in modo che persona avrebbe visto che lui stava coprendo il part di un cambiamento e non si confondono e pensano che PersonB è capitato di lavorare due turni di un'ora uno.

Questo è tutto comincia a sembrare un po 'complicato. Fondamentalmente voglio turni di PersonB di essere essere blu, i turni di Persona per essere di colore giallo, e voglio il database per riportare tutte le parti che non sono di colore verde.

È stato utile?

Soluzione

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

Questo seleziona gli eventi di Ondra, che può inserirsi in un gap nel diario di Zizka.

Modificato:. In origine era un Intersect, ma se si desidera che il relativo complemento, questo è sufficiente

Altri suggerimenti

Sia $shift_id essere l'ID del turno che il vostro utente vuole scambiare.

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;

Testato 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');

Risultati:

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

Questo mostra tutti i turni per i quali qualsiasi parte (s) possono essere scambiati, tra cui come molto tempo totale (in secondi) è swappable. L'ultima colonna, conflict_times, mostra i tempi per cui l'utente scambio è già pianificata per funzionare. Dovrebbe essere facile per l'applicazione per estrarre i tempi disponibili da questo; è possibile, ma molto difficile, in MySQL.

Task

restituire tutti gli intervalli di due utenti diversi escluse le parti in cui si sovrappongono.

Tabella e dati di test

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');
risultati

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

Soluzione

Ho usato caratteristica di MySQL chiamato Variabili definite dall'utente per raggiungere l'obiettivo con la seguente query:

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;

L'idea di base è quella di visualizzare la tabella due volte ordinati per tempo in modo che ogni record appaiono due volte. Una volta per l'ora di inizio e poi per il tempo della fine. Poi sto usando variabili definite dall'utente per tenere traccia dello stato durante l'attraversamento record e tornare solo i record 'ora di fine' con il tempo di inizio e di fine rettificato per sovrapposizione di intervalli.

Ipotesi

Solo ipotesi è che nessuna intervallo di persona x si sovrappone a un altro intervallo della stessa persona.

Comportamento

pochi casi, ed i loro risultati:

<  (   >   )
<  >   (   )

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

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

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

Avvertimenti

Devo aver utilizzato unix timestamp dato che il mio server MySQL non potrebbe fare il confronto tra DATETIME tenuto in variabile definita dall'utente e qualcos'altro.

Pro & Contro

Si fa il suo lavoro in unico passaggio senza giunzioni e quindi dovrebbe prendere O (n). Esso non può recuperare tutte le parti di intervallo di persona un taglio da intervalli chiusi di persona B. Esso utilizza MySQL funzionalità specifiche.

Per il riferimento un codice snipped che ho usato di recente. Può essere usato per controllare la sovrapposizione intervalli di date. E 'scritto in Ruby on Rails, ma l'idea (l'istruzione SQL) può essere facilmente tradotto in altre lingue)

  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

Come al solito con scope chiamato questa applicazione può essere riutilizzato in combinazione con altri scopi.

user = User.find(...)
today = Date.today
confirmed_absences = user.absences.confirmed.overlaps(today.beginning_of_month, today.end_of_month).count
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top