何十万ものフォーラム投稿を含むデータベース テーブルがあり、最も多くの投稿が含まれる 1 時間の期間を調べたいと考えています。
タイムスタンプの配列を保持し、その中に最も多く含まれる時間を追跡しながら、一度に 1 分ずつ前にクロールすることもできますが、これを行うにはもっと良い方法があるように感じます。私はこの操作を 1 年間の投稿に対して実行するので、1 年間のすべての分をチェックするのはかなりひどいように思えます。
理想的には、単一のデータベース クエリ内でこれを行う方法があるでしょう。
何十万ものフォーラム投稿を含むデータベース テーブルがあり、最も多くの投稿が含まれる 1 時間の期間を調べたいと考えています。
タイムスタンプの配列を保持し、その中に最も多く含まれる時間を追跡しながら、一度に 1 分ずつ前にクロールすることもできますが、これを行うにはもっと良い方法があるように感じます。私はこの操作を 1 年間の投稿に対して実行するので、1 年間のすべての分をチェックするのはかなりひどいように思えます。
理想的には、単一のデータベース クエリ内でこれを行う方法があるでしょう。
関心のある年の毎分で満たされ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秒かかりました。したがって、些細なことではありませんが、たまに1回限りのクエリを行うのは簡単ではありません。幸い、これは1日1回、または1か月に1回計算して、値を頻繁に表示する場合はキャッシュできるデータポイントです。
lassevkの改善点を見てください。
10:00〜11:00などの間隔を確認する場合は、ビニングが機能します。ただし、10:30〜11:30に突然関心が高まった場合は、2つのビンに分割されるため、たまたま1時間以内に完全に収まった少数のヒットによって隠される可能性があります。
この問題を回避する唯一の方法は、時間でソートされたリストを生成し、それをステップスルーすることです。このようなもの:
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時間のウィンドウをメモリに保持するだけで済みます。
これは、小さなテスト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
これはあまり効率的ではなく、各投稿からの1時間に基づいてチェックします。
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
すべての投稿のタイムスタンプをそのような時間の開始として扱い、それを開始した投稿を含め、その時間内に該当する他のすべての投稿をカウントします。結果の時間を、それぞれの投稿数で降順に並べ替えます。
これを行うと、投稿数が最も多い最上位の単一の「時間」が見つかりますが、この期間は正確に 1 時間ではなく、短くなる可能性があります (ただし、長くなることはありません)。
「きれいな」期間を取得するには、実際の長さを計算し、2 で割って、期間の開始をその量だけ戻し、終了を前に調整します。これにより、投稿が時間内に「中央」になります。この調整には新しい投稿が含まれないことが保証されているため、カウントは引き続き有効です。期間を 1 時間に拡大した後、投稿がその期間に突然含まれるほど近い場合、選択した時点ではなく、以前の時点に「最も多くの投稿」が含まれていたことになります。
これが SQL の質問である場合は、Josh がここに投稿した SQL を再利用できます。Minutes テーブルを posts テーブルへの別のリンクに置き換えるだけです。
使用できる別の方法は、スライディング ウィンドウを使用することです。
まず、タイムスタンプに従ってすべての投稿を並べ替えます。リストを使用して投稿を追跡します。これには、リンクされたリストを使用できます。
次に、投稿ごとにリストの最後に追加します。次に、リストの先頭からの各投稿について、その投稿が追加したばかりの投稿より 1 時間以上前にある場合は、リストから削除します。
リスト内の単一の新しい投稿に対してその 2 段階の操作を行った後、リスト内の投稿の数が以前の最大数を超えているかどうかを確認し、超えている場合は、リストのコピーを作成するか、少なくとも投稿を保存します。追加しました。
完了すると、1 時間で最も多くの投稿があった「リストのコピー」、または最も多くの投稿を含む 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
これにより、O(n) データベース クエリと O(n) 最大時間検索が発生し、全体の複雑度は O(2n) (もちろん、それでも O(n) です) になります。
アイテムを分単位で「ビン化」する SQL で count distinct コマンドを使用します。
したがって、このテーブルに対してカウント クエリを実行します。
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
これを後処理する必要がありますが、グループ化とカウントの大変な作業は完了しており、年間 (60 分、24 時間、365 日) 50 万行をわずかに超えるだけです。
後処理は次のようになります。
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
-アダム
これは、他の Josh の実装のわずかなバリエーションです。これは、即時テーブルを放棄し、それ自体に自己結合を使用して、その 1 つの投稿から 1 時間以内の投稿を探します。
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行のみのテーブルのパフォーマンスの観点から、関数を使用して中間テーブルを生成する彼の方法は16秒かかりましたが、これは1秒未満でした.
タイムスパンは各投稿のオフセットに基づいているため、これを使用して有効なタイムフレームを見逃す可能性があるかどうかはわかりません。
これでできます。
SELECT DateOfEvent HourBegin, DATEADD(hh, 1, DateOfEvent)) HourEnd, COUNT(*) AS NumEventsPerHour FROM tEvents AS A JOIN tEvents AS B ON A.DateOfEvent >= B.DateOfEvents AND DATEADD(hh, 1, A.DateOfEvent) < = B.DateOfEvent GROUP BY A.DateOfEvent
MySQLを使用している場合:
SELECT DATE(postDate), HOUR(postDate), COUNT(*) AS n
FROM posts
GROUP BY DATE(postDate), HOUR(postDate)
ORDER BY n DESC
LIMIT 1
DATEPART(hour, PostDateTime) を HourOfDay として選択し、
COUNT(*) AS フォーラム投稿
からの投稿
GROUP BY DATEPART(時間、PostDateTime)
mysql の場合:
select substr( timestamp, 1, 16 ) as hour, count(*) as count from forum_posts group by hour order by count desc limit 1;
編集:元の質問が60分の期間を意味するかどうかわからない