Skip to main content

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:

  • COMPANY
  • DEPARTMENT
  • PROJECT
  • EMPLOYEE
  • TASK

Typical topology:

  • COMPANY -> DEPARTMENT
  • DEPARTMENT -> PROJECT
  • PROJECT -> EMPLOYEE
  • PROJECT -> 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.

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)

2) Relationship Traversal with Aliases

Use aliases to shape related entity output without flattening your schema.

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
})

3) Dimensional GroupBy

Use dimensional grouping for distribution questions such as "how many projects per status?".

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"}
})

Notes:

  • dimensional keys must be property refs like $record.status
  • omit limit to return full distribution

4) Self-Group KPIs (Single-Row Totals)

Use self-group mode for whole-dataset KPIs.

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"}
})

Why this shape:

  • self-group uses select output key names in groupBy
  • orderBy on a select key 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.

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
})

Key points:

  • label traverses inline to a related label — no $alias in where required
  • $self is the alias for the current traversal level in select expressions
  • add where inside any $collect level to filter that hop (flat property conditions only)
  • nest $collect inside select to any depth

6) Relationship Matching and Direction

Use $relation when you need to constrain traversal edge type and direction.

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
})

Use timeBucket to build trend charts directly from query output.

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"}
})

8) Adoption Workflow for Teams

A high-signal workflow when introducing SearchQuery to a team:

  1. Start with records.find listing queries.
  2. Move the same where block to records.delete for controlled cleanups.
  3. Add aliases and per-record select expressions.
  4. Promote to dimensional groupBy for dashboards.
  5. Convert to self-group KPI queries for single-row metrics.
  6. Introduce nested collect when API responses must map directly to UI trees.

9) Validation Checklist

Before shipping a query:

  • groupBy exists only when select is 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 select output 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 select expressions
  • then progressively transform it into dimensional and self-group analytics