PHP / MYSQL диапазоны времени и даты перекрываются для пользователей - PullRequest
8 голосов
/ 01 апреля 2012

пожалуйста, мне нужна помощь с этим (для лучшего понимания, пожалуйста, смотрите прикрепленное изображение), потому что я совершенно беспомощен.

http://img16.imageshack.us/img16/7196/overlapsen.jpg http://img16.imageshack.us/img16/7196/overlapsen.jpg

Как видите, у меня есть пользователи, и они хранят свои начальные и конечные даты в моей БД как ГГГГ-мм-дд Ч: i: s.Теперь мне нужно найти перекрытия для всех пользователей в соответствии с наиболее частыми перекрытиями временного диапазона (для большинства пользователей).Я хотел бы получить 3 самых частых совпадения времени передачи данных для большинства пользователей.Как мне это сделать?

Я понятия не имею, какой запрос mysql мне следует использовать, или, может быть, было бы лучше выбрать все datetime (начало и конец) из базы данных и обработать его в php (но как?).Как указано на изображении результаты должны быть, например, время 8.30 - 10.00 является результатом для пользователей A + B + C + D.

Table structure:
UserID | Start datetime | End datetime
--------------------------------------
A | 2012-04-03 4:00:00 | 2012-04-03 10:00:00
A | 2012-04-03 16:00:00 | 2012-04-03 20:00:00
B | 2012-04-03 8:30:00 | 2012-04-03 14:00:00
B | 2012-04-06 21:30:00 | 2012-04-06 23:00:00
C | 2012-04-03 12:00:00 | 2012-04-03 13:00:00
D | 2012-04-01 01:00:01 | 2012-04-05 12:00:59
E | 2012-04-03 8:30:00 | 2012-04-03 11:00:00
E | 2012-04-03 21:00:00 | 2012-04-03 23:00:00

Ответы [ 4 ]

2 голосов
/ 21 сентября 2012

То, что у вас есть, - это набор множеств, и вы хотите определить, имеют ли какие-либо из них ненулевые пересечения.Это именно тот вопрос, который задают при попытке найти всех предков узла во вложенном наборе.

Мы можем доказать, что для каждого перекрытия по крайней мере одно временное окно будет иметь начальное время, которое попадает во вседругие перекрывающиеся временные окна.Используя этот лакомый кусочек, нам не нужно создавать искусственные временные интервалы в течение дня.Просто возьмите время начала и посмотрите, пересекается ли оно с любым другим временным окном, а затем просто подсчитайте количество пересечений.

Так, каков запрос?

/*SELECT*/
SELECT DISTINCT
    MAX(overlapping_windows.start_time) AS overlap_start_time,
    MIN(overlapping_windows.end_time) AS overlap_end_time ,
    (COUNT(overlapping_windows.id) - 1) AS num_overlaps
FROM user_times AS windows
INNER JOIN user_times AS overlapping_windows
ON windows.start_time BETWEEN overlapping_windows.start_time AND overlapping_windows.end_time
GROUP BY windows.id
ORDER BY num_overlaps DESC;

В зависимости от вашей таблицыразмер и то, как часто вы планируете запускать этот запрос, возможно, стоит добавить к нему пространственный индекс (см. ниже).

UPDATE

Если вы часто выполняете этот запрос, вынужно использовать пространственный индекс.Из-за обхода, основанного на диапазоне (т. Е. Время начала_падающего находится между диапазоном начала / конца), индекс BTREE ничего не сделает для вас.ЭТО ДОЛЖНО БЫТЬ ПРОСТРАНСТВЕННЫМ.

ALTER TABLE user_times ADD COLUMN time_windows GEOMETRY NOT NULL DEFAULT 0;
UPDATE user_times SET time_windows = GeomFromText(CONCAT('LineString( -1 ', start_time, ', 1 ', end_time, ')'));
CREATE SPATIAL INDEX time_window ON user_times (time_window);

Затем вы можете обновить предложение ON в приведенном выше запросе следующим образом:

ON MBRWithin( Point(0,windows.start_time), overlapping_windows.time_window )

Это даст вам индексированный обход запроса.Опять же, делайте это только в том случае, если вы планируете часто выполнять запрос.

Зачислите пространственный индекс на блог Кассони .

0 голосов
/ 27 июля 2012

Я бы не стал делать много в SQL, это намного проще в языке программирования, SQL не создан для чего-то подобного.

Конечно, разумно разбить день на "временные интервалы".«- это статистика.Но как только вы начинаете обрабатывать даты за границей 00:00, все начинает становиться непорядочным, когда вы используете соединения и внутренние операции выбора.Особенно с MySQL, который не очень любит внутренние выборки.

Вот возможный запрос SQL

SELECT count(*) FROM `times`
WHERE
  ( DATEDIFF(`Start`,`End`) = 0 AND
    TIME(`Start`) < TIME('$SLOT_HIGH') AND
    TIME(`End`) > TIME('$SLOT_LOW'))
  OR
  ( DATEDIFF(`Start`,`End`) > 0 AND
    TIME(`Start`) < TIME('$SLOT_HIGH') OR
    TIME(`End`) > TIME('$SLOT_LOW')

Вот некоторый псевдокод

granularity = 30*60; // 30 minutes
numslots = 24*60*60 / granularity;
stats = CreateArray(numslots);
for i=0, i < numslots, i++ do
  stats[i] = GetCountFromSQL(i*granularity, (i+1)*granularity); // low, high
end

Да, это делает numslots Запросы, но без присоединений нет ничего, следовательно, это должно быть довольно быстро.Также вы можете легко изменить разрешение.

И еще один положительный момент: вы можете «спросить себя»: «У меня есть два возможных таймслота, и мне нужен тот, где больше людей, и какой мне следует использовать»?»и просто дважды выполните запрос с соответствующими диапазонами, и вы не застряли с предварительно определенными временными интервалами.

Чтобы найти только полные перекрытия (запись учитывается только в том случае, если она охватывает полный интервал), вы должны переключать низкие и высокие диапазоныв запросе.

Возможно, вы заметили, что я не добавляю время между записями, которые могут занимать несколько дней, однако добавление целого дня просто увеличит все слоты на один, что сделает это совершенно бесполезным.Однако вы можете добавить их, выбрав sum(DAY(End) - DAY(Start)) и просто добавив возвращаемое значение ко всем слотам.

0 голосов
/ 26 августа 2012

Таблица кажется довольно простой. Я бы оставил ваш SQL-запрос довольно простым:

SELECT * FROM tablename

Тогда, когда у вас есть информация, сохраненная в вашем объекте PHP. Выполните обработку с помощью PHP, используя циклы и сравнения.

В простейшем виде:

for($x, $numrows = mysql_num_rows($query); $x < $numrows; $x++){

     /*Grab a row*/
     $row = mysql_fetch_assoc($query);

     /*store userID, START, END*/
     $userID = $row['userID'];
     $start = $row['START'];
     $end = $row['END'];

     /*Have an array for each user in which you store start and end times*/  

     if(!strcmp($userID, "A")
     {
        /*Store info in array_a*/
     }
     else if(!strcmp($userID, "B")
     {
        /*etc......*/
     } 
}
 /*Now you have an array for each user with their start/stop times*/

 /*Do your loops and comparisons to find common time slots. */

 /*Also, use strtotime() to switch date/time entries into comparable values*/

Конечно, это в очень простой форме. Вы, вероятно, захотите сделать один цикл по массиву, чтобы сначала получить все идентификаторы пользователя, прежде чем сравнивать их в цикле, показанном выше.

0 голосов
/ 01 апреля 2012

Как-то так должно начаться -

SELECT slots.time_slot, COUNT(*) AS num_users, GROUP_CONCAT(DISTINCT user_bookings.user_id ORDER BY user_bookings.user_id) AS user_list
FROM (
    SELECT CURRENT_DATE + INTERVAL ((id-1)*30) MINUTE AS time_slot
    FROM dummy
    WHERE id BETWEEN 1 AND 48
) AS slots
LEFT JOIN user_bookings
    ON slots.time_slot BETWEEN `user_bookings`.`start` AND `user_bookings`.`end`
GROUP BY slots.time_slot
ORDER BY num_users DESC

Идея состоит в том, чтобы создать производную таблицу, которая состоит из временных интервалов для дня.В этом примере я использовал пустышку (которая может быть любой таблицей с идентификатором AI, смежным для требуемого набора), чтобы создать список временных интервалов, добавляя 30 минут постепенно.Результат этого затем присоединяется к бронированиям, чтобы иметь возможность подсчитать количество книг для каждого временного интервала.

ОБНОВЛЕНИЕ Для всего диапазона дат / времени вы можете использовать такой запрос, чтобыполучить другие необходимые данные -

SELECT MIN(`start`) AS `min_start`, MAX(`end`) AS `max_end`, DATEDIFF(MAX(`end`), MIN(`start`)) + 1 AS `num_days`
FROM user_bookings

Затем эти значения можно подставить в исходный запрос или объединить два -

SELECT slots.time_slot, COUNT(*) AS num_users, GROUP_CONCAT(DISTINCT user_bookings.user_id ORDER BY user_bookings.user_id) AS user_list
FROM (
    SELECT DATE(tmp.min_start) + INTERVAL ((id-1)*30) MINUTE AS time_slot
    FROM dummy
    INNER JOIN (
        SELECT MIN(`start`) AS `min_start`, MAX(`end`) AS `max_end`, DATEDIFF(MAX(`end`), MIN(`start`)) + 1 AS `num_days`
        FROM user_bookings
    ) AS tmp
    WHERE dummy.id BETWEEN 1 AND (48 * tmp.num_days)
) AS slots
LEFT JOIN user_bookings
    ON slots.time_slot BETWEEN `user_bookings`.`start` AND `user_bookings`.`end`
GROUP BY slots.time_slot
ORDER BY num_users DESC

РЕДАКТИРОВАТЬ У меня естьдобавлены предложения DISTINCT и ORDER BY в GROUP_CONCAT() в ответ на ваш последний запрос.

Обратите внимание, что вам потребуется гораздо больший диапазон идентификаторов в фиктивной таблице.Я не проверял этот запрос, поэтому он может содержать синтаксические ошибки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...