zanith

Set operations

Combine the result rows of two or more compiled queries. union dedupes, unionAll doesn't, intersect keeps rows present in both, except subtracts. The engine renumbers placeholders so the merged query has one shared parameter list.

The six helpers

ExportSQLNotes
union(parts)A UNION BDeduplicates.
unionAll(parts)A UNION ALL BPreserves duplicates. Cheaper.
intersect(parts)A INTERSECT BRows present in both.
intersectAll(parts)A INTERSECT ALL BMultiset intersection.
except(parts)A EXCEPT BRows in A not in B.
exceptAll(parts)A EXCEPT ALL BMultiset difference.

Each helper takes a CompiledQuery[] with at least two parts. Pass three or more and they fold left to right.

Compiling a query without executing it

Set operators consume CompiledQuery — get one with .toSQL() on the relational builder, or by using helpers like search() that already return a compiled query.

TS
import { unionAll } from 'zanith';
 
const adminsQ = db.user.query()
.select(({ user }) => ({ id: user.id, email: user.email }))
.where(({ user }) => user.role.eq('ADMIN'))
.toSQL();
 
const moderatorsQ = db.user.query()
.select(({ user }) => ({ id: user.id, email: user.email }))
.where(({ user }) => user.role.eq('MODERATOR'))
.toSQL();
 
const merged = unionAll([adminsQ, moderatorsQ]);
const { rows } = await adapter.execute(merged);

Parameter renumbering

Each part's $1, $2, … placeholders get shifted so the merged query has one continuous list. The merged params is just [...partA.params, ...partB.params] — no de-duplication, no re-ordering.

TS
// partA.params = ['ADMIN'], sql contains $1
// partB.params = ['MODERATOR'], sql contains $1
// merged.params = ['ADMIN', 'MODERATOR']
// merged.sql contains $1 (from A) and $2 (renumbered from B)

Dialect support

Each operator is capability-gated. setUnion, setIntersect, and setExcept are flags on the dialect — Postgres has all three; SQLite ships UNION and INTERSECT but not the ALL variants for them in older versions. Calling an unsupported operator throws.

When to reach for set ops

Use caseOperator
Merge results from two unrelated tables that share a shape (audit log + system events).unionAll
Combine a recommendation list with a manually curated list, dedup by id.union + DISTINCT ON
Show users who exist in both *paying* and *active* cohorts.intersect
Find users in *signed_up* but not in *converted*.except
Avoid IN (...) on huge id lists — sometimes a UNION ALL of small SELECTs plans better.unionAll