Firebase Querying

Query based on multiple where clauses in Firebase

Using Firebase's Query API, you might be tempted to try this:

// !!! THIS WILL NOT WORK !!!
ref
.orderBy('genre')
.startAt('comedy').endAt('comedy')
.orderBy('lead') // !!! THIS LINE WILL RAISE AN ERROR !!!
.startAt('Jack Nicholson').endAt('Jack Nicholson')
.on('value', function(snapshot) {
console.log(snapshot.val());
});

But as @RobDiMarco from Firebase says in the comments:

multiple orderBy() calls will throw an error

So my code above will not work.

I know of three approaches that will work.

1. filter most on the server, do the rest on the client

What you can do is execute one orderBy().startAt()./endAt() on the server, pull down the remaining data and filter that in JavaScript code on your client.

ref
.orderBy('genre')
.equalTo('comedy')
.on('child_added', function(snapshot) {
var movie = snapshot.val();
if (movie.lead == 'Jack Nicholson') {
console.log(movie);
}
});

2. add a property that combines the values that you want to filter on

If that isn't good enough, you should consider modifying/expanding your data to allow your use-case. For example: you could stuff genre+lead into a single property that you just use for this filter.

"movie1": {
"genre": "comedy",
"name": "As good as it gets",
"lead": "Jack Nicholson",
"genre_lead": "comedy_Jack Nicholson"
}, //...

You're essentially building your own multi-column index that way and can query it with:

ref
.orderBy('genre_lead')
.equalTo('comedy_Jack Nicholson')
.on('child_added', function(snapshot) {
var movie = snapshot.val();
console.log(movie);
});

David East has written a library called QueryBase that helps with generating such properties.

You could even do relative/range queries, let's say that you want to allow querying movies by category and year. You'd use this data structure:

"movie1": {
"genre": "comedy",
"name": "As good as it gets",
"lead": "Jack Nicholson",
"genre_year": "comedy_1997"
}, //...

And then query for comedies of the 90s with:

ref
.orderBy('genre_year')
.startAt('comedy_1990')
.endAt('comedy_2000')
.on('child_added', function(snapshot) {
var movie = snapshot.val();
console.log(movie);
});

If you need to filter on more than just the year, make sure to add the other date parts in descending order, e.g. "comedy_1997-12-25". This way the lexicographical ordering that Firebase does on string values will be the same as the chronological ordering.

This combining of values in a property can work with more than two values, but you can only do a range filter on the last value in the composite property.

A very special variant of this is implemented by the GeoFire library for Firebase. This library combines the latitude and longitude of a location into a so-called Geohash, which can then be used to do realtime range queries on Firebase.

3. create a custom index programmatically

Yet another alternative is to do what we've all done before this new Query API was added: create an index in a different node:

  "movies"
// the same structure you have today
"by_genre"
"comedy"
"by_lead"
"Jack Nicholson"
"movie1"
"Jim Carrey"
"movie3"
"Horror"
"by_lead"
"Jack Nicholson"
"movie2"

There are probably more approaches. For example, this answer highlights an alternative tree-shaped custom index: https://stackoverflow.com/a/34105063


If none of these options work for you, but you still want to store your data in Firebase, you can also consider using its Cloud Firestore database.

Cloud Firestore can handle multiple equality filters in a single query, but only one range filter. Under the hood it essentially uses the same query model, but it's like it auto-generates the composite properties for you. See Firestore's documentation on compound queries.

Querying part of a string in firebase

Is it possible to query just part of the name and get the data Like I have data 12345678 can I somehow just search for 1234?

Sure it is. When it comes to Firestore, you can simply use .startAt() as seen in the following query:

db.collection("collName").orderBy("data").startAt(1234);

When it comes to the Realtime Database, you can use .startAt() too, but as seen below:

db.child("nodeName").orderByChild("data").startAt(1234);

But remember, both queries will return elements that are greater than 1234. Since 12345678 is greater, it will be present in the result set.

Are complex compound queries possible in Firebase Firestore?

This type of relational data doesn't suit a NoSQL document database very well (especially when disqualifying results based on if you've seen them before), but lets ignore that for now.

Combining condition 1, 2 and 3 is possible. But you will need to disqualify results based on condition 4 on the side requesting the data (i.e. on the client/backend - Firestore's indexes can't handle it). This is because conditions 2 and 4 both rely on arrays of data and you are limited to querying against one at a time. As the number of user matches is likely to be low vs the number of users on the platform, condition 4 is better suited to code-based filtering. These limitations are well documented.

Note: Due to the sensitive nature of a user's dating profile, a profile should only contain a user's ID and profile data (no emails or phone numbers). Furthermore, you should consider serving profiles via a regulated backend such as Cloud Functions rather than just making them client-readable directly which could lead to platform abuse.

const profilesColRef = collection('profiles');

const potentialMatchesQuery = query(
profilesColRef,
where('dob', '>=', '1996-01-01'), // use YYYY-MM-DD format (numeric/date values can get messy with timezones if not careful)
where('dob', '<=', '2000-01-01'),
where('gender', '==', 'M'),
where('interests', 'array-contains-any', userInterestArray), // supports up to 10 per query! Split into chunks of 10 and merge results for more interests
limit(50)
);

const querySnapshot = await getDocs(potentialMatchesQuery)
const results = querySnapshot.docs
.filter((doc) => !userPreviousMatches.includes(doc.id))
.map((doc) => ({ id: doc.id, ...doc.data() }) // expand to basic JavaScript object

// results now contains up to 50 matches
// return results to client/use results

Adding dynamic where conditions to a firebase query

You're almost there. Since Query is the parent class of Collection, you can do:

Query query = _mainCollection;
params.forEach((k, v) => {
query = query.where(k, isEqualTo: v)
});
var result = await query!.get();

firestore query document that have a collection contains a document

Firestore queries can only order/filter on fields in the documents that they return. There is no way to filter on fields in other documents, or the existence of other documents.

The common way to deal with this is to have a field in the parent document that indicates whether the child document you're looking for exists, and updated that on every relevant write. Then you can use that field to filter the documents in collection A.



Related Topics



Leave a reply



Submit