Можно ли достичь цели, используя PARTITION OVER
, CTE
или любой другой вид SELECT
?
Это по своей природе процессуальное проблема . В зависимости от того, с чего вы начнете, все последующие строки могут оказаться в другой группе и / или с другим значением группы. Оконные функции (с использованием предложения PARTITION
) не годятся для этого.
Вы можете использовать рекурсивный CTE :
WITH RECURSIVE rcte AS (
(
SELECT id
, measurement
, measurement - 1 AS grp_min
, measurement + 1 AS grp_max
, 1 AS grp
FROM tbl
ORDER BY id
LIMIT 1
)
UNION ALL
(
SELECT t.id
, t.measurement
, CASE WHEN t.same_grp THEN r.grp_min ELSE t.measurement - 1 END -- AS grp_min
, CASE WHEN t.same_grp THEN r.grp_max ELSE t.measurement + 1 END -- AS grp_max
, CASE WHEN t.same_grp THEN r.grp ELSE r.grp + 1 END -- AS grp
FROM rcte r
CROSS JOIN LATERAL (
SELECT *, t.measurement BETWEEN r.grp_min AND r.grp_max AS same_grp
FROM tbl t
WHERE t.id > r.id
ORDER BY t.id
LIMIT 1
) t
)
)
SELECT id, measurement, grp
FROM rcte;
Это элегантно. И прилично быстро. Но только примерно так же быстро или даже медленнее, чем - процедурная языковая функция с одним циклом над множеством - при эффективной реализации:
CREATE OR REPLACE FUNCTION f_measurement_groups(_threshold numeric = 1)
RETURNS TABLE (id int, grp int, measurement numeric) AS
$func$
DECLARE
_grp_min numeric;
_grp_max numeric;
BEGIN
grp := 0; -- init
FOR id, measurement IN
SELECT * FROM tbl t ORDER BY t.id
LOOP
IF measurement BETWEEN _grp_min AND _grp_max THEN
RETURN NEXT;
ELSE
SELECT INTO grp , _grp_min , _grp_max
grp + 1, measurement - _threshold, measurement + _threshold;
RETURN NEXT;
END IF;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Вызов:
SELECT * FROM f_measurement_groups(); -- optionally supply different threshold
db <> fiddle здесь
Мои деньги идут на процедурную функцию.
Как правило, решения на основе множеств быстрее. Но не при решении изначально процедурной проблемы.
Связанные: