Deep Relations
Querying relations
Section titled “Querying relations”UQL’s query syntax is context-aware. When you query a relation, the available fields and operators are automatically suggested and validated based on that related entity.
Basic Selection
Section titled “Basic Selection”You can select specific fields from a related entity using a nested object.
import { User } from './shared/models/index.js';
const users = await querier.findMany(User, { $select: { id: true, name: true, profile: { $select: { picture: true } } // Select specific fields from a 1-1 relation }, $where: { email: { $iincludes: '@example.com' } }});-- Main query with LEFT JOIN for OneToOne relationSELECT "User"."id", "User"."name", "profile"."picture" "profile.picture"FROM "User"LEFT JOIN "Profile" "profile" ON "profile"."userId" = "User"."id"WHERE "User"."email" ILIKE '%@example.com%'Advanced: Deep Selection & Mandatory Relations
Section titled “Advanced: Deep Selection & Mandatory Relations”Use $required: true to enforce an INNER JOIN (by default UQL uses LEFT JOIN for nullable relations).
import { User } from './shared/models/index.js';
const latestUsersWithProfiles = await querier.findOne(User, { $select: { id: true, name: true, profile: { $select: { picture: true, bio: true }, $where: { bio: { $ne: null } }, $required: true // Enforce INNER JOIN } }, $sort: { createdAt: 'desc' },});-- INNER JOIN enforced by $required: trueSELECT "User"."id", "User"."name", "profile"."picture" "profile.picture", "profile"."bio" "profile.bio"FROM "User"INNER JOIN "Profile" "profile" ON "profile"."userId" = "User"."id" AND "profile"."bio" IS NOT NULLORDER BY "User"."createdAt" DESCLIMIT 1Filtering on Related Collections
Section titled “Filtering on Related Collections”You can filter and sort when querying collections (One-to-Many or Many-to-Many).
import { User } from './shared/models/index.js';
const authorsWithPopularPosts = await querier.findMany(User, { $select: { id: true, name: true, posts: { $select: { title: true, createdAt: true }, $where: { title: { $iincludes: 'typescript' } }, $sort: { createdAt: 'desc' }, $limit: 5 } }, $where: { name: { $istartsWith: 'a' } }});-- Main query (parent rows)SELECT "User"."id", "User"."name" FROM "User"WHERE "User"."name" ILIKE $1-- OneToMany relation loaded via a second querySELECT "Post"."title", "Post"."createdAt", "Post"."authorId"FROM "Post"WHERE "Post"."authorId" IN ($1, $2, ...) AND "Post"."title" ILIKE '%typescript%'ORDER BY "Post"."createdAt" DESCLIMIT 5Sorting by Related Fields
Section titled “Sorting by Related Fields”UQL allows sorting by fields of related entities directly in the $sort object.
const items = await querier.findMany(Item, { $select: { id: true, name: true }, $sort: { tax: { name: 1 }, measureUnit: { name: 1 }, createdAt: 'desc' }});SELECT "Item"."id", "Item"."name"FROM "Item"LEFT JOIN "Tax" "tax" ON "tax"."id" = "Item"."taxId"LEFT JOIN "MeasureUnit" "measureUnit" ON "measureUnit"."id" = "Item"."measureUnitId"ORDER BY "tax"."name", "measureUnit"."name", "Item"."createdAt" DESC
Relation Filtering (EXISTS Subqueries)
Section titled “Relation Filtering (EXISTS Subqueries)”Filter parent entities based on conditions on their ManyToMany or OneToMany relations. UQL generates efficient EXISTS subqueries automatically.
ManyToMany
Section titled “ManyToMany”// Find all posts that have a tag named 'typescript'const posts = await querier.findMany(Post, { $where: { tags: { name: 'typescript' } },});SELECT * FROM "Post"WHERE EXISTS ( SELECT 1 FROM "PostTag" WHERE "PostTag"."postId" = "Post"."id" AND "PostTag"."tagId" IN ( SELECT "Tag"."id" FROM "Tag" WHERE "Tag"."name" = $1 ))OneToMany
Section titled “OneToMany”// Find users who have authored posts with 'typescript' in the titleconst users = await querier.findMany(User, { $where: { posts: { title: { $iincludes: 'typescript' } } },});SELECT * FROM "User"WHERE EXISTS ( SELECT 1 FROM "Post" WHERE "Post"."authorId" = "User"."id" AND "Post"."title" ILIKE '%typescript%')Relation Count Filtering ($size Subqueries)
Section titled “Relation Count Filtering ($size Subqueries)”Filter parent entities by the number of related records using $size. UQL generates efficient COUNT(*) subqueries. Accepts a number for exact match or any comparison operator ($eq, $ne, $gt, $gte, $lt, $lte, $between).
OneToMany
Section titled “OneToMany”// Find categories with at least 2 measure unitsconst categories = await querier.findMany(MeasureUnitCategory, { $where: { measureUnits: { $size: { $gte: 2 } } },});SELECT * FROM "MeasureUnitCategory"WHERE (SELECT COUNT(*) FROM "MeasureUnit" WHERE "MeasureUnit"."categoryId" = "MeasureUnitCategory"."id") >= $1 AND "deletedAt" IS NULLManyToMany
Section titled “ManyToMany”// Find items with more than 5 tagsconst items = await querier.findMany(Item, { $where: { tags: { $size: { $gt: 5 } } },});SELECT * FROM "Item"WHERE (SELECT COUNT(*) FROM "ItemTag" WHERE "ItemTag"."itemId" = "Item"."id") > $1Multiple Comparison Operators
Section titled “Multiple Comparison Operators”// Find items with between 2 and 10 tagsconst items = await querier.findMany(Item, { $where: { tags: { $size: { $between: [2, 10] } } },});