Query Clauses
Query clauses are the building blocks of ZQL queries. They allow you to refine your data selection by ordering, limiting, paging, and controlling result types. All clauses can be chained together to build complex queries.
Ordering
You can sort query results by adding an orderBy clause:
z.query.issue.orderBy('created', 'desc');
Multiple Order Clauses
Multiple orderBy clauses can be present, in which case the data is sorted by those clauses in order:
// Order by priority descending. For any rows with same priority,
// then order by created desc.
z.query.issue.orderBy('priority', 'desc').orderBy('created', 'desc');
Default Ordering
All queries in ZQL have a default final order of their primary key. Assuming the issue table has a primary key on the id column, then:
// Actually means: z.query.issue.orderBy('id', 'asc');
z.query.issue;
// Actually means: z.query.issue.orderBy('priority', 'desc').orderBy('id', 'asc');
z.query.issue.orderBy('priority', 'desc');
This ensures consistent, deterministic ordering even when your explicit order clauses result in ties.
Limiting Results
You can limit the number of rows to return with limit():
z.query.issue.orderBy('created', 'desc').limit(100);
The limit() clause is particularly useful for:
- Performance: Reducing the amount of data transferred and processed
- Pagination: Working with
start()to implement paging - Top-N queries: Getting the most recent, highest priority, etc.
// Get the 10 most recent high-priority issues
z.query.issue.where('priority', 'high').orderBy('created', 'desc').limit(10);
Paging with Start
You can start the results at or after a particular row with start():
let start: IssueRow | undefined;
while (true) {
let q = z.query.issue.orderBy('created', 'desc').limit(100);
if (start) {
q = q.start(start);
}
const batch = await q.run();
console.log('got batch', batch);
if (batch.length < 100) {
break;
}
start = batch[batch.length - 1];
}
Exclusive vs Inclusive Start
By default start() is exclusive - it returns rows starting after the supplied reference row. This is what you usually want for paging:
// Get next page of results after the last row from previous page
z.query.issue.orderBy('created', 'desc').start(lastRow).limit(50);
If you want inclusive results, you can specify the inclusive option:
z.query.issue.start(row, {inclusive: true});
Cursor-based Pagination Example
Here's a complete example of implementing cursor-based pagination:
function usePaginatedIssues(pageSize = 50) {
const [allIssues, setAllIssues] = useState<IssueRow[]>([]);
const [cursor, setCursor] = useState<IssueRow | undefined>();
const [hasMore, setHasMore] = useState(true);
const loadMore = async () => {
let query = z.query.issue.orderBy('created', 'desc').limit(pageSize);
if (cursor) {
query = query.start(cursor);
}
const newIssues = await query.run({type: 'complete'});
setAllIssues(prev => [...prev, ...newIssues]);
setHasMore(newIssues.length === pageSize);
if (newIssues.length > 0) {
setCursor(newIssues[newIssues.length - 1]);
}
};
return {allIssues, loadMore, hasMore};
}
Getting a Single Result
If you want exactly zero or one results, use the one() clause. This causes ZQL to return Row|undefined rather than Row[].
const result = await z.query.issue.where('id', 42).one().run();
if (!result) {
console.error('not found');
} else {
console.log('Found issue:', result.title);
}
One() Behavior
one()overrides anylimit()clause that is also present- Returns
Row | undefinedinstead ofRow[] - Useful for lookups by unique identifiers
- Commonly used with
whereclauses for specific records
// Get the current user's profile
const profile = await z.query.user.where('id', currentUserId).one().run();
// Get the most recent issue
const latestIssue = await z.query.issue.orderBy('created', 'desc').one().run();
Combining Clauses
All query clauses can be combined to create sophisticated queries:
// Get the 5 most recent high-priority issues assigned to a specific user,
// starting after a particular issue
z.query.issue
.where('priority', 'high')
.where('assignee', userId)
.orderBy('created', 'desc')
.start(lastSeenIssue)
.limit(5);
Order of Operations
While you can chain clauses in any order, they are conceptually applied in this sequence:
- Selection: Table selection (
z.query.issue) - Filtering:
whereclauses - Ordering:
orderByclauses - Paging:
startclause - Limiting:
limitoroneclause
// These are equivalent:
z.query.issue.limit(10).where('status', 'open').orderBy('created', 'desc');
z.query.issue.orderBy('created', 'desc').where('status', 'open').limit(10);
Performance Considerations
- Always use
orderBywhen usinglimit()to ensure consistent results - Combine
orderByandlimitfor efficient top-N queries - Use
start()instead ofOFFSETfor better performance in large datasets - Consider indexing columns used in
orderByclauses in your database
Next Steps
Now that you understand query clauses, explore these related topics:
- Filtering Data - Master the
whereclause and comparison operators - Relationships - Query related data across tables
- Query Lifecycle - Understand performance and caching behavior
- ZQL Fundamentals - Review the basics if needed