How are Firebase Reads charged on snapshot whereField? - swift

In this example from the firebase documentation:
// Create a reference to the cities collection
let citiesRef = db.collection("cities")
// Create a query against the collection.
let query = citiesRef.whereField("state", isEqualTo: "CA")
Suppose I were to add a snapshot listener to the query and use it when a page appears. Would I be charged a read for every city in the collection of "cities" or just the cities where the state is equal to CA? For the query to work, it seems like it would have to search through every city, and I'm wondering if those would count as reads.

You're only charged for documents that need to be read on the server for API calls. Queries are actually handled by accessing one or more indexes, so there is no charge for the query itself (*) only for the documents that are actually returned to the client.
(*) the only exception to this is when there are no results for a query, in which case you'll be charged 1 document read.

Related

How to sort by a field in a sub-collection in firebase cloud firestore in flutter

I am trying to query a cloud firestore database and i need it to return all the documents in the chats collection sorted by the timestamp field which is a field that all the documents in the messages sub-collection have.
i tried writing a query like this.
FirebaseFirestore.instance.collection("chats").orderBy("messages.timestamp", descending: true)].get(),
but it does not return any documents when actually there are some documents there.
Firestore can only order or filter on data in the documents that it returns. There is no ability to order or filter on data outside of those documents.
So if we want to order chats in the timestamp of the last message in that chat (a common use-case), you'll have to include a lastMessageTimestamp field in the chat document itself, and update that whenever a message is written in its messages subcollection. With that lastMessageTimestamp field in place in each chats document, you can then order and filter on that.
Create a new collection called messages and store all messages for every user there (with a user id field). Reference the message uid's via an array in each chat. This way you can easily query for the messages associated with a chat session then sort them.

Query firebase docs by subcollection value

I have a Firebase Firestore DB with this structure:
Users (collection) -> user (doc) -> Reports (subcollection) -> report (doc) -> isHandled (bool field).
In my Flutter app, I want to query Users collection, and get only the user docs in which any of their reports collection docs is not handled (isHandled field == false).
I've tried to use Firestore's collectionGroup, but it returns only 1 report doc, instead of many user docs:
await FirebaseFirestore.instance
.collectionGroup('Reports')
.where(
'isHandled',
isEqualTo: false,
)
.get();
Does Firebase Firestore support any query of a collection by its docs' subcollection values?
Thank you very much in advance!
Does Firebase Firestore support any query of a collection by its docs' subcollection values?
No. When you query a collection or subcollection, it will only consider documents immediately within it. The data from a single collection's documents is managed by an "index" - queries only use that index to rapidly find data at scale.
Collection group queries are special in that they consider all collections with the same name, but they will still not consider nested subcollections.
You could perhaps combine a collection group query with further queries to get the "parent" user document from a matched report.

how to use firestore wherefield method and adding document that collection to another collection

I am working a project to use shops. My structure data:
My code snippet
//current user sign in here and uid "VjZfKF1zmrV00C9VeTZxRmogybA2"
let currentUser = Auth.auth().currentUser
let db = Firestore.firestore()
db.collection("Blah").whereField("users", isEqualTo: currentUser?.uid).collection("otherPath").document().addDocument
I want to add data to use currentuser uid and if it is matching "Blah" inside documents then add that targeting document other collection.
.adddocumets or .setData it isn't allowed to firestore, so how can i figure it?
There is no way to send a query and an update statement to Firestore in one go. You will instead have to:
Execute a query to determine all documents that need to be updated.
Loop over the results of that query in your application code.
Update each document in turn inside that loop.
In addition, since users is an array field, you'll want to use the array-contains operator to match the correct documents.

Efficiently get user info for reviews collection query

I have a product document and a sub-collection of its reviews. Each review has the userId of the user that created the document. I want to efficiently get all users from those reviews without having to read each document beforehand.
I can query efficiently for those reviews but I can't think of a way to get the user info of the reviews i'm querying without doing a single query for the user after reading the document. Any Ideas?
productDocument.collection("reviews")
.getDocuments() { (querySnapshot, err) in
for reviewDocument in querySnapshot!.documents {
let review = reviewDocument.data()
let userId = review.senderId
usersCollection.document(userId).getDocument { (userDocument, error) in
if let userDocument = userDocument{
let user = userDocument.data()
print("\(user.name) said \(review.text)")
}
}
}
Cloud Firestore doesn't have any "join" type queries that can combine results from multiple collections. In your case, you are going to have to query and iterate each document in reviews, collect all the user IDs from those documents, then fetch each user document individually. There's no shortcut for this provided by the SDKs, though you might want to write a utility function of your own.
If this is unacceptable, consider adding another collection that you can query once that will give you everything you need. It is not uncommon to duplicate data in NoSQL type databases in order to optimize situations like this.

iOS Firestore compound query with multiple ids

This is an issue I came across while trying to mix geo location with Firestore. Long story short - I need restaurants around user's location. In order to get geo search done I use Algolia. When I do the request to Algolia it returns an array of unique restaurant IDs which correspond to Firestore document ids. This works just fine.
What makes things complicated is that I need two more conditions - I need to restrict the query to restaurants with average rating >= 8. And also I want to limit the count of the returned documents (2, 5, 20 etc).
So this is how it should look like in pseudo code:
db.restaurantsCollection
.documentIds([111, 222, 333, 444, 555, 666, 777, 888, 999])
.whereField("averageRating", isGreaterThanOrEqualTo: 8)
.order(by: "averageRating", descending: true)
.limit(to: 2)
.getDocuments()
I know as of today Firestore doesn't support queries with multiple document ids. So what is the most optimized way to perform such a query?
If I set the documentId as an id field in my document and then iterate through all of the returned ids from Algolia and do something like this (then I can do the ordering and limiting in pure Swift):
for id in ids {
db.restaurantsCollection
.whereField("averageRating", isGreaterThanOrEqualTo: 8)
.whereField("id", isEqualTo: id)
.getDocuments()
}
But still this means a lot of requests. Any other ideas?
Here is a bit more efficient way to do it:
firestoreDB
.collection("someCollection")
.whereField(FieldPath.documentID(), in: [
"05C0632C-601B-4D98-BD2B-3E809D0496B1", //up to 10 ids
"087686CA-6B21-4268-9E4C-CF833FCA92DE"
]).getDocuments { snap, error in
if let snap = snap {
snap.documents.forEach { print($0.data()) }
} else if let error = error {
print("Error: \(error)")
}
}
Getting them in batches of 10 will allow for sorting (using .order(by: ) etc.)
It is not perfect, but looks like an improvement on what you have.
You could have multiple such calls and merge the results in one, then sort those.
In case this is helpful to anyone I am posting the options you might consider if you are implementing geo queries the way I implemented them - with Algolia. So the way you do it with Algolia is to keep coordinates with your search indices.
For example:
{
"_geoloc": {
"lng": 8.507785799999999,
"lat": 47.17652589999999
},
"objectID": "1234abcd"
}
Where objectID corresponds to a document ID of a restaurant or whatever kind of venue you want to geo query.
Then you create a query through Algolia API. If you want to get all items around a CLLocation you should set it up - use query.aroundLatLng property for that. You can also set a distance property in meters. The result will be a JSON with your IDs sorted by their distance starting with the closest.
So far so good - we solved our biggest issue - querying by geo location.
Now what if I want to get the top rated from the all N IDs I got from Algolia? The sad thing is that so far there is no API to query by multiple ids. So this means if I want to get all restaurants with score >= 8, I need to make N Firestore document reads and then get the top rated. Far from ideal.
Option 1.
Save restaurant averages with Algolia data.
I know this means extra networking but this is a good way to go. Drawbacks - every time you update the restaurant average in Firestore you need to update it in Algolia also. And if you calculate averages with Cloud Functions as I do you need to set them in Algolia too. Yes, this means you need to upgrade to a paid Firebase plan since this is an external Cloud Functions call.
Now that you have averages in your Algolia payload you can easily get all restaurant IDs for your area and then sort the data by their Algolia rating client side. Once you have the ids sorted you can then request the documents from Firestore.
Option 2.
Save city name and country
You can save city and country names with your location data in Firestore. This way you can use them for your queries. Pseudo Firestore query code:
db.restaurants.where("city" == "current_city").where("country" == "current_country").where("averageRating" >= 8).getDocuments()
However this approach won't be very accurate and will mostly work for big cities. It also won't count the proximity to your location.
I hope this helps.