SearchQuery Deep Dive: Advanced Patterns
This tutorial is a practical companion to Reusable SearchQuery.
You will compose production-style queries that RushDB teams and power users rely on every day:
- label-based traversal with aliases
- per-record and grouped aggregations
- nested collect structures
- relationship matching and direction filters
- time-series bucketing
Real-World Scenario
Assume a graph with these labels and relationships:
COMPANYDEPARTMENTPROJECTEMPLOYEETASK
Typical topology:
COMPANY->DEPARTMENTDEPARTMENT->PROJECTPROJECT->EMPLOYEEPROJECT->TASK
All examples below use canonical SearchQuery behavior:
- traversal key is the related label itself (for example
PROJECT) - use
$count: '*'for record counts,$sum: '$record.field'for sums - self-group KPI queries omit
limit
1) Reusable Where Blocks
Start by building filter fragments you can reuse across endpoints.
- Python
- TypeScript
active_engineering_projects = {
"labels": ["PROJECT"],
"where": {
"status": {"$in": ["active", "planned"]},
"budget": {"$gte": 100000},
"DEPARTMENT": {
"name": "Engineering"
}
},
"orderBy": {"budget": "desc"},
"limit": 25
}
projects = db.records.find(active_engineering_projects)
const activeEngineeringProjects = {
labels: ['PROJECT'],
where: {
status: { $in: ['active', 'planned'] },
budget: { $gte: 100000 },
DEPARTMENT: {
name: 'Engineering'
}
},
orderBy: { budget: 'desc' },
limit: 25
}
const projects = await db.records.find(activeEngineeringProjects)
2) Relationship Traversal with Aliases
Use aliases to shape related entity output without flattening your schema.
- Python
- TypeScript
- shell
project_comp = db.records.find({
"labels": ["PROJECT"],
"where": {
"DEPARTMENT": {
"$alias": "$department",
"COMPANY": {
"$alias": "$company",
"region": {"$in": ["US", "CA"]}
}
},
"EMPLOYEE": {
"$alias": "$employee",
"employmentType": {"$in": ["full_time", "contract"]}
}
},
"select": {
"projectId": "$record.$id",
"projectName": "$record.name",
"departmentName": "$department.name",
"companyName": "$company.name",
"headcount": {"$count": "$employee"},
"avgSalary": {"$avg": "$employee.salary", "$precision": 0}
},
"limit": 100
})
const projectComp = await db.records.find({
labels: ['PROJECT'],
where: {
DEPARTMENT: {
$alias: '$department',
COMPANY: {
$alias: '$company',
region: { $in: ['US', 'CA'] }
}
},
EMPLOYEE: {
$alias: '$employee',
employmentType: { $in: ['full_time', 'contract'] }
}
},
select: {
projectId: '$record.$id',
projectName: '$record.name',
departmentName: '$department.name',
companyName: '$company.name',
headcount: { $count: '$employee' },
avgSalary: { $avg: '$employee.salary', $precision: 0 }
},
limit: 100
})
curl -X POST "https://api.rushdb.com/api/v1/records/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["PROJECT"],
"where": {
"DEPARTMENT": {
"$alias": "$department",
"COMPANY": {"$alias": "$company", "region": {"$in": ["US", "CA"]}}
},
"EMPLOYEE": {"$alias": "$employee", "employmentType": {"$in": ["full_time", "contract"]}}
},
"select": {
"projectId": "$record.$id",
"projectName": "$record.name",
"departmentName": "$department.name",
"companyName": "$company.name",
"headcount": {"$count": "$employee"},
"avgSalary": {"$avg": "$employee.salary", "$precision": 0}
},
"limit": 100
}'
3) Dimensional GroupBy
Use dimensional grouping for distribution questions such as "how many projects per status?".
- Python
- TypeScript
- shell
status_breakdown = db.records.find({
"labels": ["PROJECT"],
"where": {
"DEPARTMENT": {"name": "Engineering"}
},
"select": {
"count": {"$count": "*"},
"avgBudget": {"$avg": "$record.budget", "$precision": 2}
},
"groupBy": ["$record.status"],
"orderBy": {"count": "desc"}
})
const statusBreakdown = await db.records.find({
labels: ['PROJECT'],
where: {
DEPARTMENT: { name: 'Engineering' }
},
select: {
count: { $count: '*' },
avgBudget: { $avg: '$record.budget', $precision: 2 }
},
groupBy: ['$record.status'],
orderBy: { count: 'desc' }
})
curl -X POST "https://api.rushdb.com/api/v1/records/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["PROJECT"],
"where": {"DEPARTMENT": {"name": "Engineering"}},
"select": {
"count": {"$count": "*"},
"avgBudget": {"$avg": "$record.budget", "$precision": 2}
},
"groupBy": ["$record.status"],
"orderBy": {"count": "desc"}
}'
Notes:
- dimensional keys must be property refs like
$record.status - omit
limitto return full distribution
4) Self-Group KPIs (Single-Row Totals)
Use self-group mode for whole-dataset KPIs.
- Python
- TypeScript
- shell
kpi = db.records.find({
"labels": ["TASK"],
"where": {
"completed": True,
"completedAt": {
"$gte": {"$year": 2025, "$month": 1, "$day": 1},
"$lt": {"$year": 2026, "$month": 1, "$day": 1}
}
},
"select": {
"completedCount": {"$count": "*"},
"totalHours": {"$sum": "$record.hours"},
"avgHours": {"$avg": "$record.hours", "$precision": 2}
},
"groupBy": ["completedCount", "totalHours", "avgHours"],
"orderBy": {"totalHours": "asc"}
})
const kpi = await db.records.find({
labels: ['TASK'],
where: {
completed: true,
completedAt: {
$gte: { $year: 2025, $month: 1, $day: 1 },
$lt: { $year: 2026, $month: 1, $day: 1 }
}
},
select: {
completedCount: { $count: '*' },
totalHours: { $sum: '$record.hours' },
avgHours: { $avg: '$record.hours', $precision: 2 }
},
groupBy: ['completedCount', 'totalHours', 'avgHours'],
orderBy: { totalHours: 'asc' }
})
curl -X POST "https://api.rushdb.com/api/v1/records/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["TASK"],
"where": {
"completed": true,
"completedAt": {
"$gte": {"$year": 2025, "$month": 1, "$day": 1},
"$lt": {"$year": 2026, "$month": 1, "$day": 1}
}
},
"select": {
"completedCount": {"$count": "*"},
"totalHours": {"$sum": "$record.hours"},
"avgHours": {"$avg": "$record.hours", "$precision": 2}
},
"groupBy": ["completedCount", "totalHours", "avgHours"],
"orderBy": {"totalHours": "asc"}
}'
Why this shape:
- self-group uses
selectoutput key names ingroupBy orderByon aselectkey ensures correct full-scan aggregation behavior- no
limit
5) Nested Collect for Hierarchies
Use $collect with label to return structured trees for UI rendering — no $alias declarations needed. The special alias $self refers to the current traversal level.
- Python
- TypeScript
- shell
hierarchy = db.records.find({
"labels": ["COMPANY"],
"where": {
"foundedAt": {"$lt": {"$year": 2015}}
},
"select": {
"companyName": "$record.name",
"departments": {
"$collect": {
"label": "DEPARTMENT",
"select": {
"name": "$self.name",
"projects": {
"$collect": {
"label": "PROJECT",
"select": {
"name": "$self.name",
"employees": {
"$collect": {
"label": "EMPLOYEE",
"where": {"isActive": True},
"select": {"name": "$self.name", "role": "$self.role"},
"orderBy": {"name": "asc"}
}
}
}
}
}
}
}
}
},
"limit": 20
})
const hierarchy = await db.records.find({
labels: ['COMPANY'],
where: {
foundedAt: { $lt: { $year: 2015 } }
},
select: {
companyName: '$record.name',
departments: {
$collect: {
label: 'DEPARTMENT',
select: {
name: '$self.name',
projects: {
$collect: {
label: 'PROJECT',
select: {
name: '$self.name',
employees: {
$collect: {
label: 'EMPLOYEE',
where: { isActive: true },
select: { name: '$self.name', role: '$self.role' },
orderBy: { name: 'asc' }
}
}
}
}
}
}
}
}
},
limit: 20
})
curl -X POST "https://api.rushdb.com/api/v1/records/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["COMPANY"],
"where": {
"foundedAt": {"$lt": {"$year": 2015}}
},
"select": {
"companyName": "$record.name",
"departments": {
"$collect": {
"label": "DEPARTMENT",
"select": {
"name": "$self.name",
"projects": {
"$collect": {
"label": "PROJECT",
"select": {
"name": "$self.name",
"employees": {
"$collect": {
"label": "EMPLOYEE",
"where": {"isActive": true},
"select": {"name": "$self.name", "role": "$self.role"},
"orderBy": {"name": "asc"}
}
}
}
}
}
}
}
}
},
"limit": 20
}'
Key points:
labeltraverses inline to a related label — no$aliasinwhererequired$selfis the alias for the current traversal level inselectexpressions- add
whereinside any$collectlevel to filter that hop (flat property conditions only) - nest
$collectinsideselectto any depth
6) Relationship Matching and Direction
Use $relation when you need to constrain traversal edge type and direction.
- Python
- TypeScript
authored_posts = db.records.find({
"labels": ["USER"],
"where": {
"POST": {
"$alias": "$post",
"$relation": {"type": "AUTHORED", "direction": "out"},
"title": {"$contains": "searchquery"}
}
},
"select": {
"userName": "$record.name",
"authoredPosts": {"$count": "$post"}
},
"limit": 50
})
const authoredPosts = await db.records.find({
labels: ['USER'],
where: {
POST: {
$alias: '$post',
$relation: { type: 'AUTHORED', direction: 'out' },
title: { $contains: 'searchquery' }
}
},
select: {
userName: '$record.name',
authoredPosts: { $count: '$post' }
},
limit: 50
})
7) Time Bucketing for Trends
Use timeBucket to build trend charts directly from query output.
- Python
- TypeScript
- shell
monthly_completions = db.records.find({
"labels": ["TASK"],
"where": {
"completed": True
},
"select": {
"month": {"$timeBucket": {"field": "$record.completedAt", "unit": "month"}},
"count": {"$count": "*"}
},
"groupBy": ["month"],
"orderBy": {"month": "asc"}
})
const monthlyCompletions = await db.records.find({
labels: ['TASK'],
where: {
completed: true
},
select: {
month: { $timeBucket: { field: '$record.completedAt', unit: 'month' } },
count: { $count: '*' }
},
groupBy: ['month'],
orderBy: { month: 'asc' }
})
curl -X POST "https://api.rushdb.com/api/v1/records/search" \
-H "Authorization: Bearer $RUSHDB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"labels": ["TASK"],
"where": {"completed": true},
"select": {
"month": {"$timeBucket": {"field": "$record.completedAt", "unit": "month"}},
"count": {"$count": "*"}
},
"groupBy": ["month"],
"orderBy": {"month": "asc"}
}'
8) Adoption Workflow for Teams
A high-signal workflow when introducing SearchQuery to a team:
- Start with
records.findlisting queries. - Move the same
whereblock torecords.deletefor controlled cleanups. - Add aliases and per-record
selectexpressions. - Promote to dimensional
groupByfor dashboards. - Convert to self-group KPI queries for single-row metrics.
- Introduce nested
collectwhen API responses must map directly to UI trees.
9) Validation Checklist
Before shipping a query:
groupByexists only whenselectis present- each aggregation expression uses
$-prefixed operator keys - traversal uses label keys, not custom traversal operators
- date ranges use component objects when comparing
- KPI/self-group queries do not use
limit - self-group queries order by a
selectoutput key
Wrap-Up
You now have practical patterns for composing SearchQuery in real applications.
Recommended next practice:
- clone one of your existing list queries
- add aliases and per-record
selectexpressions - then progressively transform it into dimensional and self-group analytics