Querying Geohashes in Firestore Returns Nothing

Not able to query documents by geohash in google firestore, using created date field?

Firestore can only filter on range on a single field. Or simpler: you can only have a single orderBy clause in your query.

What you are trying to do requires two orderBy clauses, one for geohash and one for createdOn, which isn't possible. If you were to need an equality check on a second field though, that would be possible as thstt doesn't require an orderBy clause.

What I'm wondering is whether you can add a field createdOnDay that contains just the day part of createdOn in a fixed format, and then perform an in on that with 7 values (for the days of the past week?

Querying Geohashes in Firestore

I want to avoid adding the geoashes in the document restaurant as the document can be many times bigger than it should be.

Yes, don't do that because documents have limits. So there are some limits when it comes to how much data you can put into a document. According to the official documentation regarding usage and limits:

Maximum size for a document: 1 MiB (1,048,576 bytes)

As you can see, you are limited to 1 MiB total of data in a single document. When we are talking about storing text, you can store pretty much but if you are using complex objects, this is not an option.

The initial idea is to have all of the geohashes in a subcollection of the restaurant document but I found out its not possible to perform queries in subcollection of documents.

It's is a good solution. Your database structure should look like this:

Firestore-root
|
--- restaurants (collection)
|
--- restaurantId (document)
|
--- geohashes (collection)
|
--- geohashId (document)
|
--- //details about the location

So a query that can allow you get all geohash objects of a particular restaurant, will work perfectly fine.

The second idea was to extract the geohashes to top level in a collection of delivery areas

Is not a bad solution, but you should create an extra get() call, to get restaurant details but even if do this, is not a bad call, there is no problem regarding nested queries in Firestore. There is no need for OR operation.

Edit: According to your comment, yes, you're right, you cannot query the database to get back restaurant objects but we have here a work around too. In this case you should consider augmenting your data structure to allow a reverse lookup, by addining an new collection like this:

Firestore-root
|
--- geohashes (collection)
|
--- geohashId (document)
|
--- geohashRestaurants (collection)
|
--- restaurantId
|
--- //restaurant details

This tehnique is called denormalization which as you can see implies duplicating data. But you need to know that there is no problem with duplicating data, when it comes to Firebase. This is a quite common practice and for that, I recommend you see this video, Denormalization is normal with the Firebase Database. Is for Firebase realtime database but same principle apply to Cloud Firestore as well.

When you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.

The best way to query by location in firestore

Geohashes allow you to query documents within a certain geographic areas. These areas may overlap with the distance from a specific point that you specify, but you will usually need to query multiple ranges of geohashes to get all documents around a certain point.

A simple visualization of this based on what I showed in the video you linked:

Sample Image

So the query here is to return all documents within the gray circle. But in order to get those documents, we need to query all four colored ranges of geohashes.

If you have a relatively uniform distribution of the documents, you might end up with this:

Sample Image

Each pin in this image is a document that is read by Firestore, but only the green pins are documents that fall within the range you actually wanted.

In my experiments, for a uniform distribution of documents, you end up reading between 2x and 10x more documents than needed with this approach. If this overreading is a cost concern for you, consider using the Realtime Database and its geofire library for your geoqueries. While the overreading will be the same, the billing structure is based on bandwidth and the amount of bandwidth consumed by the geoindex is pretty minimal there.

Firestore - After orderby geoHash not able to sort documents based on another field

The query first orders on geoHash and only then orders on count, so the count only affects the results for documents where the geohash is the same. Since item1 and item2 have the lowest geohash values, they occur first in the index and are correctly returned.

There is no effective way to do this query on the database, unless you find a way to merge the count into the geohash values in a way to puts them in the order you want in the index. While this may be possible, the more likely workaround is to only filter on geohash and then get the top results by count in your application code.

Firestore query geohash filtering

You seem to misunderstand Firebase queries. Your query returns an open ended range:

docRef.whereField("geohash", isGreaterThanOrEqualTo: "c2b2qdd")

Specifically, this query opens the index on geohash, which is ordered lexicographically, it find the first entry that starts with c2b2qdd (or larger), and then returns all results from that point on. So that includes results like c2b2qehqtkg, since they are greater or equal than.

If you want to emulate a start with type operation, you need to use a closed range query, which includes an end condition of the range too.

docRef.whereField("geohash", isGreaterThanOrEqualTo: "c2b2qdd")
.whereField("geohash", isLessThan: "c2b2qde")

This returns all documents that have a geohash field that starts with c2b2qdd.

Listening for realtime updates of multiple queries with Firestore and geohashing

To cover an arbitrary region and radius requires between 4 and 9 geohash ranges. If you want to only update the state once all snapshots have been processed, you will need to track the completion of those queries yourself.

It's easiest to do this with getDocuments calls instead of onSnapshot, as you can use Promise.all in that case:

const promises = queries.map((query) => getDocs(query, (snapshot));
Promise.all((promises)).then((snapshots) => {
// process all snapshots and update state
});

You can also keep using onSnapshot, but in that case:

  • You'll need to track for which queries you've gotten results yet, and only update the state once all onSnapshot callbacks have fired at least once.
  • Keep in mind that onSnapshot can fire multiple times, so you may be updating the state later after all.


Related Topics



Leave a reply



Submit