Скользящая сумма до достижения определенного значения плюс расчетная продолжительность - PullRequest
0 голосов
/ 28 февраля 2019

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

create table sample (dt timestamp, value real);

insert into sample values
     ('2019-01-20 00:29:43 ',0.29)
    ,('2019-01-20 00:35:06 ',0.31)
    ,('2019-01-20 00:35:50 ',0.41)
    ,('2019-01-20 00:36:32 ',0.26)
    ,('2019-01-20 00:37:20 ',0.33)
    ,('2019-01-20 00:41:30 ',0.42)
    ,('2019-01-20 00:42:28 ',0.35)
    ,('2019-01-20 00:43:14 ',0.52)
    ,('2019-01-20 00:44:18 ',0.25);

Теперь мое требование состоит в том, чтобы рассчитать совокупную сумму следующих строк, чтобы увидеть, когда sum(value) превысит 1,0 .Это может потребовать всего 1 строку или n строк.Как только эта строка достигнута, мне нужно вычислить разницу во времени между текущей строкой и строкой, в которой sum(value) достигает значения выше 1,0.

По сути, мой желаемый результат находится в формате ниже.
Для 1-й строки, накопительнойsum(value) достигается в 3-м ряду.
Для 2-го ряда, совокупный sum(value) достигается в 4-м ряду и т. Д.

         dt         | value | sum(value)| time_at_sum(value)_1| Duration
---------------------+--------+------------------------------------------
 2019-01-20 00:29:43| 0.29  |   1.01    | 2019-01-20 00:35:50 | 00:06:07
 2019-01-20 00:35:06| 0.31  |   1.31    | 2019-01-20 00:37:20 | 00:02:14 
 2019-01-20 00:35:50| 0.41  |   1.00    | 2019-01-20 00:37:20 | 00:01:30 
 2019-01-20 00:36:32| 0.26  |   1.01    | 2019-01-20 00:41:30 | 00:04:58 
 2019-01-20 00:37:20| 0.33  |   1.10    | 2019-01-20 00:42:28 | 00:05:08 
 2019-01-20 00:41:30| 0.42  |   1.29    | 2019-01-20 00:43:14 | 00:01:44 
 2019-01-20 00:42:28| 0.35  |   1.12    | 2019-01-20 00:44:18 | 00:01:50 
 2019-01-20 00:43:14| 0.52  |   NULL    |  -                  | -
 2019-01-20 00:44:18| 0.25  |   NULL    |  -                  | -

У каждого есть идеи или указания о том, как с этим справлятьсяТребование

Ответы [ 2 ]

0 голосов
/ 28 февраля 2019

Способ эффективного решения этой проблемы - это процедурное решение с двумя курсорами: один явный курсор и другой неявный курсор цикла FOR :

CREATE OR REPLACE FUNCTION foo()
  RETURNS TABLE (dt timestamp
               , val real
               , sum_value real
               , time_at_sum timestamp
               , duration interval) AS
$func$
DECLARE
   _bound real := 1.0;          -- your bound here
   cur CURSOR FOR SELECT * FROM sample s ORDER BY s.dt; -- in chronological order
   s sample;                    -- cursor row 
BEGIN
   OPEN cur;
   FETCH cur INTO time_at_sum, sum_value; -- fetch first row into target

   FOR dt, val IN  -- primary pass over table
      SELECT x.dt, x.value FROM sample x ORDER BY s.dt
   LOOP
      WHILE sum_value <= _bound LOOP
         FETCH cur INTO s;
         IF NOT FOUND THEN  -- end of table
            sum_value := NULL; time_at_sum := NULL;
            EXIT;           -- exits inner loop
         END IF;
         sum_value := sum_value + s.value; 
      END LOOP;
      IF sum_value > _bound THEN  -- to catch end-of-table
         time_at_sum := s.dt;
      END IF;   
      duration := time_at_sum - dt;
      RETURN NEXT;
      sum_value := sum_value - val;  -- subtract previous row before moving on
   END LOOP;
END
$func$  LANGUAGE plpgsql;

Звоните:

SELECT * FROM foo();

дБ <> Fiddle здесь

Должно работать хорошо, так как нужно только 2просматривает таблицу.

Обратите внимание, что я реализовал > _bound, как того требует ваше описание, а не >= _bound, как показывает ваш результат.Легко изменить в любом случае.

Предполагается, что столбец значений будет NOT NULL.

Связанный:

0 голосов
/ 28 февраля 2019
WITH tmp AS (
    SELECT *
        , sum(value) OVER (ORDER BY dt rows between current row and unbounded following) as forward_sum
    FROM sample
    ORDER BY dt)
SELECT t1.dt, t1.value
    , (t2.value + t1.forward_sum - t2.forward_sum) as "sum(value)"
    , t2.dt as "time_at_sum(value)_1" 
    , t2.dt - t1.dt as "Duration"
FROM tmp t1
LEFT JOIN LATERAL (
    SELECT * 
    FROM tmp t
    WHERE t1.forward_sum - t.forward_sum < 1
        AND (t.value + t1.forward_sum - t.forward_sum) >= 0.999
    ORDER BY dt DESC 
    LIMIT 1
    ) t2
ON TRUE

доходность

| dt                  | value | sum(value) | time_at_sum(value)_1 | Duration |
|---------------------+-------+------------+----------------------+----------|
| 2019-01-20 00:29:43 |  0.29 |       1.01 | 2019-01-20 00:35:50  | 00:06:07 |
| 2019-01-20 00:35:06 |  0.31 |       1.31 | 2019-01-20 00:37:20  | 00:02:14 |
| 2019-01-20 00:35:50 |  0.41 |          1 | 2019-01-20 00:37:20  | 00:01:30 |
| 2019-01-20 00:36:32 |  0.26 |       1.01 | 2019-01-20 00:41:30  | 00:04:58 |
| 2019-01-20 00:37:20 |  0.33 |        1.1 | 2019-01-20 00:42:28  | 00:05:08 |
| 2019-01-20 00:41:30 |  0.42 |       1.29 | 2019-01-20 00:43:14  | 00:01:44 |
| 2019-01-20 00:42:28 |  0.35 |       1.12 | 2019-01-20 00:44:18  | 00:01:50 |
| 2019-01-20 00:43:14 |  0.52 |            |                      |          |
| 2019-01-20 00:44:18 |  0.25 |            |                      |          |

Сначала вычислите совокупную сумму по столбцу value:

SELECT *
    , sum(value) OVER (ORDER BY dt rows between current row and unbounded following) as forward_sum
FROM sample
ORDER BY dt

, что дает

| dt                  | value | forward_sum |
|---------------------+-------+-------------|
| 2019-01-20 00:29:43 |  0.29 |        3.14 |
| 2019-01-20 00:35:06 |  0.31 |        2.85 |
| 2019-01-20 00:35:50 |  0.41 |        2.54 |
| 2019-01-20 00:36:32 |  0.26 |        2.13 |
| 2019-01-20 00:37:20 |  0.33 |        1.87 |
| 2019-01-20 00:41:30 |  0.42 |        1.54 |
| 2019-01-20 00:42:28 |  0.35 |        1.12 |
| 2019-01-20 00:43:14 |  0.52 |        0.77 |
| 2019-01-20 00:44:18 |  0.25 |        0.25 |

Обратите внимание, что вычитание двух значений из forward_sum соответствует частичной сумме за value с.Например,

0.29 + 0.31 + 0.41 = 3.14 - 2.13

Таким образом, разница в forward_sums сыграет важную роль, и мы хотим сравнить эти различия с 1. Мы собираемся присоединиться к этой таблице ссам, используя условие соединения, например:

t1.forward_sum - t.forward_sum < 1

Давайте посмотрим, что произойдет, если мы будем использовать LEFT JOIN LATERAL.Критическая вещь, которую нужно знать о LEFT JOIN LATERAL, заключается в том, что подзапрос справа от LATERAL join должен оцениваться один раз для каждой строки в таблице слева :

WITH tmp AS (
    SELECT *
        , sum(value) OVER (ORDER BY dt rows between current row and unbounded following) as forward_sum
    FROM sample
    ORDER BY dt)
SELECT t1.*, t2.*
FROM tmp t1
LEFT JOIN LATERAL (
    SELECT * 
    FROM tmp t
    WHERE t1.forward_sum - t.forward_sum < 1
    ORDER BY dt DESC 
    LIMIT 1
    ) t2
ON TRUE

доходность

| dt                  | value | forward_sum | dt                  | value | forward_sum |
|---------------------+-------+-------------+---------------------+-------+-------------|
| 2019-01-20 00:29:43 |  0.29 |        3.14 | 2019-01-20 00:35:50 |  0.41 |        2.54 |
| 2019-01-20 00:35:06 |  0.31 |        2.85 | 2019-01-20 00:37:20 |  0.33 |        1.87 |
| 2019-01-20 00:35:50 |  0.41 |        2.54 | 2019-01-20 00:37:20 |  0.33 |        1.87 |
| 2019-01-20 00:36:32 |  0.26 |        2.13 | 2019-01-20 00:41:30 |  0.42 |        1.54 |
| 2019-01-20 00:37:20 |  0.33 |        1.87 | 2019-01-20 00:42:28 |  0.35 |        1.12 |
| 2019-01-20 00:41:30 |  0.42 |        1.54 | 2019-01-20 00:43:14 |  0.52 |        0.77 |
| 2019-01-20 00:42:28 |  0.35 |        1.12 | 2019-01-20 00:44:18 |  0.25 |        0.25 |
| 2019-01-20 00:43:14 |  0.52 |        0.77 | 2019-01-20 00:44:18 |  0.25 |        0.25 |
| 2019-01-20 00:44:18 |  0.25 |        0.25 | 2019-01-20 00:44:18 |  0.25 |        0.25 |

Обратите внимание, что мы угадали наш путь к условию соединения, которое соответствует желаемым датам.Теперь нужно просто составить правильные выражения для получения нужных столбцов: sum(value), time_at_sum(value)_1.

.
...