Firestore: How to query data from a map of an array - firebase

Here is the schema for test db:
I want to write a query that can get all the documents where contacts have id=1 in any of the array index.
I have checked array_contains operator for firestore but the thing is my array have map which then have field id.
Thanks

You currently can't build a query that looks for an object field within an array. The only things you can find in an array are the entire contents of an array element, not a part of an element.
With NoSQL type databases, the usual practice is to structure you data in a way that suits your queries. So, in your case, you're going to have to structure your data to let you find documents where an array item contains a certain string. So, you could create another array that contains just the strings you want to query for. You will have make sure to keep these arrays up to date.
You could also reconsider the use of an array here. Arrays are good for positional data, but unless these contacts need to be stored in a certain order, you might want to instead store an object whose keys are the id, and whose field data also contains the id and name.

As Doug said, you can't query it,
but, if you could structure your data to something that looks like this
Store your data as a map
Use id as key and name as value
Now you can write a query that can get all the documents where contacts have id=1
db.collection("test").where("contacts.1", ">=", "").get()

If you have an array of maps or objects and want to get the docoments that contains this specific map/object like this ?
array_of_maps.png
you can use
queryForCategory(categName: string, categAlias: string): Observable[] {
firestore.collection('<Collection name>', ref =>
ref.where('categories',
"array-contains", {
"alias": categAlias,
"title": categName
}

One soulions is as #Doug said, to add array with the data you need to query, and keep it uptodate.
Another solution is to make contacts as sub collection of the document, an then you could make queries of the contacts and get its parrent.
final Query contacts = db.collectionGroup("contacts").whereEqualTo("id", 1);
for (DocumentSnapshot document : querySnapshot.get().getDocuments()) {
System.out.println(document.getId());
System.out.println(document.getString("parent_id"));
}
If you don't want to add parent as another field, you can get it from parent reference, check this answer https://stackoverflow.com/a/56223319/1278463
snapshot.getRef().getParent().getParent().collection("people")
https://firebase.google.com/docs/firestore/query-data/queries#collection-group-query

Related

Firestore: Wherein custom object array?

im trying to make a sort of chat app with the following architecture:
Its currently working as follows: in a root doc, there is an array of chat objects. Each chat object spawns a message doc, which contains an array of message objects. Same logic for comments.
Im thinking about a function to update all posts relative to the associated user, IE if a user changes their name all associated comments will be updated. Is this possible with the WhereIn() function? Or should i edit the architecture to something more like each document being its own message/comment? Thanks!
An in clause checks if a specific field is equal to one of a set of values, which doesn't apply here.
You might be thinking of array-contains, but that wouldn't work in your current structure either. The array-contains only matches exact an item in the array if it completely/exactly matches the value in the query.
The common way to allow such a query is to add a participants array field to each document where you store the UIDs of all participants in that doc. Then you can do an array-contains query against that.

how to query nested maps inside an array field in firebase

I have structured my data in Firebase such that I have a collection of 'users' and each user has various fields as well as a 'skillsIndustry' Array field which contain Maps items (that have fields 'experience' and 'industry').
Now I would like to query my collection of users so that I can ask for all users that have worked in 'Airlines' with experience code of >= 1
Is this possible at all? Or do I have to create sub collections and then use Collection Group Queries?
Many thanks
No, this is not possible. You can use array-contains to query for the entire object, as explained in this SO answer, but not to query with > or <.
You could create a subcollection, but you can probably find a solution based on the duplication of the data in the same document, which is a common approach in the NoSQL world.
For example, you could have some extra fields named airlinesExperience, shippingExperience, etc. Then you use the skillsIndustry Array field when you want to display all the skills and years of experience, but you use the xxxExperience fields when you want to query.
Another possibility would be to use a map of key/value pairs like
key = "Airlines" / value = 1
key = "Shipping" / value = 3
Then you can query with:
firebase
.firestore()
.collection('users')
.where('skillsIndustry.Shipping', '>', 2)

Is it possible to fetch all documents whose sub-collection contains a specific document ID?

I am trying to fetch all documents whose sub-collection contain a specific document ID. Is there any way to do this?
For example, if the boxed document under 'enquiries' sub-collection exists, then I need the boxed document ID from 'books' collection. I couldn't figure out how to go backwards to get the parent document ID.
I make the assumption that all the sub-collections have the same name, i.e. enquiries. Then, you could do as follows:
Add a field docId in your enquiries document that contains the document ID.
Execute a Collection Group query in order to get all the documents with the desired docId value (Firestore.instance.collectionGroup("enquiries").where("docId", isEqualTo: "ykXB...").getDocuments()).
Then, you loop over the results of the query and for each DocumentReference you call twice the parent() methods (first time you will get the CollectionReference and second time you will get the DocumentReference of the parent document).
You just have to use the id property and you are done.
Try the following:
Firestore.instance.collection("books").where("author", isEqualTo: "Arumugam").getDocuments().then((value) {
value.documents.forEach((result) {
var id = result.documentID;
Firestore.instance.collection("books").document(id).collection("enquiries").getDocuments().then((querySnapshot) {
querySnapshot.documents.forEach((result) {
print(result.data);
});
First you need to retrieve the id under the books collection, to be able to do that you have to do a query for example where("author", isEqualTo: "Arumugam"). After retrieving the id you can then do a query to retrieve the documents inside the collection enquiries
For example, if the boxed document under 'enquiries' sub-collection exists, then I need the boxed document ID from 'books' collection.
There is no way you can do that in a single go.
I couldn't figure out how to go backwards to get the parent document ID.
There is no going back in Firestore as you probably were thinking. In Firebase Realtime Database we have a method named getParent(), which does exactly what you want but in Firestore we don't.
Queries in Firestore are shallow, meaning that it only get items from the collection that the query is run against. Firestore doesn't support queries across different collections in one go. A single query may only use the properties of documents in a single collection. So the solution to solving your problem is to perform two get() calls. The first one would be to check that document for existence in the enquiries subcollection, and if it exists, simply create another get() call to get the document from the books collection.
Renaud Tarnec's answer is great for fetching the IDs of the relevant books.
If you need to fetch more than the ID, there is a trick you could use in some scenarios. I imagine your goal is to show some sort of an index of all books associated with a particular enquiry ID. If the data you'd like to show in that index is not too long (can be serialized in less than 1500 bytes) and if it is not changing frequently, you could try to use the document ID as the placeholder for that data.
For example, let's say you wanted to display a list of book titles and authors corresponding to some enquiryId. You could create the book ID in the collection with something like so:
// Assuming admin SDK
const bookId = nanoid();
const author = 'Brandon Sanderson';
const title = 'Mistborn: The Final Empire';
// If title + author are not unique, you could add the bookId to the array
const uniquePayloadKey = Buffer.from(JSON.stringify([author, title])).toString('base64url');
booksColRef.doc(uniquePayloadKey).set({ bookId })
booksColRef.doc(uniquePayloadKey).collection('enquiries').doc(enquiryId).set({ enquiryId })
Then, after running the collection group query per Renaud Tarnec's answer, you could extract that serialized information with a regexp on the path, and deserialize. E.g.:
// Assuming Web 9 SDK
const books = query(collectionGroup(db, 'enquiries'), where('enquiryId', '==', enquiryId));
return getDocs(books).then(snapshot => {
const data = []
snapshot.forEach(doc => {
const payload = doc.ref.path.match(/books\/(.*)\/enquiries/)[1];
const [author, title] = JSON.parse(atob(details));
data.push({ author, title })
});
return data;
});
The "store payload in ID" trick can be used only to present some basic information for your child-driven search results. If your book document has a lot of information you'd like to display once the user clicks on one of the books returned by the enquiry, you may want to store this in separate documents whose IDs are the real bookIds. The bookId field added under the unique payload key allows such lookups when necessary.
You can reuse the same data structure for returning book results from different starting points, not just enquiries, without duplicating this structure. If you stored many authors per book, for example, you could add an authors sub-collection to search by. As long as the information you want to display in the resulting index page is the same and can be serialized within the 1500-byte limit, you should be good.
The (quite substantial) downside of this approach is that it is not possible to rename document IDs in Firestore. If some of the details in the payload change (e.g. an admin fixes a book titles), you will need to create all the sub-collections under it and delete the old data. This can be quite costly - at least 1 read, 1 write, and 1 delete for every document in every sub-collection. So keep in mind it may not be pragmatic for fast changing data.
The 1500-byte limit for key names is documented in Usage and Limits.
If you are concerned about potential hotspots this can generate per Best Practices for Cloud Firestore, I imagine that adding the bookId as a prefix to the uniquePayloadKey (with a delimiter that allows you to throw it away) would do the trick - but I am not certain.

Firestore - query where filter of object in array field

I have question about query in firecloud.
For example, see my document:
i need filter all documents where the account_id is "value" and "approve" is true. How i do it?
I tried: where("members.account_id", "==, "qgZ564nfEaNP3Nt4cW1K3jCeVlY2") and not work:
See, this is a question that not for specific language, just about firestore query, after undestand, i go apply it in my code.
I'm programing in flutter.
If you query like this:
where("members.account_id", "==", "qgZ564nfEaNP3Nt4cW1K3jCeVlY2")
Firebase checks if there's a nested field /members/account_id with the given value. And such a field doesn't exist, because the value is actually at /members/0/account_id.
Since you don't know the position in the array the the ID exists, you instead want to use an array-contains condition. With array-contains Firestore checks if the array you contains the exact member that you specify.
With your current structure you can check if the member exists with:
.where("members", "array-contains", { accountId: "qgZ564nfEaNP3Nt4cW1K3jCeVlY2", approved: true })
So you must know the entire data of the array element in order to use array-contains.
If you only know a single member, while you're storing multiple members, you can't use array-contains. In that case, I recommend adding an additional member_accountIds field to each document, where you store just their account IDs:
member_accountIds: ["qgZ564nfEaNP3Nt4cW1K3jCeVlY2", ... ]
With that in place, you can query like this:
.where("members_accountIds", "array-contains", "qgZ564nfEaNP3Nt4cW1K3jCeVlY2")

Select documents where array field contains one of value from given array (array matching)

Consider the following data in Firestore:
I also have a set of selected by the user he's interested in: ['python', 'sql', 'swift']
How to get all the documents from my collection where tags field contains at least one of tags selected by the user? I'm aware that Firestore doesn't have such operator out of the box. Probably I need to rethink the way I store data somehow? Please point me to the right direction.
With the way you have your data structured, you will need to make one array-contains type query for each of the tags the user is interested in, then merge the results of each of those queries in your app code.

Resources