pgconfigurator
pgconfigurator

SetOp

Appears in EXPLAIN asSetOp / HashSetOp

Implements INTERSECT and EXCEPT (with ALL variants).

What it is

A SetOp node computes INTERSECT/EXCEPT (and their ALL forms) by counting matching rows from the two branches. It comes in a sorted variant (SetOp) and a hashed variant (HashSetOp).

When the planner picks it

For INTERSECT / EXCEPT queries. (UNION is handled differently — by Append plus a deduplicating step.)

Is it good or bad?

Normal for set operations. As with aggregation, the hashed variant can spill if the working set is large.

In depth

INTERSECT and EXCEPT internals

SetOp implements the INTERSECT and EXCEPT operators (and their ALL variants) by counting how many of each row appear in each branch and emitting the right number based on the operator. It comes in two strategies:

  • SetOp — sorted strategy. Both branches arrive sorted on the comparison columns and SetOp walks them in lockstep.
  • HashSetOp — hash strategy. SetOp builds a hash table keyed on the comparison columns and counts occurrences.

PostgreSQL picks whichever it estimates is cheaper, similarly to DISTINCT and GROUP BY.

UNION is not a SetOp

UNION (without ALL) is implemented as Append + a deduplication step (a Sort + Unique or HashAggregate), not as SetOp. So you'll see SetOp only for INTERSECT/EXCEPT.

Tuning notes

The cost is dominated by what feeds the two branches. A sorted SetOp sometimes spends most of its time in the Sort nodes; a HashSetOp's memory budget is the usual work_mem × hash_mem_multiplier story — if the hash spills to disk, raising work_mem for the query restores it.

If you're using INTERSECT/EXCEPT for uniqueness rather than for set semantics (e.g. as a clever way to filter), an EXISTS / NOT EXISTS subquery is often clearer and lets the planner pick a more conventional join shape.

See also