How To Limit CollectionGroup Queries to a Particular Path
Learn how to easily limit the sub collections searched by collectionGroup query using a pathPrefix.
In this tutorial, you will learn:
- The big problem with collectionGroup.
- How to solve the problem.
When you’re done, you’ll be using collectionGroup like a pro to target dispersed data with ease.
The Problem
collectionGroup queries are great for fast searches across dispersed but similar-use-case collections. But they have one problem: they can return too much data. For example:
db.collectionGroup("some-collection-id");
Gives you every collection and subcollection with the ID: "some-collection-id". If your database structure has two paths:
collection1/{a-doc}/some-collection-id
collection2/{a-doc}/subcollection1/{a-different-doc}/some-collection-id
And it turns out you only want data from the sub collections stored down the collection1/{a-doc}/some-collection-id path, tough luck, you’re stuck with everything.
The Solution
The solution is to use the __name__ field which is attached to every document, an orderBy, startAt, and endAt. I learned how to do this from this Stack Overflow post:
CollectionGroupQuery but limit search to subcollections under a particular document
But I’ve turned the method into a fully fledged function you can copy and paste in and use easily:
/**
export async function collectionGroupWithPathPrefixQuery(
collectionGroup: string,
pathPrefix: string,
db: admin.firestore.Firestore
): Promise<admin.firestore.Query<admin.firestore.DocumentData>> {
let startPathPrefix = pathPrefix;
let endPathPrefix = pathPrefix + "\uf8ff"; // \uf8ff is the high Unicode character for inclusive upper bound
const pathParts = pathPrefix.split("/");
if (pathParts.length % 2 !== 0) {
const collectionRef = db.collection(pathPrefix);
const firstSnapshot = await collectionRef.orderBy("__name__").limit(1).get();
// Requires an index to be built
const lastSnapshot = await collectionRef.orderBy("__name__", "desc").limit(1).get();
if (firstSnapshot.empty || lastSnapshot.empty) {
throw new Error("Collection is empty");
}
startPathPrefix = firstSnapshot.docs[0].ref.path;
endPathPrefix = lastSnapshot.docs[0].ref.path;
}
const query = db.collectionGroup(collectionGroup).orderBy("__name__").startAt(startPathPrefix).endAt(endPathPrefix);
return query;
}
In short, the great thing about this method is the following:
- It uses
orderByto make the database query on the server side. - This saves data being sent to the client, decreasing query completion time, and data transfer costs.
- It returns the query builder so you can add your own statements to it if you like.
Final Words
I hope you find this helpful, and it lets you use collectionGroup queries in places you’ve never thought to use them, or speeds up your code in places you’re already using them!