Change nested sub-collections on CloudFunctions - firebase

I need help with something.
I have 2 collections on my project, one is called products and another called stores.
One product can belong to many stores, so to make the app more “performatic”, I’m duplicating the selected product information into a sub-collection of stores, in this case stores > products.
My idea is to create a cloud-function that will watch for any updates on any product and then reflect those changes on the stores > products sub-collection by updating the values.
I’m already saving it with the same ID. So for example, the productID 1 under the products collection will have the same id under the stores > products sub-collection.
My question is, how can I query only the products sub-collection? Is this possible?

It depends what do you exactly want to query.
If you want to query only ONE specific product document within the products sub-collection of ONE given store document you would do as follows:
var docRef = db.collection("stores").doc(storeId).collection("products").doc(productId)
docRef.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
If you want to query the products sub-collection of ONE given store (getting all the products docs within this sub-collection) you would do as follows:
db.collection("stores").doc(storeId).collection("products").get()
.then(function(querySnapshot) {
//....
})
.catch(function(error) {
//....
});
If you want to query the products sub-collections of ALL the stores, you would use a Collection Group Query, as follows:
var productsCollGroup = db.collectionGroup('products');
productsCollGroup.get()
.then(function (querySnapshot) {
//....
})
.catch(function(error) {
//....
});

You can check the thread on this link [1], specifically to your question you can check this answer [2].
For example they mention have an array of documents and they provide the answer of how to search for an specific document within a collection.
[1] Firestore query subcollections
[2] https://stackoverflow.com/a/46585748/9054282

Related

Flutter Firebase How to get random document

I am trying to get some random posts from Firebase. But i am unable to get random document id.
Is there any way to retrieve data from Firebase like this :-
getRandomData() async {
QuerySnapshot snapshot = await posts
.document(random.id)
.collection('userPosts')
.getDocuments();}
i am trying to say that. Now i am able to get documentID normally not in italics.so now how can i get random documentID from Firebase.
List documentIds in a list first.
var list = ['documentId1','documentId2','documentId3'];
var element = getRandomElement(list);
Then query the documentSnapshot
You can first get all documents in you collection.
Try this code:
async getMarker() {
const snapshot = await firebase.firestore().collection('userPosts').get();
const documents = [];
snapshot.forEach(doc => {
documents[doc.id] = doc.data();
});
return documents;
}
Next, from return documents you can create a list of documents id and get random numbers (document id) from this list.
The main problem here that's going to prevent you from moving forward is the fact that you don't actually have any documents nested immediately under "posts". Notice that the names of the documents are in italics. That means there isn't actually a document here at all. The reason why they are show, however, is because you have a subcollection "userPosts" nested under the path where that document ID exists.
Since you don't have any documents at all under "posts", the usual strategies to find a random document won't work at all. You're going to have to actually populate some data there to select from, or find another way to select from the data in the subcollections.
firestore()
.collection('SOURCE')
.doc(props?.route?.params?.data?.id)
.collection('userPosts')
.get()
.then(querySnapshot => {
querySnapshot.forEach(documentSnapshot => {
console.log('User ID: ', documentSnapshot.id, documentSnapshot.data());
})
})

Firestore How to retrieve subcollection

I am working on App that uses Firestore please see the following pic how I organize the data
I am trying to get data using the following code
let db = admin.firestore();
async function getMenu() {
let query = await db.collection('menu').orderBy("order", "asc").get();
const snapshot = query.docs;
snapshot.forEach((doc) => {
console.log(doc.id, '->', doc.data());
});
}
getMenu();
Output:
Feedings -> { order: 1 }
Diapers -> { order: 2 }
Can't able to get subcollection
Diapers & Wipes -> Disposable Diapers
Any Help will be appreciated.
It's not possible to get data from a subcollection using a query for a top-level collection. When you query menu, it will only ever give you document immediately within that collection. In this respect, Firestore queries are said to be "shallow".
If you want documents from a subcollection, you will need to make another query that targets that specific subcollection. For example:
db.collection("menu").doc("Diapers").collection("Diapers & Wipes").get()

Firestore/Cloud functions: Finding a document in array of document references that match criteria

Using Firebase Cloud Functions I'd like to search for documents that contain a certain other document in an array of document references. My structure looks as follows;
Users
name
email
cars
ref to cars/car1 for example
ref to cars/car2 for example
Cars
registration
make
model
There are multiple users and multiple cars. I need to search for users that have a certain 'car' in their car array.
I'm trying to write this in a Cloud Function and have the following;
admin.firestore()
.collection('users')
.where('cars', 'array-contains', registration)
.get().then(doc => {
console.log("TESTING: found the user " + doc.data().email)
return
}).catch(error => {
console.error(error);
});
I know this is currently just searching for the registration string in the array. Is there anyway to search for a specific document reference. I'm using Node.js.
Working code to get all the documents that have a document reference in an array;
// Notify the owner of the car
admin.firestore()
.collection('users')
.where('cars', 'array-contains', carRef)
.get().then(snapshot => {
snapshot.forEach(doc => {
console.log("TESTING found the user " + doc.data().email);
const message = {
notification: {
body: 'Your vehicle (' + carReg + ') recieved a report. Tap here to see!',
},
token: doc.data().cloudMessagingToken
};
sendMessage(message);
});
return
}).catch(error => {
console.error("Error finding a user that has the car in their garage");
console.error(error);
});
If you want to query using reference type fields, you will need to provide a DocumentReference type object to the query. If you pass a DocumentReference to a car, the query should work. For example:
const ref = admin.firestore().collection('Cars').doc(id)
where id is the id of the document.
However, you can't search using values of fields inside the referenced document. Firestore queries only work against data in a single collection at a time. With the way you have your data organized right now, it's not possible to make a single query for all users who have references to cars with a specific registration string field. For that query, you would need to also store an array of registration strings for each user that you could query with array-contains.
Yes, this involves duplication of data, and it's called "denormalization". This is very common in nosql type databases to enable the queries you need.

Fetch all the posts of all the users from Cloud Firestore

I am building a Flutter app. Cloud firestore database structure is given in the picture. I want to get all the posts of all the users using a StreamBuilder. How can I do that? So far I have tried this :
Stream<List<PostModel>> jobs() {
return usersCollection.snapshots().map((snapshot) {
return snapshot.documents.map((doc) {
doc['posts'].map((docu) {
return PostModel.fromSnapshot(docu);
});
}).toList();
});
}
If you want all the document in all of the subcollections called "posts" (for all users), then you probably want a collection group query using collectionGroup():
db.collectionGroup("posts").snapshots()...
This will give you all documents in any collection or subcollection called "posts", no matter where it's nested.

When I get a document do I also get sub-collections? [duplicate]

Say I have this kind of structure
A (collection): {
a (doc): {
name:'Tim',
B (collection):{
b (doc): {
color:'blue'
}
}
}
}
where A and B are collections while a and b are documents.
Is there a way to get everything contained in a root document with one query?
If I query like this
db.collection("A").doc("a").get()
I just gets name:'Tim' field. What I want is to also get all B's documents.
I basically wish my query returns
{
user:'Tim',
B (collection):{
b (doc): {
color:'blue'
}
}
}
Is it possibly or do I really need to make multiple queries one for each collection :/ ?
Say I have a really deep nested tree of collections representing the user profile, my costs will raise like hell since each time I load a user profile I have a multiplier of read requests 1 x N where N is the depth of my tree :/.
If you are concerned about costs of each pull, you will need to structure your data according to your common view / pull needs, rather than what you might prefer for a perfect structure. If you need to pull these things together every time, Consider using "maps" for things that do not actually need to be sub-collections with documents.
In this example, "preferences" is a map.
{
user: "Tim",
preferences: {
color: "blue",
nickname: "Timster"
}
}
Each document is also limited in size to 1MB - so if you need to store something for this user that will scale and continue to grow, like log records, then it would make sense to break logs into a sub-collection that only gets pulled when you want it, making each log entry a separate document... And whether all logs for all users are stored in a separate parent collection, or a sub-collection of each user really depends on how you will be pulling logs and what will result in fast speeds, balanced against costs of pulls. If you're showing this user their last 10 searches, then a search-log would make good sense as a sub-collection. If you're pulling all search data for all users for analysis, then a separate parent level collection would make sense because you can pull all logs in 1 pull, to prevent the need to pull logs from each user separately.
You can also nest your pulls and promises together for convenience purposes.
// Get reference to all of the documents
console.log("Retrieving list of documents in collection");
let documents = collectionRef.limit(1).get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log("Parent Document ID: ", doc.id);
let subCollectionDocs = collectionRef.doc(doc.id).collection("subCollection").get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log("Sub Document ID: ", doc.id);
})
}).catch(err => {
console.log("Error getting sub-collection documents", err);
})
});
}).catch(err => {
console.log("Error getting documents", err);
});
As we know querying in Cloud Firestore is shallow by default. This type of query isn't supported, although it is something Google may consider in the future.
Adding to Matt R answer, if you're using babel or you can use async/await, you can get the same result with less code(no catch/then):
// Get reference to all of the documents
console.log("Retrieving list of documents in collection");
let documents = await collectionRef.get();
documents.forEach(async doc => {
console.log("Parent Document ID: ", doc.id);
let subCollectionDocs = await collectionRef.doc(doc.id).collection("subCollection").get()
subCollectionDocs.forEach(subCollectionDoc => {
subCollectionDoc.forEach(doc => {
console.log("Sub Document ID: ", doc.id);
})
});
});

Resources