2009-02-03 7 views
9

Ho una tabella di database con centinaia di migliaia di post nel forum, e vorrei scoprire che periodo di un'ora contiene il maggior numero di post.Come trovare il periodo di un'ora con più datapoint?

Potrei strisciare avanti di un minuto alla volta, mantenendo una serie di timestamp e tenendo traccia di quale ora ha avuto il massimo in esso, ma sento che c'è un modo molto migliore per farlo. Gestirò questa operazione per un anno di post, quindi controllare ogni minuto in un anno sembra davvero orribile.

Idealmente ci sarebbe un modo per farlo all'interno di una singola query di database.

+0

CHIARIMENTO: desidero considerare un periodo di 60 minuti. Quindi i metodi che usano la parte "ora" del timestamp non funzioneranno. – OverloadUT

+0

Vedo ora, quale database stai usando? – JoshBerke

risposta

5

dato un tavolo pieno di ogni minuto durante l'anno che ti interessa Minutes e un tavolo Posts con una colonna 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 

Per risolvere la generazione della tabella di minuti, è possibile utilizzare una funzione come ufn_GenerateIntegers. poi la funzione diventa

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 

ho appena fatto un test con circa 5000 posti casuali e ci sono voluti 16 secondi sulla mia macchina. Quindi, non banale, ma non ridicolo per l'occasionale query una tantum. Fortunatamente, questo è un punto dati che è possibile calcolare uno al giorno o anche una volta al mese e cache se si desidera visualizzare il valore frequentemente.

Dai uno sguardo allo lassevk's improvement.

+0

Ah ah! Questo è il tipo di cosa che stavo cercando! Dovrò testare per vedere quanto durerà questa query perché il mio server di database ha molte meno risorse del mio server php, ma è decisamente più nella direzione che speravo di fare. – OverloadUT

+0

Sì, questo potrebbe richiedere molto tempo - non l'ho provato affatto. Ma ho fatto cose simili con risoluzioni di un giorno. – Eclipse

+0

Sono abbastanza sicuro di aver letto un Daily WTF su questo design solo pochi giorni fa ... – rmeador

0
 
SELECT DATEPART(hour, PostDateTime) AS HourOfDay, 
     COUNT(*) AS ForumPosts 
FROM Posts 
GROUP BY DATEPART(hour, PostDateTime) 
1

Ciò si traduce in una query di database O (n), e un O (n) più grande ricerca a tempo, per una complessità totale di O (2n) (che, naturalmente, è ancora O (n)) :

Utilizzare un comando distinto di conteggio in SQL che consente di "raggruppare" gli elementi per l'utente in incrementi di minuti.

Così ci si esegue la query contare su questo tavolo:

time 
1 
2  
4 
3 
3 
2 
4 
1 
3 
2 

e sarebbe tornare:

0 1 
1 1 
2 3 
3 3 
4 2 

Contando ogni elemento.

Ho il sospetto che tu possa fare la stessa cosa con la tua tabella, e scassinarli di minuto in minuto, quindi eseguire un algoritmo su quello.

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

Da questo tutorial sul conteggio: http://www.techonthenet.com/sql/count.php (vicino alla fine).

Ecco una pagina simile da manuale di MySQL: http://dev.mysql.com/doc/refman/5.1/en/counting-rows.html

Quindi, se si dispone di un tavolo con un orologio calendario in esso (al minuto, permettendo binning accada per minuti):

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

Poi SQL

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

dovrebbe tornare

200901121435 7 
200901121538 5 

Sarà ancora necessario processo di questo post, ma il duro lavoro di gruppo e il conteggio viene fatto, e si tradurrà solo in poco più di 500k righe per anno (60 minuti, 24 ore, 365 giorni)

I post-elaborazione sarebbe:

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

Grazie. Se la soluzione "query singola" sopra risulta essere troppo dura sul server del database, probabilmente finirò per utilizzare questo metodo, in quanto è un ottimo modo per dividere il lavoro tra il server del database e il server del codice. – OverloadUT

+0

@recursive: ayup. –

0

Se mysql:

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

edit: non è sicuro se domanda originale: qualsiasi eventuale periodo di 60 minuti

+0

Sì, avrei dovuto renderlo più chiaro. Il problema è facile da risolvere se voglio prendere in considerazione ogni "ora di orologio" ma voglio considerare un periodo di 60 minuti. – OverloadUT

0

Se si utilizza MySQL:

SELECT DATE(postDate), HOUR(postDate), COUNT(*) AS n 
FROM posts 
GROUP BY DATE(postDate), HOUR(postDate) 
ORDER BY n DESC 
LIMIT 1 
4

categorizzazione funziona se si vuole guardare a intervalli, come 10:00-11:00 . Tuttavia, se hai avuto un'improvvisa raffica di interesse dalle 10:30 alle 11:30, allora sarà divisa in due contenitori, e quindi potrebbe essere nascosta da un numero minore di accessi che si è verificato per adattarsi interamente entro una singola ora di orologio.

L'unico modo per evitare questo problema è generare un elenco ordinato per ora e passarlo attraverso. Qualcosa di simile a questo:

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 

In questo modo avete solo bisogno di tenere una finestra di 1 ora in memoria piuttosto che l'intera lista.

+0

Sì, questo metodo è il miglior metodo che potrei inventare da solo. Lo farò se devo, ma speravo che ci potesse essere un modo per farlo senza dover passare attraverso centinaia di migliaia di oggetti. – OverloadUT

+0

Incredibile quante persone ignorano il fatto che il raggruppamento a ore non lo fa! –

+0

Solo poche centinaia di migliaia? Perl! –

2

Tratta il timestamp di ogni post come l'inizio di tale ora e conta tutti gli altri post che rientrano in quell'ora, incluso il post che l'ha avviato.Ordina le ore risultanti in ordine decrescente in base al numero di post in ciascuna di esse.

Fatto ciò, troverai la singola "ora" più alta che contiene il maggior numero di post, ma questo periodo di tempo potrebbe non essere esattamente di un'ora, potrebbe essere più breve (ma non più lungo).

Per ottenere un periodo "più carino", è possibile calcolare quanto è lungo, dividere per due e regolare l'inizio del periodo indietro di tale importo e la fine in avanti, questo "centrerà" i post all'interno del ora. Questo aggiustamento è garantito per non includere nuovi post, quindi il conteggio è ancora valido. Se i post sono abbastanza vicini da essere inclusi improvvisamente nel periodo dopo averlo espanso a un'ora, un punto precedente avrebbe avuto "il maggior numero di post" al posto di quello selezionato.

Se si tratta di una domanda SQL, è possibile riutilizzare l'SQL che Josh ha pubblicato here, basta sostituire la tabella dei Minuti con un altro collegamento alla tabella dei post.


Un altro metodo è possibile utilizzare è quello di utilizzare una finestra scorrevole.

Prima ordinare tutti i messaggi in base al timestamp. Tieni traccia dei post utilizzando un elenco, un elenco collegato potrebbe essere utilizzato per questo.

Ora, per ogni post, aggiungerlo alla fine dell'elenco. Quindi, per ogni post dall'inizio della lista, se quel post è più di un'ora prima del post che hai appena aggiunto, rimuovilo dall'elenco.

Dopo aver eseguito l'operazione in due passaggi per un singolo nuovo post nell'elenco, controllare se il numero di post nell'elenco è superiore a un massimo precedente, e se lo è, effettuare una copia dell'elenco o in Memorizza almeno il post che hai appena aggiunto.

Dopo aver finito, hai la "copia della lista" con il maggior numero di messaggi in un'ora, o hai il post che è la fine di una finestra di 1 ora che contiene il maggior numero di post.

pseudo-codice:

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 
2

Questo ha funzionato su un database MS-SQL piccolo test.

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 

Questo non è molto efficiente, controlli basati su un'ora da ciascun post.

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

Ecco una leggera variazione sull'attuazione dell'altro Josh questo rinuncia al tavolo immediato e utilizza un self join su se stesso alla ricerca di eventuali messaggi all'interno di un'ora di quella posta.

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 

Dal punto di vista delle prestazioni su un tavolo con solo 6 righe suo metodo che utilizza la funzione per generare la tabella intermiadte prese 16 secondi vs questa che era inferiore al secondo.

Non sono positivo se sarebbe possibile utilizzare questo per perdere un periodo di tempo valido poiché l'intervallo di tempo si basa sull'offset di ciascun post.

1

Questo lo farà.

SELEZIONA DateOfEvent HourBegin, DATEADD (hh, 1, DateOfEvent)) HourEnd, COUNT (*) AS NumEventsPerHour DA tEvents COME ENTRA tEvents AS B SU A.DateOfEvent> = B.DateOfEvents E DATEADD (hh, 1, A.DateOfEvent) < = B.DateOfEvent GROUP BY A.DateOfEvent