Получить различные последовательные диапазоны дат из перекрывающихся диапазонов дат - PullRequest
0 голосов
/ 16 октября 2018

Мне нужно получить список диапазонов дат, которые НЕ перекрываются друг с другом, из списка перекрывающихся дат и получить сумму монет за это перекрытие.Я попробовал поискать в Google в качестве примера, но пока не повезло.Я не могу использовать правильные ключевые слова?

У меня есть список перекрывающихся дат

1.1.2018 - 31.1.2018 80
7.1.2018 - 10.1.2018 10
7.1.2018 - 31.1.2018 10
11.1.2018 - 31.1.2018 5
25.1.2018 - 27.1.2018 5
2.2.2018 - 23.2.2018 100

Желаемый результат будет

1.1.2018 - 6.7.2018 80 coins
7.1.2018 - 10.1.2018 100 coins
11.1.2018 - 24.1.2018 95 coins
25.1.2018 - 27.1.2018 100 coins
28.1.2018 - 31.1.2018 95 coins
2.2.2018 - 23.2.2018 100 coins

Вот рисунок, какэто должно работать

|------------------------------|
       |---|
       |-----------------------|
           |-------------------|
                      |---|
                                   |----------------------|
Outcome              
|------|---|----------|---|----|   |----------------------|
   80   100     95     100  95                100

Это мои тестовые данные

drop table coinsonperiod2;
create table coinsonperiod2(
  id serial,
  startdate date,
  enddate date,
  coins integer,
  userid integer
);
insert into coinsonperiod2 (startdate, enddate, coins,userid) values
  ('2018-01-01','2018-01-31', 80,1)
, ('2018-01-07','2018-01-10', 10,1)
, ('2018-01-07','2018-01-31', 10,1)
, ('2018-01-11','2018-01-31', 5,1)
, ('2018-01-25','2018-01-27', 5,1)
, ('2018-02-02','2018-02-23', 100,2)
, ('2018-01-01','2018-01-31', 80,2)
, ('2018-01-07','2018-01-10', 10,2)
, ('2018-01-07','2018-01-31', 10,2)
, ('2018-01-11','2018-01-31', 5,2)
, ('2018-01-25','2018-01-27', 5,2)
, ('2018-02-02','2018-02-23', 100,3)
; 

ОБНОВЛЕНИЕ: На самом деле ответы StephenM и joops не соответствуют моему желаемому результату.Оба ответа показывают неправильную конечную дату.

Когда один период заканчивается, следующий должен начаться на следующий день (или позже, если есть разрыв).В мой желаемый результат 1.1.2018-6.1.2018 входит 6-й день.Между 6-м и 7-м нет разрыва, потому что 7-е включено в 7.1.2018-10.1.2018.

ОБНОВЛЕНИЕ 2: Теперь я понял, в чем разница между открытым, полуоткрытым и закрытым интервалами.В решении joops вычисление должно выполняться с полуоткрытыми интервалами, но желаемый результат - закрытый интервал.Вот почему конечная дата должна быть уменьшена, чтобы сделать результат закрытым интервалом.Поправьте меня, если я ошибаюсь.

Я также добавил userid в пример данных и еще немного изменил решение joops.Вот запрос, который дает мне желаемый результат.

with changes AS (
  SELECT
    userid,
    startdate AS tickdate,
    coins,
    1         AS cover
  FROM coinsonperiod2
  UNION ALL
  -- add 1 day to correct intervals into half open intervals, so the calculation is correct
  SELECT
    userid,
    1 + enddate AS tickdate,
    -1 * coins,
    -1          AS cover
  FROM coinsonperiod2
)
, sumchanges  AS (
    SELECT
      userid,
      tickdate,
      SUM(coins) AS change,
      SUM(cover) AS cover
    FROM changes
    GROUP BY tickdate, userid
)
, aggregated AS (
    SELECT
      userid   AS userid,
      tickdate AS startdate,
      lead(tickdate)
      over www AS enddate,
      sum(change)
      OVER www AS cash,
      sum(cover)
      OVER www AS cover
    FROM sumchanges
    WINDOW www AS (
      partition by userid
      ORDER BY tickdate )
)
-- reduce 1 day from the enddate to make closed interval
SELECT
userid
, startdate
, enddate-1 as enddate
, cash
, cover
FROM aggregated
WHERE cover > 0
ORDER BY userid, startdate
;

Результат: Outcome

Ответы [ 4 ]

0 голосов
/ 17 октября 2018

Правильный ответ:

Это мои тестовые данные

drop table coinsonperiod2;
create table coinsonperiod2(
  id serial,
  startdate date,
  enddate date,
  coins integer,
  userid integer
);
insert into coinsonperiod2 (startdate, enddate, coins,userid) values
  ('2018-01-01','2018-01-31', 80,1)
, ('2018-01-07','2018-01-10', 10,1)
, ('2018-01-07','2018-01-31', 10,1)
, ('2018-01-11','2018-01-31', 5,1)
, ('2018-01-25','2018-01-27', 5,1)
, ('2018-02-02','2018-02-23', 100,2)
, ('2018-01-01','2018-01-31', 80,2)
, ('2018-01-07','2018-01-10', 10,2)
, ('2018-01-07','2018-01-31', 10,2)
, ('2018-01-11','2018-01-31', 5,2)
, ('2018-01-25','2018-01-27', 5,2)
, ('2018-02-02','2018-02-23', 100,3)
; 

ОБНОВЛЕНИЕ2: Теперь я понял, в чем разница между открытым, полуоткрытым и закрытым интервалами.В решении joops вычисление должно выполняться с полуоткрытыми интервалами, но желаемый результат - закрытый интервал.Вот почему конечная дата должна быть уменьшена, чтобы сделать результат в виде закрытого интервала.Поправьте меня, если я ошибаюсь.

Я также добавил userid в пример данных и еще немного изменил решение joops.Вот запрос, который дает мне желаемый результат.

with changes AS (
  SELECT
    userid,
    startdate AS tickdate,
    coins,
    1         AS cover
  FROM coinsonperiod2
  UNION ALL
  -- add 1 day to correct intervals into half open intervals, so the calculation is correct
  SELECT
    userid,
    1 + enddate AS tickdate,
    -1 * coins,
    -1          AS cover
  FROM coinsonperiod2
)
, sumchanges  AS (
    SELECT
      userid,
      tickdate,
      SUM(coins) AS change,
      SUM(cover) AS cover
    FROM changes
    GROUP BY tickdate, userid
)
, aggregated AS (
    SELECT
      userid   AS userid,
      tickdate AS startdate,
      lead(tickdate)
      over www AS enddate,
      sum(change)
      OVER www AS cash,
      sum(cover)
      OVER www AS cover
    FROM sumchanges
    WINDOW www AS (
      partition by userid
      ORDER BY tickdate )
)
-- reduce 1 day from the enddate to make closed interval
SELECT
userid
, startdate
, enddate-1 as enddate
, cash
, cover
FROM aggregated
WHERE cover > 0
ORDER BY userid, startdate
;

Результат: Outcome

0 голосов
/ 16 октября 2018

Похоже, я нашел уродливый, который работает

select t1.dt, t1.enddt, sum(coins)
from (
    select distinct cp1.dt, min(cp2.dt) enddt
    from ( select startdate as dt from coinsonperiod union all select enddate as dt from coinsonperiod ) cp1, 
         ( select startdate as dt from coinsonperiod union all select enddate as dt from coinsonperiod ) cp2
    where cp2.dt > cp1.dt
    group by cp1.dt
    order by cp1.dt ) t1, coinsonperiod t2
where t1.dt between t2.startdate and t2.enddate
and t1.enddt between t2.startdate and t2.enddate
group by t1.dt, t1.enddt

Вывод:

dt         |enddt      |sum |
-----------|-----------|----|
2018-01-01 |2018-01-07 |80  |
2018-01-07 |2018-01-10 |100 |
2018-01-10 |2018-01-11 |90  |
2018-01-11 |2018-01-25 |95  |
2018-01-25 |2018-01-27 |100 |
2018-01-27 |2018-01-31 |95  |
2018-02-02 |2018-02-23 |100 |

Единственная разница с вашим выводом в том, что я полагаю, вы забыли интервал между 01/10 и01/11

0 голосов
/ 16 октября 2018

Логика:

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

Итак, смысл в том, чтобы: преобразовать данные из серии интервалов в серию (запуск / останов) событий и агрегировать по ним.


-- \i tmp.sql

create table coinsonperiod(
  id serial,
  startdate date,
  enddate date,
  coins integer
);
insert into coinsonperiod (startdate, enddate, coins) values
  ('2018-01-01','2018-01-31', 80)
, ('2018-01-07','2018-01-10', 10)
, ('2018-01-07','2018-01-31', 10)
, ('2018-01-11','2018-01-31', 5)
, ('2018-01-25','2018-01-27', 5)
, ('2018-02-02','2018-02-23', 100)
        ;

WITH changes AS (
    SELECT startdate AS tickdate , coins
            , 1 AS cover
    FROM coinsonperiod
    UNION ALL
    -- add 1 day to convert to half-open intervals
    SELECT 1+enddate AS tickdate, -1* coins
            , -1 AS cover
    FROM coinsonperiod
    )
, sumchanges  AS (
        SELECT tickdate, SUM(coins) AS change, SUM(cover) AS cover
        FROM changes
        GROUP BY tickdate
        )
, aggregated AS (
        SELECT
        tickdate AS startdate
        , lead(tickdate) over www AS enddate
        , sum(change) OVER www AS cash
          -- number of covered intervals
        , sum(cover) OVER www AS cover
        FROM sumchanges
        WINDOW www AS (ORDER BY tickdate)
        )
             -- substract one day from enddate to correct back to closed intervals
SELECT startdate, enddate-1 AS enddate, cash, cover
FROM aggregated
WHERE cover > 0
ORDER BY startdate
        ;
0 голосов
/ 16 октября 2018

Хорошо, так что я собираюсь помочь вам с битом логики, синтаксис, который вы можете найти в Интернете.

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

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

Простое получение строки 1 столбца 1, сравнение со всеми другими данными столбца 1 и столбца 2.

...