La siguiente tabla de Historial de usuarios contiene un registro por cada día que un usuario determinado ha accedido a un sitio web (en un período UTC de 24 horas). Tiene muchos miles de registros, pero solo un registro por día por usuario. Si el usuario no ha accedido al sitio web para ese día, no se generará ningún registro.
Id UserId CreationDate ------ ------ ------------ 750997 12 2009-07-07 18: 42: 20.723 750998 15 2009-07-07 18: 42: 20.927 751000 19 2009-07-07 18: 42: 22.283
Lo que estoy buscando es una consulta SQL en esta tabla con buen rendimiento , que me diga qué ID de usuario ha accedido al sitio web durante (n) días continuos sin perder un día.
En otras palabras, ¿cuántos usuarios tienen (n) registros en esta tabla con fechas secuenciales (día anterior o posterior) ? Si falta algún día de la secuencia, la secuencia se interrumpe y debería reiniciarse nuevamente en 1; Estamos buscando usuarios que hayan logrado un número continuo de días aquí sin brechas.
Cualquier parecido entre esta consulta y una insignia de Stack Overflow es pura coincidencia, por supuesto ... :)
fuente
Respuestas:
La respuesta es obviamente:
EDITAR:
Bien, aquí está mi respuesta seria:
EDITAR:
[Jeff Atwood] Esta es una gran solución rápida y merece ser aceptada, pero la solución de Rob Farley también es excelente y posiblemente incluso más rápida (!). ¡Por favor échale un vistazo también!
fuente
ON uh2.CreationDate >= uh1.CreationDate AND uh2.CreationDate < DATEADD(dd, DATEDIFF(dd, 0, uh1.CreationDate) + @days, 0)
que significa "Todavía no el día 31 después?". También significa que puede omitir el cálculo de @ segundos.¿Qué tal (y asegúrese de que la declaración anterior terminara con un punto y coma):
La idea es que si tenemos una lista de los días (como un número) y un número de fila, los días perdidos hacen que el desplazamiento entre estas dos listas sea un poco más grande. Por lo tanto, estamos buscando un rango que tenga un desplazamiento constante.
Podrías usar "ORDER BY NumConsecutivoDays DESC" al final de esto, o decir "HAVING count (*)> 14" para un umbral ...
Sin embargo, no he probado esto, solo escribiéndolo en la parte superior de mi cabeza. Esperemos que funcione en SQL2005 y en adelante.
... y sería muy útil con un índice en tablename (UserID, CreationDate)
Editado: resulta que Offset es una palabra reservada, así que usé TheOffset en su lugar.
Editado: la sugerencia de usar COUNT (*) es muy válida; debería haberlo hecho en primer lugar, pero no estaba pensando realmente. Anteriormente usaba dateiff (day, min (CreationDate), max (CreationDate)) en su lugar.
Robar
fuente
Si puede cambiar el esquema de la tabla, sugeriría agregar una columna
LongestStreak
a la tabla que establecería en la cantidad de días secuenciales que terminan enCreationDate
. Es fácil actualizar la tabla en el momento de inicio de sesión (similar a lo que está haciendo ya, si no existen filas del día actual, comprobará si existe alguna fila para el día anterior. Si es cierto, aumentaráLongestStreak
el nueva fila, de lo contrario, lo establecerá en 1.)La consulta será obvia después de agregar esta columna:
fuente
Algunos SQL muy bien expresivos en la línea de:
Suponiendo que tiene una función agregada definida por el usuario, algo similar a (tenga cuidado con esto es defectuoso):
fuente
Parece que podría aprovechar el hecho de que ser continuo durante n días requeriría que haya n filas.
Entonces algo como:
fuente
Hacer esto con una sola consulta SQL me parece demasiado complicado. Permítanme dividir esta respuesta en dos partes.
ejecute un trabajo cron diario que verifique a cada usuario si ha iniciado sesión hoy y luego incrementa un contador si lo tiene o lo establece en 0 si no lo ha hecho.
- Exporte esta tabla a un servidor que no ejecute su sitio web y no sea necesario por un tiempo. ;)
- Ordénelo por usuario, luego fecha.
- Revíselo secuencialmente, mantenga un contador ...
fuente
Si esto es tan importante para usted, obtenga este evento y maneje una tabla para darle esta información. No es necesario matar la máquina con todas esas consultas locas.
fuente
Puede usar un CTE recursivo (SQL Server 2005+):
fuente
Joe Celko tiene un capítulo completo sobre esto en SQL para Smarties (llamándolo Runs and Sequences). No tengo ese libro en casa, así que cuando llegue al trabajo ... en realidad responderé esto. (suponiendo que la tabla de historial se llama dbo.UserHistory y el número de días es @Days)
Otra pista es del blog de SQL Team sobre carreras
La otra idea que he tenido, pero no tengo un servidor SQL a mano para trabajar aquí, es usar un CTE con un ROW_NUMBER particionado como este:
Es probable que lo anterior sea MUCHO MÁS DIFÍCIL de lo que debe ser, pero se deja como un cosquilleo cerebral para cuando tienes alguna otra definición de "una carrera" que solo las fechas.
fuente
Un par de opciones de SQL Server 2012 (suponiendo N = 100 a continuación).
Aunque con mis datos de muestra, lo siguiente resultó más eficiente
Ambos se basan en la restricción establecida en la pregunta de que hay como máximo un registro por día por usuario.
fuente
¿Algo como esto?
fuente
Utilicé una propiedad matemática simple para identificar quién accedió consecutivamente al sitio. Esta propiedad es que debe tener la diferencia de días entre el primer acceso y la última vez igual al número de registros en el registro de la tabla de acceso.
Aquí hay un script SQL que probé en Oracle DB (también debería funcionar en otros DB):
Script de preparación de tabla:
fuente
La declaración
cast(convert(char(11), @startdate, 113) as datetime)
elimina la parte de hora de la fecha, por lo que comenzamos a medianoche.Supongo también que las columnas
creationdate
yuserid
están indexadas.Me acabo de dar cuenta de que esto no le dirá a todos los usuarios y sus días consecutivos totales. Pero le diremos qué usuarios habrán estado visitando un número determinado de días desde la fecha de su elección.
Solución revisada:
He comprobado esto y consultará a todos los usuarios y todas las fechas. Se basa en la primera solución de Spencer (¿broma?) , Pero la mía funciona.
Actualización: mejoró el manejo de la fecha en la segunda solución.
fuente
Esto debería hacer lo que quiere, pero no tengo suficientes datos para probar la eficiencia. Lo complicado de CONVERTIR / PISO es quitar la porción de tiempo del campo de fecha y hora. Si está utilizando SQL Server 2008, podría usar CAST (x.CreationDate AS DATE).
Guion de creacion
fuente
Spencer casi lo hizo, pero este debería ser el código de trabajo:
fuente
Fuera de mi cabeza, MySQLish:
No probado, y casi seguramente necesita alguna conversión para MSSQL, pero creo que eso da algunas ideas.
fuente
¿Qué tal uno usando las tablas de Tally? Sigue un enfoque más algorítmico, y el plan de ejecución es muy sencillo. Rellene la tabla de conteo con números del 1 al 'MaxDaysBehind' que desea escanear la tabla (es decir, 90 buscará 3 meses de retraso, etc.).
fuente
Ajustando un poco la consulta de Bill. Es posible que deba truncar la fecha antes de agrupar para contar solo un inicio de sesión por día ...
EDITADO para usar DATEADD (dd, DATEDIFF (dd, 0, CreationDate), 0) en lugar de convertir (char (10), CreationDate, 101).
@IDisposable Estaba buscando usar datepart antes, pero era demasiado vago para buscar la sintaxis, así que pensé que id usar convert en su lugar. No sé que tuvo un impacto significativo ¡Gracias! ahora sé.
fuente
asumiendo un esquema que dice así:
esto extraerá rangos contiguos de una secuencia de fechas con espacios en blanco.
fuente