Subqueries
Subqueries let you filter based on conditions that depend on related data—check if related records exist, or if values appear in another query’s results.
EXISTS
Section titled “EXISTS”Check if related records exist:
import { exists, fieldRef } from "@nicia-ai/typegraph";
// Find people who have authored at least one PRconst authors = await store .query() .from("Person", "p") .whereNode("p", () => exists( store .query() .from("PullRequest", "pr") .traverse("author", "e", { direction: "in" }) .to("Person", "author") .whereNode("author", (a) => a.id.eq(fieldRef("p", ["id"]))) .select((ctx) => ({ id: ctx.pr.id })) .toAst() ) ) .select((ctx) => ctx.p) .execute();NOT EXISTS
Section titled “NOT EXISTS”Find records without related records:
import { notExists, fieldRef } from "@nicia-ai/typegraph";
// Find people with no pull requestsconst nonContributors = await store .query() .from("Person", "p") .whereNode("p", () => notExists( store .query() .from("PullRequest", "pr") .traverse("author", "e", { direction: "in" }) .to("Person", "author") .whereNode("author", (a) => a.id.eq(fieldRef("p", ["id"]))) .select((ctx) => ({ id: ctx.pr.id })) .toAst() ) ) .select((ctx) => ctx.p) .execute();Check if a value is in a subquery result set:
import { inSubquery, fieldRef } from "@nicia-ai/typegraph";
// Find people who work at tech companiesconst techWorkers = await store .query() .from("Person", "p") .whereNode("p", () => inSubquery( fieldRef("p", ["companyId"]), store .query() .from("Company", "c") .whereNode("c", (c) => c.industry.eq("Technology")) .aggregate({ id: fieldRef("c", ["id"], { valueType: "string" }), }) .toAst() ) ) .select((ctx) => ctx.p) .execute();NOT IN
Section titled “NOT IN”Exclude values that appear in a subquery:
import { notInSubquery, fieldRef } from "@nicia-ai/typegraph";
// Find people not in the blocklistconst allowedUsers = await store .query() .from("Person", "p") .whereNode("p", () => notInSubquery( fieldRef("p", ["id"]), store .query() .from("BlockedUser", "b") .aggregate({ userId: fieldRef("b", ["props", "userId"], { valueType: "string" }), }) .toAst() ) ) .select((ctx) => ctx.p) .execute();fieldRef()
Section titled “fieldRef()”The fieldRef() function creates a reference to a field in the outer query for use in subquery predicates:
import { fieldRef } from "@nicia-ai/typegraph";
fieldRef("alias", ["field"]) // Reference a single fieldfieldRef("alias", ["nested", "path"]) // Reference a nested fieldParameters:
| Parameter | Type | Description |
|---|---|---|
alias | string | The alias of the node/edge in the outer query |
path | string[] | Path to the field (array for nested access) |
Helpers Reference
Section titled “Helpers Reference”| Function | Description |
|---|---|
exists(subqueryAst) | True if subquery returns any rows |
notExists(subqueryAst) | True if subquery returns no rows |
inSubquery(fieldRef, subqueryAst) | True if field value is in subquery results |
notInSubquery(fieldRef, subqueryAst) | True if field value is not in subquery results |
For inSubquery() and notInSubquery(), the subquery must project exactly one
scalar column. Prefer aggregate({ ... }) with a single field.
Real-World Examples
Section titled “Real-World Examples”Users with Recent Activity
Section titled “Users with Recent Activity”// Find users who logged in within the last 7 daysconst activeUsers = await store .query() .from("User", "u") .whereNode("u", () => exists( store .query() .from("LoginEvent", "e") .whereNode("e", (e) => e.userId.eq(fieldRef("u", ["id"])) .and(e.timestamp.gte(sevenDaysAgo)) ) .select((ctx) => ({ id: ctx.e.id })) .toAst() ) ) .select((ctx) => ctx.u) .execute();Products Not in Any Cart
Section titled “Products Not in Any Cart”// Find products that haven't been added to any cartconst unpopularProducts = await store .query() .from("Product", "p") .whereNode("p", () => notExists( store .query() .from("CartItem", "ci") .whereNode("ci", (ci) => ci.productId.eq(fieldRef("p", ["id"]))) .select((ctx) => ({ id: ctx.ci.id })) .toAst() ) ) .select((ctx) => ctx.p) .execute();Users in Specific Teams
Section titled “Users in Specific Teams”// Find users who are members of either the engineering or design teamconst targetTeamIds = ["team-eng", "team-design"];
const teamMembers = await store .query() .from("User", "u") .whereNode("u", () => inSubquery( fieldRef("u", ["id"]), store .query() .from("TeamMembership", "tm") .whereNode("tm", (tm) => tm.teamId.in(targetTeamIds)) .aggregate({ userId: fieldRef("tm", ["props", "userId"], { valueType: "string", }), }) .toAst() ) ) .select((ctx) => ctx.u) .execute();Query Debugging
Section titled “Query Debugging”For debugging or advanced use cases, you can inspect the query AST or generated SQL.
View the AST
Section titled “View the AST”const query = store .query() .from("Person", "p") .whereNode("p", (p) => p.status.eq("active")) .select((ctx) => ctx.p);
const ast = query.toAst();console.log(JSON.stringify(ast, null, 2));View Generated SQL
Section titled “View Generated SQL”toSQL() returns the SQL text and bound parameters for the current backend dialect:
const { sql, params } = query.toSQL();console.log("SQL:", sql);console.log("Parameters:", params);This is useful for:
- Debugging query behavior
- Understanding performance characteristics
- Logging queries in production
- Running the query with a custom executor