Eu tenho uma tabela que armazena compromissos disponíveis para professores, permitindo dois tipos de inserções:
Horário : com total liberdade para adicionar vagas ilimitadas por dia por professor (contanto que as vagas não se sobreponham): em 15 de abril, o professor pode ter vagas às 10:00, 11:00, 12:00 e 16:00 . Uma pessoa é atendida após escolher um horário / horário específico para o professor.
Período / intervalo de tempo : em 15 / abr, outro professor pode trabalhar das 10:00 às 12:00 e, em seguida, das 14:00 às 18:00. Uma pessoa é atendida por ordem de chegada; portanto, se um professor trabalha das 10:00 às 12:00, todas as pessoas que chegarem nesse período serão atendidas por ordem de chegada (fila local).
Como tenho que retornar todos os professores disponíveis em uma pesquisa, preciso que todos os slots sejam salvos na mesma tabela que a ordem de chegada. Dessa forma, posso encomendar por date_from ASC, mostrando os primeiros slots disponíveis primeiro nos resultados da pesquisa.
Estrutura atual da tabela
CREATE TABLE `teacher_slots` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`teacher_id` mediumint(8) unsigned NOT NULL,
`city_id` smallint(5) unsigned NOT NULL,
`subject_id` smallint(5) unsigned NOT NULL,
`date_from` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`date_to` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`status` tinyint(4) NOT NULL DEFAULT '0',
`order_of_arrival` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `by_hour_idx` (`teacher_id`,`order_of_arrival`,`status`,`city_id`,`subject_id`,`date_from`),
KEY `order_arrival_idx` (`order_of_arrival`,`status`,`city_id`,`subject_id`,`date_from`,`date_to`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Consulta de pesquisa
Preciso filtrar por: data / hora real, city_id, subject_id e se um slot está disponível (status = 0).
Para horários, eu tenho que mostrar todos os horários disponíveis para o primeiro dia mais próximo disponível para cada professor (mostrar todos os horários de um determinado dia e não posso mostrar mais de um dia para o mesmo professor). (Eu recebi a consulta com a ajuda de mattedgod ).
Para o intervalo baseado (order_of_arrival = 1), preciso mostrar o intervalo disponível mais próximo, apenas uma vez por professor.
A primeira consulta é executada individualmente em cerca de 0,10 ms, a segunda consulta em 0,08 ms e o UNION ALL em uma média de 300ms.
(
SELECT id, teacher_slots.teacher_id, date_from, date_to, order_of_arrival
FROM teacher_slots
JOIN (
SELECT DATE(MIN(date_from)) as closestDay, teacher_id
FROM teacher_slots
WHERE date_from >= '2014-04-10 08:00:00' AND order_of_arrival = 0
AND status = 0 AND city_id = 6015 AND subject_id = 1
GROUP BY teacher_id
) a ON a.teacher_id = teacher_slots.teacher_id
AND DATE(teacher_slots.date_from) = closestDay
WHERE teacher_slots.date_from >= '2014-04-10 08:00:00'
AND teacher_slots.order_of_arrival = 0
AND teacher_slots.status = 0
AND teacher_slots.city_id = 6015
AND teacher_slots.subject_id = 1
)
UNION ALL
(
SELECT id, teacher_id, date_from, date_to, order_of_arrival
FROM teacher_slots
WHERE order_of_arrival = 1 AND status = 0 AND city_id = 6015 AND subject_id = 1
AND (
(date_from <= '2014-04-10 08:00:00' AND date_to >= '2014-04-10 08:00:00')
OR (date_from >= '2014-04-10 08:00:00')
)
GROUP BY teacher_id
)
ORDER BY date_from ASC;
Questão
Existe uma maneira de otimizar o UNION, para que eu possa obter uma resposta razoável de no máximo ~ 20ms ou até mesmo retornar intervalo com base + horária com base em apenas uma consulta (com um IF, etc)?
SQL Fiddle: http://www.sqlfiddle.com/#!2/59420/1/0
EDITAR:
Tentei alguma desnormalização criando um campo "only_date_from" onde armazenava apenas a data, para que eu pudesse alterar isso ...
DATE(MIN(date_from)) as closestDay / DATE(teacher_slots.date_from) = closestDay
... para isso
MIN(only_date_from) as closestDay / teacher_slots.only_date_from = closestDay
Já me salvou 100ms! Ainda 200ms em média.