Internally, there are **two separate forms** of `IN`, as well as for the `ANY` construct. One or each, taking a ***set***, is equivalent to the other and also leads to the same query plan that can use the plain index. Details: - [IN vs ANY operator in PostgreSQL][1] Consequently, these two queries are equivalent and both can use the plain index `t_a_b_idx` (which can also be the **solution** if you are trying to get your query to use the index): EXPLAIN ANALYZE SELECT * FROM t WHERE (a,b) = ANY(VALUES (1,1),(1,2)); Or: ... WHERE (a,b) IN (VALUES (1,1),(1,2)); Identical for both: > QUERY PLAN > > -------------------------------------------------------------------------------------------------------------------------- > Nested Loop (cost=0.33..16.71 rows=1 width=8) (actual time=0.101..0.101 rows=0 loops=1) > -> Unique (cost=0.04..0.05 rows=2 width=8) (actual time=0.068..0.070 rows=2 loops=1) > -> Sort (cost=0.04..0.04 rows=2 width=8) (actual time=0.067..0.068 rows=2 loops=1) > Sort Key: "*VALUES*".column1, "*VALUES*".column2 > Sort Method: quicksort Memory: 25kB > -> Values Scan on "*VALUES*" (cost=0.00..0.03 rows=2 width=8) (actual time=0.005..0.005 rows=2 loops=1) > -> Index Only Scan using t_plain_idx on t (cost=0.29..8.32 rows=1 width=8) (actual time=0.009..0.009 rows=0 loops=2) > Index Cond: ((a = "*VALUES*".column1) AND (b = "*VALUES*".column2)) > Heap Fetches: 0 > Planning time: 4.080 ms > Execution time: 0.202 ms The second form of each is different: ANY takes an actual ***array***, while IN takes a comma separated ***list of values*** (elements) This has different consequences for **typing** the input. As we can see in the `EXPLAIN` output of the question, this form WHERE (a,b) = ANY(ARRAY[(1,1),(1,2)]); is seen as shorthand for: ROW(a, b) = ANY (ARRAY[ROW(1, 1), ROW(1, 2)]) And actual ROW values are compared. Postgres is not currently smart enough to see that the index on the composite type `t_row_idx` is applicable here. On the other hand, when using `IN` with a *list* of composite values WHERE (a,b) IN ((1,1),(1,2)); .. values are taken as *elements* and Postgres is able to compare individual integer values as we can see in the `EXPLAIN` output once more: > Filter: ((b = 1) OR (b = 2)) Hence the simple index can be used. [1]: http://stackoverflow.com/a/34627688/939860