実際のクエリはより複雑ですが、私が直面している問題はこれに抽出することができます:
単調に増加する整数の行セットをフィルタリングするクエリ。これにより、最終結果セットで-、row(n + 1).value> = row(n)になります。値+ 5。
私が解決する必要がある実際の問題については、行セット数は1000です。
明確にするいくつかの例:
次のクエリで必要な結果を得ることができましたが、複雑すぎるようです。異なる "..with t(k).."のコメントを外して試してください。
同じ結果を得るための簡素化または代替アプローチを探しています。
with recursive r(n, pri) as (
with t(k) as (values (1),(2),(3),(4),(5)) -- the data we want to filter
-- with t(k) as (values (1),(5),(7),(10),(11),(12),(13))
-- with t(k) as (values (6),(8),(11),(16),(20),(23))
-- with t(k) as (values (6),(8),(12),(16),(20),(23))
select min(k), 1::bigint from t -- bootstrap for recursive processing. 1 here represents rank().
UNION
select k, (rank() over(order by k)) rr -- rank() is required just to filter out the rows we dont want from the final result set, and no other reason
from r, t
where t.k >= r.n+5 and r.pri = 1 -- capture the rows we want, AND unfortunately a bunch of rows we dont want
)
select n from r where pri = 1;
これは大変でした!これがsimplerかどうかはわかりませんが、少なくともウィンドウ関数を使用せず、フィルターで除外する必要のある行を生成しません。
with recursive r(k, n) as (
with t(k) as (values (1),(2),(3),(4),(5)) -- the data we want to filter
-- with t(k) as (values (1),(5),(7),(10),(11),(12),(13))
-- with t(k) as (values (6),(8),(11),(16),(20),(23))
-- with t(k) as (values (6),(8),(12),(16),(20),(23))
,t2(k,n) AS (select k, (select min(k) from t tt where k >= t.k+5) from t) -- precalculate what's next
select * from (select * from t2 limit 1) x -- limit 1 directly fails in a union!
UNION ALL
select t2.* from r, t2 where t2.k = r.n -- on each iteration, keep only the value that matches the previous precalculated next one
)
select k from r
テスト
この代替案は、非常に小さなセットでは効率が悪いように見えますが、パフォーマンスはほぼ直線的ですが、オリジナルは指数関数的に遅くなります。
drop table if exists t;
create temp table t(k) AS
with recursive r(n) as (
select floor(random()*10)::int + 1
UNION ALL
select n + floor(random()*10)::int + 1
from r
where n < 100000) -- change to increase or reduce set
select * from r; -- surprisingly fast! Go PG!
create index on t(k);
with recursive r(n, pri) as (
select min(k), 1::bigint from t
UNION
select k, (rank() over(order by k)) rr
from r, t
where t.k >= r.n+5 and r.pri = 1
)
select count(*) from r where pri = 1; -- I aborted it after waiting for a minute
with recursive r(k, n) as (
with t2(k,n) AS (select k, (select min(k) from t tt where k >= t.k+5) from t)
select * from (select * from t2 limit 1) x
UNION ALL
select t2.* from r, t2 where t2.k = r.n
)
select count(*) from r -- 26" in my server
通常、前の行のdataにアクセスする必要がある場合は、ウィンドウ化された集計関数を利用できますが、ここでは現在の行の計算は前のresult、行ではありません。この種の質問については、許容できるパフォーマンスのセットベースのソリューションを見つけることはできませんでした。
しかし、行ごとのロジックを使用して書くのは簡単です。一時テーブルでROW_NUMBER
を具体化したい場合、次の行を見つけるのはn+1
だけです。
CREATE TABLE temp AS
( SELECT k,
ROW_NUMBER() OVER (ORDER BY k) AS rn
FROM t
)
;
-- I don't know enough about PostgreSQL, but this is probably needed for performance
create unique index on temp(rn)
;
WITH RECURSIVE cte(k,rn, k1) AS
(
SELECT k, rn, k AS k1
FROM tmp
WHERE rn = 1 -- start with the minimum value
UNION ALL
SELECT tmp.k, tmp.rn,
CASE
WHEN tmp.k >= cte.k1 + 5
THEN tmp.k -- use the new value
ELSE cte.k1 -- keep the old value
END
FROM tmp JOIN cte
ON tmp.rn = cte.rn+1 -- next row
)
SELECT count(k)
FROM cte
WHERE k = k1
これは非常に高速に実行され、@ ZiggyCrueltyfreeZeitgeisterの fiddle をハイジャックしました。
実際、これは古いスタイルのカーソルロジック処理であり、実際にはカーソルの方が高速な場合があります(一時テーブルとROW_NUMBER
と再帰は必要ありません)。私のPostgreSQLの知識は非常に限られていて、PGランドで何が好まれるのかわかりません:)