2009-02-03 8 views
9

Tengo una tabla de base de datos con cientos de miles de publicaciones en el foro, y me gustaría saber qué período de una hora contiene la mayor cantidad de publicaciones.¿Cómo encontrar el período de una hora con la mayoría de los puntos de datos?

Podía avanzar lentamente un minuto a la vez, manteniendo una variedad de marcas de tiempo y haciendo un seguimiento de qué hora tenía más, pero creo que hay una forma mucho mejor de hacerlo. Realizaré esta operación en un año de publicaciones, por lo que consultar cada minuto en un año parece bastante horrible.

Idealmente, habría una manera de hacer esto dentro de una única consulta de base de datos.

+0

ACLARACIÓN: Quiero considerar CUALQUIER período de 60 minutos. Por lo tanto, los métodos que usan la parte "hora" de la marca de tiempo no funcionarán. – OverloadUT

+0

Veo ahora, ¿qué base de datos está usando? – JoshBerke

Respuesta

5

Dada una mesa llena cada minuto en el año que está interesado en Minutes y una mesa Posts con una columna 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 

Para resolver generar la tabla de minutos, puede utilizar una función como ufn_GenerateIntegers. a continuación, la función se convierte en

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 

simplemente hice una prueba de funcionamiento con cerca de 5000 mensajes aleatorios y tardó 16 segundos en mi máquina. Por lo tanto, no es trivial, pero no ridícula para la consulta puntual ocasional. Afortunadamente, este es un punto de datos que puede calcular uno al día o incluso una vez al mes y caché si desea mostrar el valor con frecuencia.

Eche un vistazo a lassevk's improvement.

+0

¡Ah, ja! ¡Este es el tipo de cosa que estaba buscando! Tendré que probar para ver cuánto tiempo llevará esta consulta ya que mi servidor de base de datos tiene muchos menos recursos que mi servidor php, pero definitivamente es más en la dirección que esperaba. – OverloadUT

+0

Sí, este podría llevar mucho tiempo; no lo he probado en absoluto. Pero he hecho cosas similares con resoluciones de un día. – Eclipse

+0

Estoy bastante seguro de que leí un Daily WTF sobre este diseño hace unos días ... – rmeador

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

Esto se traduce en una consulta de base de datos de O (n), y un O (n) mayor búsqueda de tiempo, para un total complejidad de O (2n) (que, por supuesto, sigue siendo O (n)) :

Utilice un comando count distinct en SQL que 'bin' elementos para usted en incrementos de minutos.

Así que es ejecutar la consulta recuento en esta tabla:

time 
1 
2  
4 
3 
3 
2 
4 
1 
3 
2 

y sería volver:

0 1 
1 1 
2 3 
3 3 
4 2 

Al contar cada elemento.

Sospecho que usted puede hacer lo mismo con su mesa, y bin por minuto, luego ejecutar un algoritmo sobre eso.

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

De este tutorial en cuenta: http://www.techonthenet.com/sql/count.php (cerca del final).

Aquí es una página similar desde el manual de MySQL: http://dev.mysql.com/doc/refman/5.1/en/counting-rows.html

Así que si usted tiene una mesa con un timedate en ella (al minuto, lo que permite hurgar en la basura suceda por minuto):

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

Entonces SQL

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

debe devolver

200901121435 7 
200901121538 5 

todavía tendrá al proceso de publicar esto, pero el duro trabajo de la agrupación y conteo se realiza, y sólo dará lugar a poco más de 500 mil filas por año (60 minutos, 24 horas, 365 días)

Los el procesamiento posterior sería:

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

Gracias. Si la solución de "consulta única" anterior termina siendo demasiado difícil en el servidor de la base de datos, probablemente termine usando este método, ya que es una gran manera de dividir el trabajo entre el servidor de la base de datos y el servidor de código. – OverloadUT

+0

@recursive: ayup. –

0

Si MySQL:

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

edición: no está seguro de si la pregunta original significa cualquier posible período de 60 minutos

+0

Sí, debería haber dejado eso más claro. El problema es fácil de resolver si solo quiero considerar cada "hora del reloj", pero quiero considerar cualquier período de 60 minutos. – OverloadUT

0

Si el uso de MySQL:

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

Agrupación funcionará si desea buscar en intervalos tales como 10:00-11:00 . Sin embargo, si tuvo una súbita oleada de interés de 10:30 a 11:30, se dividirá en dos intervalos y, por lo tanto, puede ocultarse por un número menor de coincidencias que se ajustaron por completo en una sola hora.

La única manera de evitar este problema es generar una lista ordenada por tiempo y recorrerla. Algo como esto:

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 

De esta manera solo necesita mantener una ventana de 1 hora en la memoria en lugar de toda la lista.

+0

Sí, este método es el mejor método que podría encontrar por mi cuenta. Lo haré si es necesario, pero esperaba que hubiera una forma de hacerlo sin tener que pasar por cientos de miles de elementos. – OverloadUT

+0

¡Es sorprendente cuánta gente ignora el hecho de que la agrupación por hora no lo hace! –

+0

¿Solo unos pocos cientos de miles? Perl! –

2

Trate la marca de tiempo de cada publicación como el inicio de dicha hora y cuente todas las demás publicaciones que caigan dentro de esa hora, incluida la publicación que la inició.Ordene las horas resultantes en orden descendente por la cantidad de publicaciones en cada una de ellas.

Una vez hecho esto, encontrará la "hora" más alta que tiene la mayor cantidad de publicaciones, pero este período de tiempo puede no ser exactamente de una hora, puede ser más corto (pero nunca más).

Para obtener un período "más bonito", puede calcular cuánto tiempo realmente es, dividir por dos y ajustar el inicio del período en esa cantidad y el final hacia adelante, esto "centrará" las publicaciones dentro del hora. Se garantiza que este ajuste no incluirá ninguna publicación nueva, por lo que el recuento sigue siendo válido. Si las publicaciones están lo suficientemente cerca como para incluirlas repentinamente en el período posterior a la expansión a una hora, un punto anterior tendría "la mayor cantidad de publicaciones" en lugar de la que seleccionó.

Si esta es una pregunta de SQL, puede reutilizar el SQL que Josh publicó here, simplemente reemplace la tabla de Minutos con otro enlace a su tabla de mensajes.


Otro método puede utilizar es el uso de una ventana deslizante.

Primero ordena todas las publicaciones de acuerdo con la marca de tiempo. Mantenga un registro de las publicaciones usando una lista, una lista vinculada podría ser utilizada para esto.

Ahora, para cada publicación, agréguela al final de la lista. Luego, para cada publicación desde el inicio de la lista, si esa publicación es más de una hora antes de la publicación que acaba de agregar, elimínela de la lista.

Después de hacer la operación en 2 pasos para una nueva publicación en la lista, verifique si el número de publicaciones en la lista es más que un máximo anterior, y si lo es, haga una copia de la lista o al Almacene la publicación que acaba de agregar.

Una vez que haya terminado, obtendrá la "copia de la lista" con la mayor cantidad de publicaciones en una hora o la publicación que sea el final de una ventana de 1 hora que contenga la mayor cantidad de publicaciones.

Pseudo-código:

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

Esto funcionó en una base de datos MS-SQL pequeña prueba.

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 

Esto no es muy eficiente, los controles se basan en una hora de cada publicación.

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

Aquí hay una ligera variación en la aplicación del otro Josh esta renuncia a la mesa inmediata y utiliza una combinación de autorrestricción sobre sí mismo en busca de cualquier puestos dentro de una hora de que un puesto.

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 

Desde la perspectiva del rendimiento sobre una mesa con 6 filas sólo su método que utiliza la función para generar la tabla intermiadte tomó 16 segundos frente a éste que era por debajo del segundo.

No estoy seguro de si sería posible usar esto para omitir un período de tiempo válido ya que el intervalo de tiempo se basa en el desplazamiento de cada publicación.

1

Esto lo hará.

SELECT DateOfEvent HourBegin, DATEADD (hh, 1, DateOfEvent)) HourEnd, COUNT (*) AS NumEventsPerHour DE tEvents COMO JOIN tEvents como b EN A.DateOfEvent> = B.DateOfEvents Y DATEADD (hh, 1, A.DateOfEvent) < = B.DateOfEvent GROUP BY A.DateOfEvent

Cuestiones relacionadas