Assuming that the function foo()
cannot be inlined, Postgres has no idea what it might return, so it has to assume that any number is equally common.
The predicate foo = 4
tells Postgres to expect that next to no row will qualify.
The predicate foo != 0 AND foo != 2
, OTOH, tells Postgres to expect that almost all rows qualify. With foo > 2
it's still about half of all rows.
That typically leads to different query plans, and the first one seems to perform poorly, while the other ones seem to perform nicely.
Details are hidden by missing information. But that's the point here.
If the function is IMMUTABLE
, you might create an expression index on foo(foo(id))
. That index is probably useless by itself, assuming that the 3 possible values are evenly distributed. (Maybe an index-only scan for a multicolumn index foo(foo(id), id)
would help, if the function is expensive and declared as such.) But it makes Postgres gather additional statistics that would tell the query planner what to expect from the function. Related: