The fact that you get a sequential scan with only two entries does not make sense. An index will never be faster than selective scanning for such a tiny set. I built a small sample table close to yours and populated it with millions of rows, and the following query style consistently creates nice plans and fast execution:
prepare s4 as select id from users join (select * from (values ($1,$2),($3,$4)) as v(branch, cid)) as p using (branch, cid); explain analyze execute s4('b11','c11','b1234','c1234'); QUERY PLAN
It seems your real problem is how to associate a dynamically defined number of value pairs with your sql. My PHP is terribly rusty, and reading online documents reminded me how much I hate it, but I think the following will do what you want by building the sql of the above form with the number of dynamic creators of the value pair based on the number of values you want to link . I don't have a php runtime, so I didn’t even check if it was syntactically fixed, but you should be able to get this idea and work out some trivial errors in my example.
$values = array( 'a', 'b', 'c', 'd',
If you really just have to have one prepared statement (and for a guy who can't be bothered to actually try his queries against a real data set instead of two records, we completely avoid talking about premature optimization ), I think I have there is an answer for you:
create index u_i2 on users ((branch||cid)); prepare sa as select id from users where branch||cid in (select unnest($1::text[])); explain analyze execute sa(ARRAY['b1c1','b1234c1234']); QUERY PLAN
Note. I could not find access to indexed access to line pairs. But if you create a functional index to concatenate two fields, then put the associated array of such concatenations, you will get a good quick index scan using nest-loop.