SQL to determine minimum sequential days of access? SQL to determine minimum sequential days of access? sql-server sql-server

SQL to determine minimum sequential days of access?


How about (and please make sure the previous statement ended with a semi-colon):

WITH numberedrows     AS (SELECT ROW_NUMBER() OVER (PARTITION BY UserID                                        ORDER BY CreationDate)                - DATEDIFF(day,'19000101',CreationDate) AS TheOffset,                CreationDate,                UserID         FROM   tablename)SELECT MIN(CreationDate),       MAX(CreationDate),       COUNT(*) AS NumConsecutiveDays,       UserIDFROM   numberedrowsGROUP  BY UserID,          TheOffset  

The idea being that if we have list of the days (as a number), and a row_number, then missed days make the offset between these two lists slightly bigger. So we're looking for a range that has a consistent offset.

You could use "ORDER BY NumConsecutiveDays DESC" at the end of this, or say "HAVING count(*) > 14" for a threshold...

I haven't tested this though - just writing it off the top of my head. Hopefully works in SQL2005 and on.

...and would be very much helped by an index on tablename(UserID, CreationDate)

Edited: Turns out Offset is a reserved word, so I used TheOffset instead.

Edited: The suggestion to use COUNT(*) is very valid - I should've done that in the first place but wasn't really thinking. Previously it was using datediff(day, min(CreationDate), max(CreationDate)) instead.

Rob


The answer is obviously:

SELECT DISTINCT UserIdFROM UserHistory uh1WHERE (       SELECT COUNT(*)        FROM UserHistory uh2        WHERE uh2.CreationDate        BETWEEN uh1.CreationDate AND DATEADD(d, @days, uh1.CreationDate)      ) = @days OR UserId = 52551

EDIT:

Okay here's my serious answer:

DECLARE @days intDECLARE @seconds bigintSET @days = 30SET @seconds = (@days * 24 * 60 * 60) - 1SELECT DISTINCT UserIdFROM (    SELECT uh1.UserId, Count(uh1.Id) as Conseq    FROM UserHistory uh1    INNER JOIN UserHistory uh2 ON uh2.CreationDate         BETWEEN uh1.CreationDate AND             DATEADD(s, @seconds, DATEADD(dd, DATEDIFF(dd, 0, uh1.CreationDate), 0))        AND uh1.UserId = uh2.UserId    GROUP BY uh1.Id, uh1.UserId    ) as TblWHERE Conseq >= @days

EDIT:

[Jeff Atwood] This is a great fast solution and deserves to be accepted, but Rob Farley's solution is also excellent and arguably even faster (!). Please check it out too!


If you can change the table schema, I'd suggest adding a column LongestStreak to the table which you'd set to the number of sequential days ending to the CreationDate. It's easy to update the table at login time (similar to what you are doing already, if no rows exist of the current day, you'll check if any row exists for the previous day. If true, you'll increment the LongestStreak in the new row, otherwise, you'll set it to 1.)

The query will be obvious after adding this column:

if exists(select * from table          where LongestStreak >= 30 and UserId = @UserId)   -- award the Woot badge.