-- The cost estimate of a sift_col index makes the estimate of the selectivity of its clauses once: it reads the one PostgreSQL caches in the RestrictInfo, -- so an IN list of N elements costs N calls of the restriction estimator for the whole planning, however many index paths the joins build. -- The operator sift.=== (int4 = float4, pg_sift_test) counts the calls of its estimator: scalararraysel makes one for each element each time it estimates the clause. CREATE EXTENSION pg_sift; CREATE EXTENSION pg_sift_test; SET sift.test_col_legacy_options = on; CREATE TABLE sel_fact (id int, k int, v int, w text DEFAULT 'x') WITH (autovacuum_enabled = false); INSERT INTO sel_fact SELECT i, i % 3000, i % 7 FROM generate_series(1, 30000) i; CREATE TABLE sel_dim (id int PRIMARY KEY, n int); INSERT INTO sel_dim SELECT i, i FROM generate_series(0, 6) i; -- the point directory of k is what the estimate reads for an equality CREATE INDEX sel_fact_col ON sel_fact USING sift_col (id, k, v) WITH (key_cut = k, point_columns = 'k', rollup_columns = 'off'); NOTICE: sift_col index "sel_fact_col": 1 advice (track_changes) DETAIL: advice: track_changes = the table has no one-column primary key HINT: Reasons, costs and how to turn each off: SELECT * FROM sift.index_choices('sel_fact_col'); ANALYZE sel_fact; ANALYZE sel_dim; SET sift.enable_agg = off; \o /dev/null -- the list of 100 keys; `w` is not in the index, so the planner builds index paths on it SELECT sift._test_eq_sel_calls(true) AS discard \gset SELECT format('EXPLAIN (COSTS OFF) SELECT * FROM sel_fact WHERE k OPERATOR(sift.===) ANY (%L::float4[])', (SELECT array_agg(g) FROM generate_series(1, 100) g)) \gexec EXPLAIN (COSTS OFF) SELECT * FROM sel_fact WHERE k OPERATOR(sift.===) ANY ('{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100}'::float4[]) \o SELECT sift._test_eq_sel_calls(true) <= 100 AS one_estimate_of_the_list; one_estimate_of_the_list -------------------------- t (1 row) \o /dev/null -- joins build index paths with their own clauses; the list is still estimated once SELECT format('EXPLAIN (COSTS OFF) SELECT * FROM sel_fact f JOIN sel_dim d ON d.id = f.v WHERE f.k OPERATOR(sift.===) ANY (%L::float4[])', (SELECT array_agg(g) FROM generate_series(1, 100) g)) \gexec EXPLAIN (COSTS OFF) SELECT * FROM sel_fact f JOIN sel_dim d ON d.id = f.v WHERE f.k OPERATOR(sift.===) ANY ('{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100}'::float4[]) \o SELECT sift._test_eq_sel_calls(true) <= 100 AS one_estimate_of_the_list_joined; one_estimate_of_the_list_joined --------------------------------- t (1 row) DROP TABLE sel_fact, sel_dim; DROP EXTENSION pg_sift_test, pg_sift CASCADE;