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.
Я использую PostgreSQL.
было обсуждение похожей темы. посмотрите здесь: dba.stackexchange.com/questions/163557/…
Как насчет «кумулятивного расстояния» по модулю 10?
Использовать определяемый пользователем агрегат
Живой тест: 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. Вы можете, хотя на вашей машине.
language sql
гораздо эффективнее для простых функций «только запрос», чем plpgsql
@a_horse_with_no_name спасибо. Я постараюсь использовать это в следующий раз. Я привык к sqlfiddle.com, так как он имеет Text to DDL
функциональность, я могу мгновенно использовать образцы данных OP.
Как вариант, вы можете использовать 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 строками.
какую СУБД вы используете? майскл или постгре? вы отметили обоих