It's rather simple, really. The function random()
is defined VOLATILE
.
If you put it into a subquery, you generate a derived table with a single row. Postgres can materialize the result and reuse it in the outer query many times.
Otherwise Postgres calls the function for every row. That's how VOLATILE
functions have to be treated. Per documentation on function volatility:
A
VOLATILE
function can do anything, including modifying the database. It can return different results on successive calls with the same arguments. The optimizer makes no assumptions about the behavior of such functions. A query using a volatile function will re-evaluate the function at every row where its value is needed.
Bold emphasis mine.