Subqueries
EXISTS, IN (subquery), CTEs, and FROM subquery — for composing complex queries from simpler building blocks.
When to use subqueries
Subqueries let you use the result of one query inside another. This is essential for questions like "find users who have at least one published post" or "show me only data from the last 7 days." Zanith supports four subquery patterns.
inner query
EXISTS
The most common subquery pattern. "Find records where a related subquery returns at least one result." More efficient than JOIN when you only need to check existence.
import { exists, notExists } from 'zanith'; // Users who have at least one published postconst activeAuthors = await db.user.query() .where(({ user }) => exists( db.post.query() .where(({ post }) => post.published.eq(true)) ) ) .execute();NOT EXISTS
// Users who have never posted anythingconst lurkers = await db.user.query() .where(({ user }) => notExists( db.post.query() ) ) .execute();IN (subquery)
Check if a field's value appears in the results of another query:
import { inSubquery, notInSubquery } from 'zanith'; // Users whose IDs appear in the post authors listconst result = await db.user.query() .where(({ user }) => inSubquery( user.id, db.post.query() .select(({ post }) => ({ authorId: post.authorId })) ) ) .execute();CTE (WITH clause)
Common Table Expressions let you define a temporary named result set, then query from it. This makes complex queries more readable by breaking them into logical steps.
const results = await db.post.query() // Step 1: Define the CTE .withCTE( 'recent', "SELECT * FROM posts WHERE created_at > NOW() - INTERVAL '7 days'" ) // Step 2: Query FROM the CTE instead of the raw table .fromCTE('recent') // Step 3: Add additional filters .where(({ post }) => post.published.eq(true)) .limit(20) .execute(); // Generated SQL:// WITH "recent" AS (// SELECT * FROM posts WHERE created_at > NOW() - INTERVAL '7 days'// )// SELECT "posts".*// FROM "recent" AS "posts"// WHERE "posts"."published" = TRUE// LIMIT $1FROM subquery
Use a raw SQL subquery as the FROM source. Useful when you need a pre-filtered or pre-transformed dataset as the starting point.
const results = await db.post.query() .fromSubquery( 'SELECT * FROM posts WHERE published = true', [], // params (none in this case) 'pub' // alias for the subquery ) .where(({ post }) => post.title.contains('guide')) .execute(); // Generated SQL:// SELECT "pub".*// FROM (SELECT * FROM posts WHERE published = true) AS "pub"// WHERE "pub"."title" ILIKE $1Function reference
| Function | SQL pattern | Import from |
|---|---|---|
exists(subquery) | WHERE EXISTS (SELECT ...) | 'zanith' |
notExists(subquery) | WHERE NOT EXISTS (SELECT ...) | 'zanith' |
inSubquery(field, subquery) | WHERE field IN (SELECT ...) | 'zanith' |
notInSubquery(field, subquery) | WHERE field NOT IN (SELECT ...) | 'zanith' |
.withCTE(name, sql) | WITH name AS (sql) | Query builder method |
.fromCTE(name) | FROM name AS alias | Query builder method |
.fromSubquery(sql, params, alias) | FROM (sql) AS alias | Query builder method |