Как найти один час с наибольшим количеством точек данных? - PullRequest
9 голосов
/ 03 февраля 2009

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

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

В идеале был бы способ сделать это внутри одного запроса к базе данных.

Ответы [ 10 ]

5 голосов
/ 03 февраля 2009

Учитывая таблицу, заполненную каждой минутой интересующего вас года Minutes и таблицу Posts со столбцом Time:

select top 1 minutes.time, count (posts.time)
from Minutes
   left join posts on posts.time >= minutes.time AND posts.time < dateadd(hour, 1, Minutes.Time)
group by minutes.time
order by count (posts.time) desc

Для создания таблицы минут вы можете использовать такую ​​функцию, как ufn_GenerateIntegers. Тогда функция становится

select top 5 minutes.time, count (posts.time)
from (select dateadd(minute, IntValue, '2008-01-01') as Time from ufn_GenerateIntegers(525600)) Minutes
   left join posts on posts.time >= minutes.time AND posts.time < dateadd(hour, 1, Minutes.Time)
group by minutes.time
order by count(posts.time) desc

Я только что провел тестовый прогон с около 5000 случайных записей, и на моей машине это заняло 16 секунд. Таким образом, нетривиально, но не смешно для случайного разового запроса. К счастью, это точка данных, которую вы можете вычислять один раз в день или даже раз в месяц и кэшировать, если хотите часто отображать значение.

Взгляните на улучшение Лассевка .

4 голосов
/ 03 февраля 2009

Биннинг будет работать, если вы хотите посмотреть на такие интервалы, как 10:00 - 11:00. Однако если с 10:30 до 11:30 у вас внезапно возник интерес, то он будет разбит на две корзины и, следовательно, может быть скрыт меньшим числом попаданий, которые полностью соответствуют одному часу. *

Единственный способ избежать этой проблемы - создать список, отсортированный по времени, и пройти по нему. Примерно так:

max = 0; maxTime = 0
for each $item in the list:
   push $item onto queue
   while head of queue is more than an hour before $item
      drop queue head.
   if queue.count > max then max = queue.count; maxTime = $item.time

Таким образом, вам нужно хранить только 1 час в памяти, а не весь список.

2 голосов
/ 03 февраля 2009

Это работало на небольшой тестовой базе данных MS-SQL.

SELECT TOP 1 id, date_entered,
  (SELECT COUNT(*)
   FROM   dbo.notes AS n2
   WHERE n2.date_entered >= n.date_entered 
   AND n2.date_entered < Dateadd(hh, 1, n.date_entered)) AS num
FROM  dbo.notes n
ORDER BY num DESC

Это не очень эффективно, проверки основаны на часе от каждого сообщения.

For MYSQL 

SELECT ID,f.Date, (SELECT COUNT(*)
FROM Forum AS f2
WHERE f2.Date >= f.Date AND f2.Date < Date_ADD(f.Date, INTERVAL 1 HOUR)) As num
FROM Forum AS f
ORDER BY num
LIMIT 0,1
2 голосов
/ 03 февраля 2009

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

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

Чтобы получить «более симпатичный» период, вы можете рассчитать, сколько он на самом деле, поделить на два и скорректировать начало периода обратно на эту сумму и конец вперед, это «центрирует» сообщения в течение часа. , Эта корректировка, как гарантируют, не включает никаких новых сообщений, таким образом, счет все еще действителен. Если сообщения достаточно близки, чтобы их можно было внезапно включить в период после того, как вы расширили его до одного часа, то в более ранней точке было бы «наибольшее количество сообщений» вместо того, который вы выбрали.

Если это вопрос SQL, вы можете повторно использовать SQL, который Джош разместил здесь , просто замените таблицу минут другой ссылкой на вашу таблицу сообщений.


Другой метод , который вы можете использовать, это использовать скользящее окно.

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

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

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

После того, как вы закончите, у вас будет «копия списка» с наибольшим количеством сообщений за час, или вы получите сообщение, которое является концом 1-часового окна, содержащего большинство сообщений.

Псевдо-код:

initialize posts-window-list to empty list
for each post in sorted-posts-list:
    add post to end of posts-window-list
    for each other-post from start of posts-window-list:
        if other-post is more than one hour older than post, remove it
        otherwise, end this inner loop
    if number of posts in list is more than previous maximum:
        make copy of list, this is the new maximum
1 голос
/ 02 апреля 2009

Это сделает это.

ВЫБРАТЬ DateOfEvent HourBegin, DATEADD (чч, 1, DateOfEvent)) HourEnd, COUNT (*) AS NumEventsPerHour ОТ СОБЫТИЙ КАК А ПРИСОЕДИНЯЙТЕСЬ К TIVENTS AS B ON A.DateOfEvent> = B.DateOfEvents AND DATEADD (чч, 1, A.DateOfEvent) <= B.DateOfEvent GROUP BY A.DateOfEvent </p>

1 голос
/ 04 февраля 2009

Вот небольшая разница в реализации другого Джоша: он воздерживается от таблицы непосредственного использования и использует самосоединение для поиска любых сообщений в течение часа после этого сообщения.

select top 1 posts.DateCreated, count (posts.datecreated),
min(minutes.DateCreated) as MinPostDate,
max(minutes.datecreated) as MaxPostDate
from posts Minutes   
left join posts on posts.datecreated >= minutes.DateCreated 
AND posts.datecreated < dateadd(hour, 1, Minutes.DateCreated)
group by posts.DateCreated
order by count(posts.datecreated) desc

С точки зрения производительности для таблицы, содержащей только 6 строк, его метод, который использовал функцию для создания таблицы intermiadte, занял 16 секунд по сравнению с этой, которая была подсекундной.

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

1 голос
/ 03 февраля 2009

Это приводит к запросу базы данных O (n) и поиску по наибольшему времени O (n) для общей сложности O (2n) (которая, конечно, все еще равна O (n)):

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

Итак, вы бы запустили запрос подсчета для этой таблицы:

time
1
2      
4
3
3
2
4
1
3
2

И он вернется:

0 1
1 1
2 3
3 3
4 2

Подсчитывая каждый предмет.

Я подозреваю, что вы можете сделать то же самое со своей таблицей, и поминать их за минуту, а затем запустить алгоритм для этого.

SELECT customer_name, COUNT(DISTINCT city) as "Distinct Cities"
FROM customers
GROUP BY customer_name;

Из этого урока на счет: http://www.techonthenet.com/sql/count.php (ближе к концу).

Вот похожая страница из руководства MySQL: http://dev.mysql.com/doc/refman/5.1/en/counting-rows.html

Итак, если у вас есть таблица с временной датой (с точностью до минуты, позволяющей выполнять биннинг по минутам):

datetime (yyyymmddhhmm)
200901121435
200901121538
200901121435
200901121538
200901121435
200901121538
200901121538
200901121435
200901121435
200901121538
200901121435
200901121435

Тогда SQL

SELECT datetime, COUNT(DISTINCT datetime) as "Date Time"
FROM post
GROUP BY datetime;

должен вернуть

200901121435 7
200901121538 5

Вам все еще нужно будет обработать это, но тяжелая работа по группировке и подсчету выполнена, и в результате будет получено чуть более 500 тыс. Строк в год (60 минут, 24 часа, 365 дней)

Постобработка будет:

Start at time T = first post time.
Set greatestTime = T
Sum all counts between T and T+one hour --> currentHourCount and greatestHourCount
While records exist past T+one hour
   Increment T by one minute.
   While the first element is prior to time T, subtract it
   while the last element is before time T+ one hour, add it
   If currentHourCount > greatestHourCount then
      greatestHourCount = currentHourCount
      greatestTime = T
end while

-Adam

0 голосов
/ 03 февраля 2009

При использовании MySQL:

SELECT DATE(postDate), HOUR(postDate), COUNT(*) AS n
FROM posts
GROUP BY DATE(postDate), HOUR(postDate)
ORDER BY n DESC
LIMIT 1
0 голосов
/ 03 февраля 2009

Если mysql:

select substr( timestamp, 1, 16 ) as hour, count(*) as count from forum_posts group by hour order by count desc limit 1;

edit: не уверен, означает ли оригинальный вопрос какой-либо возможный 60-минутный период

0 голосов
/ 03 февраля 2009
SELECT  DATEPART(hour, PostDateTime) AS HourOfDay,
        COUNT(*) AS ForumPosts
FROM    Posts
GROUP BY DATEPART(hour, PostDateTime)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...