SQL получить диапазон дат между заданными датами, сгруппированный по другому столбцу в таблице - PullRequest
1 голос
/ 21 февраля 2020

В этой таблице -

----------------------------------------------
ID  | user   | type   | timestamp
----------------------------------------------
1   | 1      | 1      | 2019-02-08 15:00:00
2   | 1      | 3      | 2019-02-15 15:00:00
3   | 1      | 2      | 2019-03-06 15:00:00
4   | 2      | 3      | 2019-02-01 15:00:00
5   | 2      | 1      | 2019-02-06 15:00:00
6   | 3      | 1      | 2019-01-10 15:00:00
7   | 3      | 4      | 2019-02-08 15:00:00
8   | 3      | 3      | 2019-02-24 15:00:00
9   | 3      | 2      | 2019-03-04 15:00:00
10  | 3      | 3      | 2019-03-05 15:00:00

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

Например: для заданного диапазона 2019-02-01 до 2019-03-04 , выходной сигнал должен быть

--------------------------------
user   | type   | No. of days
--------------------------------
1      | 1      | 7
1      | 3      | 17
2      | 3      | 6
3      | 1      | 29
2      | 4      | 16
2      | 3      | 8

Использование может переключаться между типами в любой день, но мне нужно захватить все эти переключатели и количество дней, в течение которых пользователь находился в типе. В настоящее время я решаю это путем получения всех значений и фильтрации вручную в JS. Есть ли способ сделать это по запросу SQL? Я использую MYSQL 5.7.23.

РЕДАКТИРОВАТЬ:

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

--------------------------------
user | type | No. of days
--------------------------------
   1 |    1 |          7
   1 |    3 |         19
   2 |    3 |          5
   3 |    1 |         29
   3 |    2 |          1
   3 |    3 |          8
   3 |    4 |         16

Ответы [ 4 ]

2 голосов
/ 21 февраля 2020

Используйте lead(), а затем datediff() и sum() и множество сравнений дат:

select user, type,
       sum(datediff( least(next_ts, '2019-03-04'), greatest(timestamp, '2019-02-01'))
from (select t.*,
             lead(timestamp, 1, '2019-03-04') over (partition by user order by timestamp) as next_ts
      from t
     ) t
where next_ts >= '2019-02-01' and
      timestamp <= '2019-03-04'
group by user, type;

РЕДАКТИРОВАТЬ:

В более старых версиях вы можете использовать:

select user, type,
       sum(datediff( least(next_ts, '2019-03-04'), greatest(timestamp, '2019-02-01'))
from (select t.*,
             (select coalesce(min(timestamp), '2019-03-04')
               from t t2
               where t2.user = t.user and t2.timestamp > t.timestamp
             ) as next_ts
      from t
     ) t
where next_ts >= '2019-02-01' and
      timestamp <= '2019-03-04'
group by user, type;
1 голос
/ 21 февраля 2020

Вот один из способов сделать это в MysQL 5.7 и без пользовательских переменных:

select 
    t.user,
    t.type,
    sum(datediff(
        greatest(tlead.timestamp, '2019-02-01'), 
        least(t.timestamp, '2019-03-04'))
    ) no_of_days
from mytable t
inner join mytable tlead 
    on  tlead.user = t.user
    and tlead.timestamp > t.timestamp
    and not exists (
        select 1
        from mytable t1
        where 
            t1.user = t.user 
            and t1.timestamp > t.timestamp
            and t1.timestamp < tlead.timestamp
    )
where tlead.timestamp >= '2019-02-01' and t.timestamp <= '2019-03-04'
group by t.user, t.type
order by t.user, t.type

По сути, это эмулирует lead() с самостоятельным соединением и условием not exists: псевдоним таблицы tlead - это запись next для того же пользователя. Остальное - это фильтрация, агрегация и вычисление разностей дат в целевом диапазоне дат.

Демонстрация на DB Fiddle - результаты не совсем такие же, как у вас, но Я подозреваю, что они на самом деле правы:

user | type | no_of_days
---: | ---: | ---------:
   1 |    1 |          7
   1 |    3 |         19
   2 |    3 |          5
   3 |    1 |         29
   3 |    2 |          1
   3 |    3 |          8
   3 |    4 |         16
0 голосов
/ 21 февраля 2020

Вы получаете не совсем то, что хотели, но это точно

SELECT 
  `user`
  ,`type`
  ,dategone `No. of days`
  FROM
(SELECT 
  `type`,
  IF(@id = `user`,DATEDIFF(`timestamp` , @days), -1) dategone #
  ,@id := `user`  `user`
  ,@days := `timestamp` 
 FROM
   (SELECT 
      `D`, `user`, `type`, `timestamp`
    From table1
    ORDER BY `user` ASC, `timestamp`  ASC) a
   , (SELECT @days :=0) b, (SELECT @id :=0) c) d
WHERE dategone > -1;
CREATE TABLE table1 (
  `D` INTEGER,
  `user` INTEGER,
  `type` INTEGER,
  `timestamp` VARCHAR(19)
);

INSERT INTO table1
  (`D`, `user`, `type`, `timestamp`)
VALUES
  ('1', '1', '1', '2019-02-08 15:00:00'),
  ('2', '1', '3', '2019-02-15 15:00:00'),
  ('3', '1', '2', '2019-03-06 15:00:00'),
  ('4', '2', '3', '2019-02-01 15:00:00'),
  ('5', '2', '1', '2019-02-06 15:00:00'),
  ('6', '3', '1', '2019-01-10 15:00:00'),
  ('7', '3', '4', '2019-02-08 15:00:00'),
  ('8', '3', '3', '2019-02-24 15:00:00'),
  ('9', '3', '2', '2019-03-04 15:00:00'),
  ('10', '3', '3', '2019-03-05 15:00:00');
✓

✓
SELECT 
  `user`
  ,`type`
  ,dategone `No. of days`
  FROM
(SELECT 
`type`,
IF(@id = `user`,DATEDIFF(`timestamp` , @days), -1) dategone #
,@id := `user`  `user`
,@days := `timestamp` 
FROM
(SELECT 
  `D`, `user`, `type`, `timestamp`
From table1
ORDER BY `user` ASC, `timestamp`  ASC) a, (SELECT @days :=0) b, (SELECT @id :=0) c) d
WHERE dategone > -1;
user | type | No. of days
---: | ---: | ----------:
   1 |    3 |           7
   1 |    2 |          19
   2 |    1 |           5
   3 |    4 |          29
   3 |    3 |          16
   3 |    2 |           8
   3 |    3 |           1

дБ <> скрипка здесь

0 голосов
/ 21 февраля 2020

Это должно дать вам то, что вы хотите:

select id, user, type, time_stamp, (
    select datediff(min(time_stamp), t1.time_stamp)
    from table1 as t2
    where t2.user = t1.user 
    and   t2.time_stamp > t1.time_stamp
    ) as days
from table1 as t1
where 0 < (select count(*) from table1 as t3 where t3.user = t1.user
           and   t3.time_stamp > t1.time_stamp )
order by id;

Работа в скрипке здесь: http://sqlfiddle.com/#! 9 / 347ab5 / 26

Если вы также хотите, чтобы «последняя» строка для каждого пользователя использовалась в этом варианте:

select id, user, type, time_stamp, (
    select datediff(coalesce(min(time_stamp),current_timestamp()) , t1.time_stamp)
    from table1 as t2
    where t2.user = t1.user 
    and   t2.time_stamp > t1.time_stamp
    ) as days
from table1 as t1
order by id;
...