MySQL: группировка по дням подряд и количество групп - PullRequest
8 голосов
/ 17 августа 2011

У меня есть таблица базы данных, в которой хранятся регистрации каждого пользователя в городах. Мне нужно знать, сколько дней пользователь провел в городе, а затем, сколько посещений посетил город (посещение состоит из последовательных дней, проведенных в городе).

Итак, представьте, что у меня есть следующая таблица (упрощенная, содержащая только DATETIME s - тот же пользователь и город):

      datetime
-------------------
2011-06-30 12:11:46
2011-07-01 13:16:34
2011-07-01 15:22:45
2011-07-01 22:35:00
2011-07-02 13:45:12
2011-08-01 00:11:45
2011-08-05 17:14:34
2011-08-05 18:11:46
2011-08-06 20:22:12

Количество дней пребывания этого пользователя в этом городе будет 6 ( 30,06 , 01,07 , 02,07 , 01.08 , 05.08 , 06.08 ).

Я думал сделать это, используя SELECT COUNT(id) FROM table GROUP BY DATE(datetime)

Затем, для количества посещений этого пользователя этим городом, запрос должен вернуть 3 ( 30.06-02.07 , 01.08 , 05.08-06.08 ).

Проблема в том, что я понятия не имею, как мне построить этот запрос.

Любая помощь будет принята с благодарностью!

Ответы [ 5 ]

11 голосов
/ 17 августа 2011

Первый день каждого посещения можно найти, найдя чеки, в которых не было регистрации накануне.

select count(distinct date(start_of_visit.datetime))
from checkin start_of_visit
left join checkin previous_day
    on start_of_visit.user = previous_day.user
    and start_of_visit.city = previous_day.city
    and date(start_of_visit.datetime) - interval 1 day = date(previous_day.datetime)
where previous_day.id is null

В этом запросе есть несколько важных частей.

Во-первых, каждая регистрация присоединяется к любой регистрации за предыдущий день. Но поскольку это внешнее объединение, если в предыдущий день не было никакой проверки, правая сторона объединения будет иметь NULL результатов. Фильтрация WHERE происходит после объединения, поэтому она сохраняет только те чекины с левой стороны, где с правой стороны их нет. LEFT OUTER JOIN/WHERE IS NULL действительно удобен для поиска, где вещи не .

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

Редактировать: я просто перечитал предложенный вами вопрос по первому вопросу. Ваш запрос даст вам количество проверок на определенную дату, а не количество дат. Я думаю, что вы хотите что-то вроде этого:

select count(distinct date(datetime))
from checkin
where user='some user' and city='some city'
3 голосов
/ 31 августа 2011

Попробуйте применить этот код к своей задаче -

CREATE TABLE visits(
  user_id INT(11) NOT NULL,
  dt DATETIME DEFAULT NULL
);

INSERT INTO visits VALUES 
  (1, '2011-06-30 12:11:46'),
  (1, '2011-07-01 13:16:34'),
  (1, '2011-07-01 15:22:45'),
  (1, '2011-07-01 22:35:00'),
  (1, '2011-07-02 13:45:12'),
  (1, '2011-08-01 00:11:45'),
  (1, '2011-08-05 17:14:34'),
  (1, '2011-08-05 18:11:46'),
  (1, '2011-08-06 20:22:12'),
  (2, '2011-08-30 16:13:34'),
  (2, '2011-08-31 16:13:41');


SET @i = 0;
SET @last_dt = NULL;
SET @last_user = NULL;

SELECT v.user_id,
  COUNT(DISTINCT(DATE(dt))) number_of_days,
  MAX(days) number_of_visits
FROM
  (SELECT user_id, dt
        @i := IF(@last_user IS NULL OR @last_user <> user_id, 1, IF(@last_dt IS NULL OR (DATE(dt) - INTERVAL 1 DAY) > DATE(@last_dt), @i + 1, @i)) AS days,
        @last_dt := DATE(dt),
        @last_user := user_id
   FROM
     visits
   ORDER BY
     user_id, dt
  ) v
GROUP BY
  v.user_id;

----------------
Output:

+---------+----------------+------------------+
| user_id | number_of_days | number_of_visits |
+---------+----------------+------------------+
|       1 |              6 |                3 |
|       2 |              2 |                1 |
+---------+----------------+------------------+

Объяснение:

Чтобы понять, как это работает, давайте проверим подзапрос, вот онis.

SET @i = 0;
SET @last_dt = NULL;
SET @last_user = NULL;


SELECT user_id, dt,
        @i := IF(@last_user IS NULL OR @last_user <> user_id, 1, IF(@last_dt IS NULL OR (DATE(dt) - INTERVAL 1 DAY) > DATE(@last_dt), @i + 1, @i)) AS 

days,
        @last_dt := DATE(dt) lt,
        @last_user := user_id lu
FROM
  visits
ORDER BY
  user_id, dt;

Как видите, запрос возвращает все строки и выполняет ранжирование по количеству посещений.Это известный метод ранжирования на основе переменных, обратите внимание, что строки упорядочены по полям пользователя и даты.Этот запрос вычисляет посещения пользователей и выводит следующий набор данных, где в столбце days указан ранг для количества посещений -

+---------+---------------------+------+------------+----+
| user_id | dt                  | days | lt         | lu |
+---------+---------------------+------+------------+----+
|       1 | 2011-06-30 12:11:46 |    1 | 2011-06-30 |  1 |
|       1 | 2011-07-01 13:16:34 |    1 | 2011-07-01 |  1 |
|       1 | 2011-07-01 15:22:45 |    1 | 2011-07-01 |  1 |
|       1 | 2011-07-01 22:35:00 |    1 | 2011-07-01 |  1 |
|       1 | 2011-07-02 13:45:12 |    1 | 2011-07-02 |  1 |
|       1 | 2011-08-01 00:11:45 |    2 | 2011-08-01 |  1 |
|       1 | 2011-08-05 17:14:34 |    3 | 2011-08-05 |  1 |
|       1 | 2011-08-05 18:11:46 |    3 | 2011-08-05 |  1 |
|       1 | 2011-08-06 20:22:12 |    3 | 2011-08-06 |  1 |
|       2 | 2011-08-30 16:13:34 |    1 | 2011-08-30 |  2 |
|       2 | 2011-08-31 16:13:41 |    1 | 2011-08-31 |  2 |
+---------+---------------------+------+------------+----+

Затем мы группируем эти данные по пользователю и используем статистические функции: 'COUNT (DISTINCT)(DATE (dt))) '- подсчитывает количество дней. MAX (days)' - количество посещений. Это максимальное значение для поля days из нашего подзапроса.

Это все;)

1 голос
/ 02 сентября 2011

Как пример данных, предоставленных Devart, внутренний «PreQuery» работает с переменными sql.По умолчанию для @LUser установлено значение -1 (вероятный несуществующий идентификатор пользователя), тест IF () проверяет любые различия между последним пользователем и текущим.Как только новый пользователь получает значение 1 ... Кроме того, если последняя дата составляет более 1 дня с новой даты регистрации, он получает значение 1. Затем последующие столбцы сбрасывают@LUser и @LDate к значению входящей записи, только что проверенной для следующего цикла.Затем внешний запрос просто суммирует их и подсчитывает их для окончательных правильных результатов для набора данных Devart

User ID    Distinct Visits   Total Days
1           3                 9
2           1                 2

select PreQuery.User_ID,
       sum( PreQuery.NextVisit ) as DistinctVisits,
       count(*) as TotalDays
   from
      (  select v.user_id,
               if( @LUser <> v.User_ID OR @LDate < ( date( v.dt ) - Interval 1 day ), 1, 0 ) as NextVisit,
               @LUser := v.user_id,
               @LDate := date( v.dt )
            from 
               Visits v,
               ( select @LUser := -1, @LDate := date(now()) ) AtVars 
            order by
               v.user_id,
               v.dt  ) PreQuery
    group by 
       PreQuery.User_ID
0 голосов
/ 30 августа 2011

Я думаю, вы должны рассмотреть вопрос об изменении структуры базы данных.Вы можете добавить посещения таблицы и visit_id в свою таблицу регистрации.Каждый раз, когда вы хотите зарегистрировать новую регистрацию, вы проверяете, есть ли регистрация в день назад.Если да, то вы добавляете новую регистрацию с помощью visit_id из вчерашней регистрации.Если нет, то вы добавляете новые посещения и новые проверки с новым visit_id.

Тогда вы можете получить данные в одном запросе, например: SELECT COUNT(id) AS number_of_days, COUNT(DISTINCT visit_id) number_of_visits FROM checkin GROUP BY user, city

Это не очень оптимально, новсе же лучше, чем делать что-либо с текущей структурой, и это будет работать.Также, если результаты могут быть отдельными запросами, это будет работать очень быстро.

Но, конечно, недостатками является то, что вам нужно будет изменить структуру базы данных, сделать еще несколько сценариев и преобразовать текущие данные в новую структуру (т.е. вам нужно будет добавитьвизит_ид к текущим данным).

0 голосов
/ 17 августа 2011

для первой подзадачи:

select count(*) 
from (
select TO_DAYS(p.d)
from p
group by TO_DAYS(p.d)
) t
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...