By using db<>fiddle, you agree to license everything you submit by Creative Commons CC0.
| version |
|---|
| PostgreSQL 15.0 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-10), 64-bit |
SELECT 1
CREATE TABLE
| id | is_skippable | prev | next |
|---|---|---|---|
| A1 | f | null | A2 |
| A2 | t | A1 | A3 |
| A3 | f | A2 | null |
| B1 | f | null | B2 |
| B2 | f | B1 | null |
INSERT 0 5
| id | prev | next |
|---|---|---|
| A1 | null | A3 |
| A3 | A1 | null |
| B1 | null | B2 |
| B2 | B1 | null |
SELECT 4
| id | prev | next |
|---|---|---|
| A1 | null | A3 |
| A3 | A1 | null |
| B1 | null | B2 |
| B2 | B1 | null |
SELECT 4
| id | prev | next |
|---|---|---|
| A1 | null | A3 |
| A3 | A1 | null |
| B1 | null | B2 |
| B2 | B1 | null |
SELECT 4
| now | setseed |
|---|---|
| 2024-05-07 19:10:57.467445+01 |
SELECT 1
INSERT 0 10000
CREATE INDEX
VACUUM
| QUERY PLAN |
|---|
| Seq Scan on public.node n1 (cost=0.00..1486771.12 rows=7982 width=69) (actual time=0.104..263.144 rows=7982 loops=1) |
| Output: n1.id, (SubPlan 2), (SubPlan 4) |
| Filter: (NOT n1.is_skippable) |
| Rows Removed by Filter: 2023 |
| SubPlan 2 |
| -> Limit (cost=93.08..93.12 rows=1 width=32) (actual time=0.014..0.014 rows=1 loops=7982) |
| Output: cte_1.id |
| CTE cte |
| -> Recursive Union (cost=0.29..93.08 rows=21 width=44) (actual time=0.009..0.012 rows=1 loops=7982) |
| -> Index Only Scan using node_id_is_skippable_prev_next_idx on public.node n2 (cost=0.29..4.30 rows=1 width=44) (actual time=0.007..0.007 rows=1 loops=7982) |
| Output: n2.id, n2.prev, n2.is_skippable, false, ARRAY[ROW(n2.prev)] |
| Index Cond: (n2.id = n1.prev) |
| Heap Fetches: 0 |
| -> Nested Loop (cost=0.29..8.84 rows=2 width=44) (actual time=0.008..0.008 rows=1 loops=2358) |
| Output: n2_1.id, n2_1.prev, n2_1.is_skippable, CASE WHEN (ROW(n2_1.prev) = ANY (cte.path)) THEN true ELSE false END, array_cat(cte.path, ARRAY[ROW(n2_1.prev)]) |
| Inner Unique: true |
| -> WorkTable Scan on cte (cost=0.00..0.20 rows=2 width=64) (actual time=0.000..0.000 rows=1 loops=2358) |
| Output: cte.path, cte.prev |
| Filter: (cte.is_skippable AND (NOT cte.is_cycle)) |
| -> Index Only Scan using node_id_is_skippable_prev_next_idx on public.node n2_1 (cost=0.29..4.30 rows=1 width=11) (actual time=0.006..0.006 rows=1 loops=1912) |
| Output: n2_1.id, n2_1.is_skippable, n2_1.prev, n2_1.next |
| Index Cond: (n2_1.id = cte.prev) |
| Heap Fetches: 0 |
| -> CTE Scan on cte cte_1 (cost=0.00..0.42 rows=10 width=32) (actual time=0.014..0.014 rows=1 loops=7982) |
| Output: cte_1.id |
| Filter: (NOT cte_1.is_skippable) |
| Rows Removed by Filter: 0 |
| SubPlan 4 |
| -> Limit (cost=93.08..93.12 rows=1 width=32) (actual time=0.016..0.016 rows=1 loops=7982) |
| Output: cte_3.id |
| CTE cte |
| -> Recursive Union (cost=0.29..93.08 rows=21 width=44) (actual time=0.010..0.014 rows=1 loops=7982) |
| -> Index Only Scan using node_id_is_skippable_prev_next_idx on public.node n2_2 (cost=0.29..4.30 rows=1 width=44) (actual time=0.008..0.008 rows=1 loops=7982) |
| Output: n2_2.id, n2_2.next, n2_2.is_skippable, false, ARRAY[ROW(n2_2.next)] |
| Index Cond: (n2_2.id = n1.next) |
| Heap Fetches: 0 |
| -> Nested Loop (cost=0.29..8.84 rows=2 width=44) (actual time=0.008..0.009 rows=1 loops=2313) |
| Output: n2_3.id, n2_3.next, n2_3.is_skippable, CASE WHEN (ROW(n2_3.next) = ANY (cte_2.path)) THEN true ELSE false END, array_cat(cte_2.path, ARRAY[ROW(n2_3.next)]) |
| Inner Unique: true |
| -> WorkTable Scan on cte cte_2 (cost=0.00..0.20 rows=2 width=64) (actual time=0.000..0.000 rows=1 loops=2313) |
| Output: cte_2.path, cte_2.next |
| Filter: (cte_2.is_skippable AND (NOT cte_2.is_cycle)) |
| -> Index Only Scan using node_id_is_skippable_prev_next_idx on public.node n2_3 (cost=0.29..4.30 rows=1 width=11) (actual time=0.006..0.006 rows=1 loops=1946) |
| Output: n2_3.id, n2_3.is_skippable, n2_3.prev, n2_3.next |
| Index Cond: (n2_3.id = cte_2.next) |
| Heap Fetches: 0 |
| -> CTE Scan on cte cte_3 (cost=0.00..0.42 rows=10 width=32) (actual time=0.015..0.015 rows=1 loops=7982) |
| Output: cte_3.id |
| Filter: (NOT cte_3.is_skippable) |
| Rows Removed by Filter: 0 |
| Planning Time: 1.275 ms |
| Execution Time: 264.227 ms |
EXPLAIN
| QUERY PLAN |
|---|
| Seq Scan on public.node n1 (cost=0.00..3546401.20 rows=7982 width=69) (actual time=0.066..366.128 rows=7982 loops=1) |
| Output: n1.id, COALESCE((SubPlan 2), n1.prev), COALESCE((SubPlan 4), n1.next) |
| Filter: (NOT n1.is_skippable) |
| Rows Removed by Filter: 2023 |
| SubPlan 2 |
| -> Limit (cost=222.14..222.14 rows=1 width=36) (actual time=0.027..0.027 rows=1 loops=7982) |
| Output: cte_1.prev, cte_1.i |
| CTE cte |
| -> Recursive Union (cost=0.29..221.86 rows=11 width=42) (actual time=0.010..0.022 rows=1 loops=7982) |
| -> Index Only Scan using node_id_is_skippable_prev_next_idx on public.node n2 (cost=0.29..4.30 rows=1 width=42) (actual time=0.007..0.008 rows=1 loops=7982) |
| Output: 1, n2.prev, false, ARRAY[ROW(n2.prev)] |
| Index Cond: (n2.id = n1.prev) |
| Heap Fetches: 0 |
| -> Nested Loop (cost=0.29..21.73 rows=1 width=42) (actual time=0.009..0.009 rows=0 loops=9821) |
| Output: (cte.i + 1), n2_1.prev, CASE WHEN (ROW(n2_1.prev) = ANY (cte.path)) THEN true ELSE false END, array_cat(cte.path, ARRAY[ROW(n2_1.prev)]) |
| Inner Unique: true |
| -> WorkTable Scan on cte (cost=0.00..0.20 rows=5 width=68) (actual time=0.000..0.000 rows=1 loops=9821) |
| Output: cte.i, cte.path, cte.prev |
| Filter: (NOT cte.is_cycle) |
| -> Index Only Scan using node_id_is_skippable_prev_next_idx on public.node n2_1 (cost=0.29..4.30 rows=1 width=10) (actual time=0.007..0.007 rows=0 loops=9375) |
| Output: n2_1.id, n2_1.is_skippable, n2_1.prev, n2_1.next |
| Index Cond: (n2_1.id = cte.prev) |
| Filter: n2_1.is_skippable |
| Rows Removed by Filter: 1 |
| Heap Fetches: 0 |
| -> Sort (cost=0.28..0.30 rows=11 width=36) (actual time=0.026..0.026 rows=1 loops=7982) |
| Output: cte_1.prev, cte_1.i |
| Sort Key: cte_1.i DESC |
| Sort Method: quicksort Memory: 25kB |
| -> CTE Scan on cte cte_1 (cost=0.00..0.22 rows=11 width=36) (actual time=0.011..0.023 rows=1 loops=7982) |
| Output: cte_1.prev, cte_1.i |
| SubPlan 4 |
| -> Limit (cost=222.14..222.14 rows=1 width=36) (actual time=0.016..0.016 rows=0 loops=7982) |
| Output: cte_3.next, cte_3.i |
| CTE cte |
| -> Recursive Union (cost=0.29..221.86 rows=11 width=42) (actual time=0.009..0.012 rows=0 loops=7982) |
| -> Index Only Scan using node_id_is_skippable_prev_next_idx on public.node n2_2 (cost=0.29..4.30 rows=1 width=42) (actual time=0.007..0.007 rows=0 loops=7982) |
| Output: 1, n2_2.next, false, ARRAY[ROW(n2_2.next)] |
| Index Cond: (n2_2.id = n1.next) |
| Filter: n2_2.is_skippable |
| Rows Removed by Filter: 1 |
| Heap Fetches: 0 |
| -> Nested Loop (cost=0.29..21.73 rows=1 width=42) (actual time=0.002..0.002 rows=0 loops=8375) |
| Output: (cte_2.i + 1), n2_3.next, CASE WHEN (ROW(n2_3.next) = ANY (cte_2.path)) THEN true ELSE false END, array_cat(cte_2.path, ARRAY[ROW(n2_3.next)]) |
| Inner Unique: true |
| -> WorkTable Scan on cte cte_2 (cost=0.00..0.20 rows=5 width=68) (actual time=0.000..0.000 rows=0 loops=8375) |
| Output: cte_2.i, cte_2.path, cte_2.next |
| Filter: (NOT cte_2.is_cycle) |
| -> Index Only Scan using node_id_is_skippable_prev_next_idx on public.node n2_3 (cost=0.29..4.30 rows=1 width=10) (actual time=0.007..0.007 rows=0 loops=1946) |
| Output: n2_3.id, n2_3.is_skippable, n2_3.prev, n2_3.next |
| Index Cond: (n2_3.id = cte_2.next) |
| Filter: n2_3.is_skippable |
| Rows Removed by Filter: 1 |
| Heap Fetches: 0 |
| -> Sort (cost=0.28..0.30 rows=11 width=36) (actual time=0.015..0.015 rows=0 loops=7982) |
| Output: cte_3.next, cte_3.i |
| Sort Key: cte_3.i DESC |
| Sort Method: top-N heapsort Memory: 25kB |
| -> CTE Scan on cte cte_3 (cost=0.00..0.22 rows=11 width=36) (actual time=0.010..0.013 rows=0 loops=7982) |
| Output: cte_3.next, cte_3.i |
| Planning Time: 0.944 ms |
| Execution Time: 368.270 ms |
EXPLAIN