Суммируйте, пока не будет достигнуто пороговое значение, а затем сбросьте счетчик

user_id | date                 | distance
1       | 2019-04-09 00:00:00  | 2
1       | 2019-04-09 00:00:30  | 5
1       | 2019-04-09 00:01:00  | 3
1       | 2019-04-09 00:01:45  | 7
1       | 2019-04-09 00:02:30  | 6
1       | 2019-04-09 00:03:00  | 1

Как суммировать расстояние по следующей строке, пока не будет достигнута пороговая точка, и снова сбросить счетчик.

Например, если пороговое значение равно 10, я пытаюсь получить следующий результат:

1       | 2019-04-09 00:00:00  | 2
1       | 2019-04-09 00:00:30  | 7            (2 + 5)
1       | 2019-04-09 00:01:00  | 10           ( 7 + 3 )
1       | 2019-04-09 00:01:45  | 7            RESET
1       | 2019-04-09 00:02:30  | 13           (7 + 6 )
1       | 2019-04-09 00:03:00  | 1            RESET

Но все, что я мог сделать, это получить кумулятивное расстояние с помощью следующего запроса:

SELECT *, sum(distance) over (order by date asc) as running_distance FROM table;

Я использую PostgreSQL.

какую СУБД вы используете? майскл или постгре? вы отметили обоих

Esteban P. 09.04.2019 09:48

Я использую PostgreSQL.

Lekhnath 09.04.2019 09:49

было обсуждение похожей темы. посмотрите здесь: dba.stackexchange.com/questions/163557/…

Esteban P. 09.04.2019 09:56

Как насчет «кумулятивного расстояния» по модулю 10?

Paul Spiegel 09.04.2019 09:57
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
4
393
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Использовать определяемый пользователем агрегат

Живой тест: http://sqlfiddle.com/#!17/16716/2

SELECT *, sum_with_reset(distance, 10) over (order by date asc) as running_distance 
FROM tbl;

Определение определяемого пользователем агрегата sum_with_reset:

create or replace function sum_reset_accum(
    _accumulated numeric, _current numeric, _threshold numeric
)
returns numeric as
$$
    select case when _accumulated >= _threshold then
        _current
    else
        _current + _accumulated
    end    
$$ language sql;


create aggregate sum_with_reset(numeric, numeric)
(
    sfunc = sum_reset_accum,
    stype = numeric,
    initcond = 0
);

Данные

CREATE TABLE tbl
    ("user_id" int, "date" timestamp, "distance" int)
;

INSERT INTO tbl
    ("user_id", "date", "distance")
VALUES
    (1, '2019-04-09 00:00:00', 2),
    (1, '2019-04-09 00:00:30', 5),
    (1, '2019-04-09 00:01:00', 3),
    (1, '2019-04-09 00:01:45', 7),
    (1, '2019-04-09 00:02:30', 6),
    (1, '2019-04-09 00:03:00', 1)
;

Выход:

| user_id |                 date | distance | running_distance |
|---------|----------------------|----------|------------------|
|       1 | 2019-04-09T00:00:00Z |        2 |                2 |
|       1 | 2019-04-09T00:00:30Z |        5 |                7 |
|       1 | 2019-04-09T00:01:00Z |        3 |               10 |
|       1 | 2019-04-09T00:01:45Z |        7 |                7 |
|       1 | 2019-04-09T00:02:30Z |        6 |               13 |
|       1 | 2019-04-09T00:03:00Z |        1 |                1 |

Один лайнер:

create or replace function sum_reset_accum(
    _accumulated numeric, _current numeric, _threshold numeric
)
returns numeric as
$$
    select _current + _accumulated * (_accumulated < _threshold)::int
$$ language 'sql';

Логическое значение Postgres может привести true к 1, false к 0 с помощью оператора приведения ::int.

Вы также можете использовать язык plpgsql:

create or replace function sum_reset_accum(
    _accumulated numeric, _current numeric, _threshold numeric
)
returns numeric as
$$begin
    return _current + _accumulated * (_accumulated < _threshold)::int;
end$$ language 'plpgsql';

Обратите внимание, что вы не можете создать функцию plpgsql на sqlfiddle.com, поэтому вы не можете протестировать этот код plpgsql на sqlfiddle.com. Вы можете, хотя на вашей машине.

dbfiddle.uk и rextester.com/l/postgresql_online_compiler позволяют создавать функции plpgsql. Обратите внимание, что language sql гораздо эффективнее для простых функций «только запрос», чем plpgsql
a_horse_with_no_name 09.04.2019 10:50

@a_horse_with_no_name спасибо. Я постараюсь использовать это в следующий раз. Я привык к sqlfiddle.com, так как он имеет Text to DDL функциональность, я могу мгновенно использовать образцы данных OP.

Michael Buen 09.04.2019 10:53

Как вариант, вы можете использовать RECURSIVE CTE, чтобы получить его

Тестовая таблица:

CREATE TABLE public.table_b (
    user_id int4 NULL,
    "date" timestamp NULL,
    distance int4 NULL
);

INSERT INTO public.table_b (user_id,"date",distance) VALUES 
(1,'2019-04-09 00:00:00.000',2)
,(1,'2019-04-09 00:00:30.000',5)
,(1,'2019-04-09 00:01:00.000',3)
,(1,'2019-04-09 00:01:45.000',7)
,(1,'2019-04-09 00:02:30.000',6)
,(1,'2019-04-09 00:03:00.000',1)
,(2,'2019-04-09 00:00:00.000',2)
,(2,'2019-04-09 00:00:30.000',5)
,(2,'2019-04-09 00:01:00.000',3)
,(2,'2019-04-09 00:01:45.000',7)
,(2,'2019-04-09 00:02:30.000',6)
,(2,'2019-04-09 00:03:00.000',1);

Запрос:

WITH RECURSIVE cte1 AS(
    SELECT *,ROW_NUMBER()OVER(PARTITION BY user_id ORDER BY date) n
    FROM table_b
),
cte2 AS(
    SELECT user_id,date,distance,distance running_distance,n
    FROM cte1
    WHERE n=1

    UNION ALL

    SELECT c1.user_id,c1.date,c1.distance,CASE WHEN c2.running_distance<10 THEN c2.running_distance ELSE 0 END+c1.distance running_distance,c1.n
    FROM cte1 c1
    JOIN cte2 c2 ON c2.user_id=c1.user_id AND c2.n+1=c1.n
)
SELECT user_id,date,distance,running_distance
FROM cte2
ORDER BY user_id,date

Спасибо, это сработало, но медленнее, чем другой ответ. Я буду использовать этот запрос для создания представления любого предложения, чтобы оно работало быстрее. Это заняло 3-4 секунды всего с 4K строками.

Lekhnath 09.04.2019 12:43

Другие вопросы по теме