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.
Related
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.
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.
I'm trying to read from a query and NOT from an individual document within a transaction.
My write is dependant on the result of the current data in the collection. But I seem to understand that, at least for the mobile and web clients, you are not allowed to read from queries, only from individual document reads.
If that's the case, how would I achieve that since I don't know what documents I want to read ahead of time?
Specifically, I'm referring to this video from the Firebase team, at 9:38
Indeed you cannot read documents from a query inside a transaction, only individual documents.
So you would have to run the query with your conditions to determine the document IDs and then update the documents with individual updates, or with one or more batched writes instead of a transaction, if you need these writes to be atomic.
Here is a snippet that represents what I meant (in Javascript, not Flutter, but enough for a practical example):
var db = firebase.firestore();
const newDocumentBody = {
message: 'hello world'
}
db.collection('myCollection').where('message', '==', 'hello').get().then(response => {
let batch = db.batch();
response.docs.forEach((doc) => {
const docRef = db.collection('myCollection').doc(doc.id);
batch.update(docRef, newDocumentBody);
})
batch.commit().then(() => {
console.log('updated all documents inside myCollection');
})
})
I'm creating an app that has uses a firebase Cloud Firestore database. The structure seems to be collection/document/fields. I am thinking of either using the user id as the prefix to the collection name or simply a field for userId.
I'm currently using:
Firestore.firestore().collection("Events").
This could be changed to prefix event with the userId
I am currently reading everything in using:
reference(to: "Events").addSnapshotListener{ (snapshot, _) in
guard let snapshot = snapshot else {return}
for document in snapshot.documents {
// code here
}
}
It's not a really good idea to use prefixes on collection names. That doesn't work well with security rules.
The usual structure for per-user data is to have a collection with documents whose IDs are the user IDs. Then, you can further organize other data in subcollections under that document ID.
/users
/uid1
/uid2
/events
/likes
/history
Then, you can write security rules using the user's UID very easily.
First of all, this is not a regular question. It's little complicated.
App summary
Recipes app where users can search recipes by selected ingredients (collection ingredients exists in firestore db). I want to store for every ingredient statistics how much did users search with that selected ingredient, so I can show them later at the top ingredients which they used mostly for searching recipes.
This is how my collection looks like:
http://prntscr.com/nlz062
And now I would like to order recipes by statistics that created logged in user.
first = firebaseHelper
.getDb()
.collection(Constants.INGREDIENTS_COLLECTION)
.orderBy("statistics." + firebaseHelper.getCurrentUser().getUid() + ".count")
.limit(25);
If logged in user hasn't yet searched recipes with ingredients, then it should order normally. Anyway the query above is not working. Is it possible this use case to be done with Firestore.
Note: Statistics may exists or may not for logged in user, it all depends on his search.
You can't query and documents by fields that don't immediately exist within the document. Or, in other words, you can't use fields documents within subcollections that are not in the named collection being queried.
As of today (using the latest Firestore client libraries), you could instead perform a collection group query to query all of the subcollections called "statistics" for their count field. However, that will still only get you the statictics documents. You would have to iterate those documents, parse the ingredient document ID out of its reference, and individually get() each one of those documents in order to display a UI.
The collection group query would look something like this in JavaScript:
firestore
.collectionGroup("statistics")
.where(FieldPath.documentId())
.orderBy("count")
.limit(25)
You should be able to iterate those results and get the related documents with no problem.