Firestore query comparing two arrays without array-contains - firebase

Is it possible to compare two arrays in a Firestore query without 'array-contains'?
Something like this:
query = query.where(
"info.currentLocation",
"==",
currentUserBasicInfo.currentLocation
);
info.currentLocation and currentUserBasicInfo.currentLocation are both arrays with mapped values. I can't change the data structure and I already have an 'array-contains' in the query. Because the two arrays have to be identical for this to run, I feel like there has to be a way to compare two arrays without having to use 'array-contains' or 'array-contains-any' or 'IN'.
Any suggestions? Is this possible?

The Problem
What you need is something similar to array-contains-all.
So let's say that in firebase you have this:
And then in your code you have something like this:
const usersIDs = [
'q4nemfzg19OCmwm1EauiZwdYD4z1',
'UzmgPRtlRSZX44erF6VrkYQ5Kyf1',
];
Then you want to compare with something like this:
yourCollectionRef.where('users', 'array-contains-all', usersIDs).get()
// or
yourCollectionRef.where('users', '==', usersIDs).get()
But this is currently not possible. The array-contains-all is still a requested feature and the == is not gonna work.
The Solution
So the good news is that I have a solution for you in case you are willing to change your collection structure.
Instead of saving the data in array, save it in a map object and the items would be the keys.
Like this:
And then you can easily use the filter == as you want. Of course you can transform the array into a map object or simply have it like this:
const usersMap = {
q4nemfzg19OCmwm1EauiZwdYD4z1: null,
UzmgPRtlRSZX44erF6VrkYQ5Kyf1: null,
};
And this would be your final query:
yourCollectionRef.where('usersMap', '==', usersMap).get();
Then if you need in the future to get all of the documents from a specific user that's inside the usersMap you can use this query here:
yourCollectionRef.where(`usersMap.${userId}`, '==', null).get();

"array-contains" method has ability to query for elements within arrays[1]. This means you can keep your elements as an array, and easily query for them without having to resort to the map hack.
If you don't want to use this method one alternative could be iterate both arrays until match the values as you need.
However, in this document[2] you can find multiple examples for perform simple and compound queries.
[1] https://firebase.google.com/docs/firestore/query-data/queries
[2] https://firebase.googleblog.com/2018/08/better-arrays-in-cloud-firestore.html

Related

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)

Flutter Firestore Can I create collections using a Map

my question is, can I create 1 document in multiple collections using a single:
firestore
.collection('map')
.doc('documentvariable')
.set(data());
Where instead "map" would be an actual Map with list of String names for collections into which I would like to add the document.
Reason why I chose a Map is because I need to be able to add more and more collections to it using UI.
Is it possible and if so is there a better solution for this?
That's not possible as .collection only accept a String. As others mentioned in the comments you can simply iterate through the list of collection names you want to add the document to. Something like this.
List<String> allCollectionNamesYouWant = ['collection1', 'collection2'];
for(String collectionName in allCollectionNamesYouWant){
FirebaseFirestore.instance
.collection(collectionName)
.doc('documentvariable')
.set(data());
}

Firestore Security Rule limit field entry

In my Firestore document, I am trying to limit the fields that the users could update to only the ones I allow. I thought about a function that could check if request.resource.keys() contains any keys that are not the ones I allow, if it does, I block update, if not, I allow update. so far, my function looks like this:
function field_limit() {
let allow_keys = ["field1", "field2", "field3"];
return request.resource.keys() in allow_keys;
}
inside the path match:
allow update: if field_limit();
When I run a unit test in Emulator that modifies "field1", it does not pass. I think I am doing something wrong here because I am comparing the whole keys list against the allow_keys list.
What should I do in order to achieve the desired functionality then?
I thought about a solution to iterate through request.resource.keys(), and see if each key is in allow_keys. If one isn't, block update. But how to traverse through each value of a list?
edit. Should be request.resource.data.keys() instead of request.resource.keys(). Otherwise, it won't work.
The in operator doesn't work the way you're expecting. See the documentation for that - it only checks if a single value is in a list.
If you want to check if a list of values contains only a subset of values in another list, you should use hasOnly instead.
request.resource.keys().hasOnly(allow_keys)
This will evaluate true if the list request.resource.keys() contains only values from the allow_keys list.

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")

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

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

Resources