2013-04-15 5 views
2

Простая таблица:SQL возвращают последовательные записи

ForumPost 
-------------- 
ID (int PK) 
UserID (int FK) 
Date (datetime) 

Что я ищу, чтобы вернуться, сколько раз конкретный пользователь сделал по крайней мере один пост в день п дней подряд.

Пример:

User 15844 has posted at least 1 post a day for 30 consecutive days 10 times 

Я помечено этот вопрос с Linq/лямбда, а также решение было бы также быть большим. Я знаю, что могу решить эту проблему, итерации всех записей пользователей, но это происходит медленно.

+1

Какая СУБД вы используете? Postgres? Oracle? –

+0

SQL Server 2008 r2 –

+0

использовать подзапрос для всех сообщений в диапазоне дат 30 дней назад, по дате и подсчету. Проверьте, если 30? –

ответ

4

Существует удобный трюк вы можете использовать с помощью ROW_NUMBER() найти последовательные записи, представьте себе следующий набор дат, с их row_number (начиная с 0):

Date  RowNumber 
20130401 0 
20130402 1 
20130403 2 
20130404 3 
20130406 4 
20130407 5 

Для последовательных записей, если вычесть row_number от значения вы получите тот же результат. например

Date  RowNumber date - row_number 
20130401 0   20130401 
20130402 1   20130401 
20130403 2   20130401 
20130404 3   20130401 
20130406 4   20130402 
20130407 5   20130402 

Вы можете группировать по date - row_number, чтобы получить наборы последовательных дней (то есть первые 4 записи, а последние 2 записей).

Чтобы применить это к вашему примеру, вы должны использовать:

WITH Posts AS 
( SELECT FirstPost = DATEADD(DAY, 1 - ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY [Date]), [Date]), 
      UserID, 
      Date 
    FROM ( SELECT DISTINCT UserID, [Date] = CAST(Date AS [Date]) 
       FROM ForumPost 
      ) fp 
), Posts2 AS 
( SELECT FirstPost, 
      UserID, 
      Days = COUNT(*), 
      LastDate = MAX(Date) 
    FROM Posts 
    GROUP BY FirstPost, UserID 
) 
SELECT UserID, ConsecutiveDates = MAX(Days) 
FROM Posts2 
GROUP BY UserID; 

Example on SQL Fiddle (simple with just most consecutive days per user)

Further example to show how to get all consecutive periods

EDIT

Я не думаю, что выше вполне ответил на вопрос, это даст сколько раз пользователь отправил на, или в течение п последовательных дней:

WITH Posts AS 
( SELECT FirstPost = DATEADD(DAY, 1 - ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY [Date]), [Date]), 
      UserID, 
      Date 
    FROM ( SELECT DISTINCT UserID, [Date] = CAST(Date AS [Date]) 
       FROM ForumPost 
      ) fp 
), Posts2 AS 
( SELECT FirstPost, 
      UserID, 
      Days = COUNT(*), 
      FirstDate = MIN(Date), 
      LastDate = MAX(Date) 
    FROM Posts 
    GROUP BY FirstPost, UserID 
) 
SELECT UserID, [Times Over N Days] = COUNT(*) 
FROM Posts2 
WHERE Days >= 30 
GROUP BY UserID; 

Example on SQL Fiddle

1

Ваше конкретное приложение делает это довольно просто, я думать. Если у вас есть «n» отдельных дат в «n'-дневном интервале», то «n» разных дат должно быть быть последовательным.

Пролистайте нижнюю часть для общего решения, которое требует только общих выражений таблицы и перехода на PostgreSQL. (Шучу. Я реализовал в PostgreSQL, потому что я не хватает времени.)

create table ForumPost (
    ID integer primary key, 
    UserID integer not null, 
    post_date date not null 
); 

insert into forumpost values 
(1, 1, '2013-01-15'), 
(2, 1, '2013-01-16'), 
(3, 1, '2013-01-17'), 
(4, 1, '2013-01-18'), 
(5, 1, '2013-01-19'), 
(6, 1, '2013-01-20'), 
(7, 1, '2013-01-21'), 

(11, 2, '2013-01-15'), 
(12, 2, '2013-01-16'), 
(13, 2, '2013-01-17'), 
(16, 2, '2013-01-17'), 
(14, 2, '2013-01-18'), 
(15, 2, '2013-01-19'), 

(21, 3, '2013-01-17'), 
(22, 3, '2013-01-17'), 
(23, 3, '2013-01-17'), 
(24, 3, '2013-01-17'), 
(25, 3, '2013-01-17'), 
(26, 3, '2013-01-17'), 
(27, 3, '2013-01-17'); 

Теперь давайте посмотрим на выходе этого запроса. Для краткости я просматриваю 5-дневные интервалы, а не 30-дневные интервалы.

select userid, count(distinct post_date) distinct_dates 
from forumpost 
where post_date between '2013-01-15' and '2013-01-19' 
group by userid; 

USERID DISTINCT_DATES 
1  5 
2  5 
3  1 

Для пользователей, которые соответствуют критериям, число различных дат в этом 5-дневном интервале должны быть 5, верно? Поэтому нам просто нужно добавить эту логику в предложение HAVING.

select userid, count(distinct post_date) distinct_dates 
from forumpost 
where post_date between '2013-01-15' and '2013-01-19' 
group by userid 
having count(distinct post_date) = 5; 

USERID DISTINCT_DATES 
1  5 
2  5 

Более общее решение

Это действительно не имеет смысла говорить, что, если вы разместите каждый день от 2013-01-01 до 2013-01-31, вы 'отправил 30 дней подряд 2 раза. Вместо этого я ожидаю, что часы начнутся в 2013-01-31 годах. Мои извинения за внедрение в PostgreSQL; Я попытаюсь реализовать в T-SQL позже.

with first_posts as (
    select userid, min(post_date) first_post_date 
    from forumpost 
    group by userid 
), 
period_intervals as (
    select userid, first_post_date period_start, 
     (first_post_date + interval '4' day)::date period_end 
    from first_posts 
), user_specific_intervals as (
    select 
    userid, 
    (period_start + (n || ' days')::interval)::date as period_start, 
    (period_end + (n || ' days')::interval)::date as period_end 
    from period_intervals, generate_series(0, 30, 5) n 
) 
select userid, period_start, period_end, 
     (select count(distinct post_date) 
     from forumpost 
     where forumpost.post_date between period_start and period_end 
      and userid = forumpost.userid) distinct_dates 
from user_specific_intervals 
order by userid, period_start; 
Смежные вопросы