How to query documents from a sub-collection, based on a main collection's field and a sub-collection's fields in cloud firestore? - firebase

I am new to firebase cloud firestore and need help understanding it a bit.
I am working on a project of the following structure.
Each "Restaurant" document contains its own "products" subcollection.
Here, I wanted to run a query to get all the products by different
restaurants that contain the tag "coffee" and are in a specific
Pincode "234144".
I tried Collection group queries by adding Pincode to each product owned by a particular restaurant but changing a Pincode would cost a lot, as all products would have to be edited, I guess.
Is there any efficient way of doing it or is it not possible in this
database in an efficient way?
Please let me know what do you think... Thank you.

If I understand correctly, you want to retrieve all products with a certain tag and pincode (I suppose this is similar to a postal zipcode?). There really are only two alternatives that I can think of:
Collection group query
As you mention, you can store both tag and pincode in product documents. Then perform a single collection group query along these lines (pardon the javascript, but I am not familiar with Dart, it should be very similar):
var products = await firestore.collectionGroup('products')
.where('tag', '==', 'coffee')
.where('pincode', '==', '234144').get();
As you have noted, with this solution you need to keep pincode in each product This piece of data is duplicated and it is normal to feel that it should be avoided because it is wasteful and dangerous (can go out of sync), but this is the way to go! It is called denormalization. This is well explained in this video by Todd Kerpelman.
You can then create a Cloud Function triggered by restaurant update to keep pincode in products in sync with the corresponding pincode in restaurants
Query restaurants then products
To keep the pincode in restaurants only, you have to do your query in two steps: first filter restaurants in the certain pincode, then filter products by tag:
// 1 - retrieve restaurants in specific pincode
var restaurants = await firestore.collection('restaurants').where('pincode', '==', '234144').get();
// 2 - For each retrieved restaurant, retrieve all products matching the tag
var products = [];
for(let i = 0; i < restaurants.docs.length; ++i) {
var p = await restaurants.docs[i].collection("products").where("tag", "==", "coffee");
products.push(p);
}
With this method, no need to duplicate pincode in each product, however your queries are less optimal, because you load potentially useless restaurants that do not serve coffee in your pincode.

I have a similar data structure, and Louis Coulet's answer helped me a lot. However, I ran into an issue, as my node/firebase environment complained that restaurants.docs[i].collection was not a function. Instead, I had to use restaurants.docs[i].ref.collection.
So, in my case, here's the code that wound up working (using the original poster's "restaurants/products" data model as an example):
// 1 - retrieve restaurants in specific pincode
const restaurants = await firestore.collection('restaurants').where('pincode', '==', '234144').get();
// 2 - For each retrieved restaurant, retrieve all products matching the tag 'coffee'
const products = [];
for (let i = 0; i < restaurants.docs.length; i++) {
await restaurants.docs[i].ref
.collection('products')
.where('tag', '==', 'coffee')
.get()
.then(s => Promise.all(s.docs.map(d => {
products.push(d);
})));
}
I have found that I need to return the sub-collection contents via some kind of promise, otherwise, after I try to get the data in the variable -- products, in this case -- it just shows up as undefined.
After I populate the products variable, I am then able to read its contents like this:
products.forEach(p => {
console.log(p.data().name);
console.log(p.data().tag);
}
I hope someone finds this useful, and many, MANY thanks to Louis Coulet for sending me/us down the correct path with this sub-collection headache :-)

Related

how to filter orderby nested list firebase to flutter App with rest database realtime

I use firebase database filter orderBy seller_id in nested list, why is the result null? thank you 🙏
:rules Firebase
var url = Uri.parse(
'https://MeDatabase.firebasedatabase.app/orders.json?auth=$authToken&orderBy="list_produk/seller_id"&equalTo="$idUser"');
try {
final response = await http.get(url);
if (response.statusCode >= 400) {
throw EasyLoading.showError('Cek Koneksi Internetmu!');
}
final extractData =
convert.jsonDecode(response.body) as Map<String, dynamic>;
print('inii orderan');
print(extractData);
if (extractData == null) {
return _orders = [];
}
} catch (e) {
throw e;
}
You're trying to index and order each order on its list_produk/seller_id property.
But if I look at your JSON data, there is no list_produk/seller_id property under the order. There's only list_produk/1/seller_id, and presumably list_produk/0/seller_id. Those are not the same as what you use in your rules/query, so the query returns no results.
Firebase Realtime Database queries function on a single, flat list of nodes. The value you order/filter on must exist at a fixed path under each direct child node.
So in your example, you can search across all orders on for example id_user, or you can search across the seller_id of the products in a specific order, but you can't search across all sellers of products in all orders.
In other words: your current data structure makes it easy to find the products and sellers per order, it does however not make it each to find the orders per product across all users. To allow that you'll want to add an additional data structure, typically known as a reverse index, that maps from each seller_id to the orders that contain products from that seller.
For more on this, I recommend checking out:
Firebase query if child of child contains a value
Firebase Query Double Nested
Many to Many relationship in Firebase

Flutter Firebase global Auto Incremental value and retrieving in document field dart

I'm quite new to Firebase Flutter. I'm developing a mobile application to share books among others.
In firebase firestore,
I have 'users' collections which contain all the user data with unique id
I have 'books' collection which contain all the book data with unique id created automatically
Also I have 'global' collection with single document with one integer field called 'bookcount'.
Users can can have many books.
Now I want to create a another unique id field for book. idea is to have simple integer id.
One way of doing this is get list of books and find the length (count) and add 1 when creating a new record. I have ruled out this method as if many users using simultaneously, I think this can lead to duplicate ids.
So I have created a another collection global with single document and field name bookcount. Which hold number of books (rough count) on books collection. So idea is each time when adding a book to a collection increase bookcount and use this value as simple unique id for a book. This bookcount may not represent actual books as user can discard the book entry before saving it, which is okay as I only need a simple unique id.
class DatabaseService {
...
...
//final CollectionReference bookCollection = Firestore.instance.collection('users');
//final CollectionReference bookCollection = Firestore.instance.collection('books');
final CollectionReference globalData = Firestore.instance.collection('global');
...
...
Future<String> bookId() async
{
String uniquebookid = await globalData.document('SomeHardcodedID').updateData(
{
'bookcount': FieldValue.increment(1)
}).then((voidvalue) async
{
String cid = await globalData.getDocuments().then((bookvalue) => bookvalue.documents.single.data['bookcount'].toString());
return cid;
});
return uniquebookid;
}//future bookId
...
...
}//class
Now this works. well somewhat, Can we do this better? In here there are two parts, first increment the value bookcount, and then retrieve it.
Can we do this in one go?
If I try to call this method consecutively really fast when returning a value it might skip few numbers. I have call this from a button and try to press as fast I could. I think counter increase but it return
same number few times. and then skip some when press again. for example 1,2,3,4,8,8,8,8,9,10,... So at counter 4 I try to press the button multiple times. I wonder how this will behave when multiple users adding multiple books at the same time.
How Can I fix this?
Please Help, Thanks.
I think the problem was since await globalData.document('SomeHardcodedID').updateData is not producing a return value (void), as soon as this fired next call also execute which is okay, which okay for most scenarios.
However if bookId called few times within very short period (milliseconds) this produce number before FieldValue.increment(1) process.

How to add 2 collections in Firestore using React Native?

I want to add 2 collections in Firestore in React Native.
Like JOIN can be used to add 2 tables. Is there any alternative for JOIN in Firestore to add collections?
I want to add these 2 collections users and users_2
How can I do this? Please help
At the time of writing it is not possible to query documents across collections in Firestore (it is apparently a feature that is on the roadmap however, see this recent blog post https://cloud.google.com/blog/products/databases/announcing-cloud-firestore-general-availability-and-updates -see bullet point "More features coming soon"-).
So that means that you'll have to issue two queries (one for each table, to get all the collection docs) and join/combine their results in your front end.
Another approach would be to duplicate your data (which is quite common in NoSQL world) and create a third collection that contains copies of all the documents.
For this last approach you could use a Batched Write as follows (in Javascript):
// Get a new write batch
var batch = db.batch();
var docData = {email: 'test#gmail.com', fullname: 'John Doe'}
// Set the value of doc in users collection
var usersRef = db.collection('users').doc();
batch.set(usersRef, docData);
// Set the value of doc in the allUsers collection (i.e. the third collection)
var allUsersRef = db.collection('allUsers').doc();
batch.set(allUsersRef, docData);
// Commit the batch
return batch.commit().then(function () {
// ...
});

Efficiently storing and getting likes in FireStore / Document DB

I have a page with posts and likes for each post.
In FireStore a collection of posts and a collection of likes, and I update the total_likes and recent likes array when a user likes or unlikes a post with cloud functions.
However, I can't figure out how to show for each post if the currently logged in user liked it or not. What's an efficient way to do that for.
Any pointers?
I believe you might need to look at data aggregration. Even though this example is with Angular, I also use the same principle in a different application: https://angularfirebase.com/lessons/firestore-cloud-functions-data-aggregation/
Alternatively, you could store the post_id's that your user likes in their own 'like_array'. Knowing which posts the user currently sees, you can cross reference the shown post_id's with the (single object) 'like_array' from the user to determine if he/she has liked a particular post. In the long run, you could disambiguate like_arrays based on days or weeks, and only query the like_arrays of this and last day/week - based on what post you are showing. If you are working with categories of posts, similar applies: different like_arrays for different categories.
Hope this helps!
One solution would be to have another collection in your Firestore database where you create a document by user, in which you save (and update) an object containing all the posts this user has liked.
Like
- likers (Collection)
- UserUID (doc)
- postIds {
post1_UID: true,
post2_UID: true
}
The idea is to use the technique described in the doc, here: https://firebase.google.com/docs/firestore/solutions/arrays#solution_a_map_of_values
I don't know which language you use in the front end but in JavaScript you would do:
var postToTestId = ....; <- You set this value as you need (e.g. as a function parameter)
firebase.auth().signInWithEmailAndPassword("...", ".....")
.then(function (info) {
var postId = 'azer';
return db.collection('likers')
.where('postIds.'+ postToTestId, '==', true)
.get();
})
.then(function(querySnapshot) {
if (querySnapshot.size > 0) {
console.log("USER LIKES THIS POST!!!");
}
})
.catch(function (error) {
console.log(error);
});
I don't think there is any solution without storing somewhere all the posts each user liked...

Firestore Cloud Functions - Keeping Count of Amount of Documents in Collection

I am trying to write a cloud function that will keep track of the amount of Documents in the Collection. There isn't a ton of documentation on this probably because of Firestore is so now.. so I was trying to think of the best way to do this.. this is the solution I come up with.. I can't figure out how to return the count
Document 1 -> Collection - > Documents
In Document 1 there would ideally store the Count of Documents in the Collection, but I can't seem to figure out how to relate this
Let's just assume Document1 is a Blog post and the subcollection is comments.
Trigger the function on comment doc create.
Read the parent doc and increment its existing count
Write the data to the parent doc.
Note: If your the count value changes faster than once-per-second, you may need a distributed counter https://firebase.google.com/docs/firestore/solutions/counters
exports.aggregateComments = functions.firestore
.document('posts/{postId}/comments/{commentId}')
.onCreate(event => {
const commentId = event.params.commentId;
const postId = event.params.postId;
// ref to the parent document
const docRef = admin.firestore().collection('posts').doc(postId)
return docRef.get().then(snap => {
// get the total comment count and add one
const commentCount = snap.data().commentCount + 1;
const data = { commentCount }
// run update
return docRef.update(data)
})
});
I put together a detailed firestore aggregation example if you need to run advanced aggregation calculations beyond a simple count.

Resources