In Firestore security rules, is there any way to check a condition for every value in an array?
I have a document that has a subcollection. The document has an order field which is an array of IDs of documents in the subcollection; this array defines a custom user-defined order for those documents.
I want a security rule that checks that any values added to the order array correspond to a document in the subcollection (i.e. that the document exists). That is, it needs to check this condition for every value in the array.
What you call an array is known as a List in Firestore security rules, and there are no list comprehension style operations beyond the has* checks for literal values.
The problem is that such a looped check would quickly become a performance (and cost) bottle neck.
Imagine the documents in my Firestore database represent recipes and each document has a 'tags' field, which is an array containing tags for that recipe (for example: spicy, French, vegetarian).
When a user searches for some tags, I use the array-contains-any operator to pull back all the recipes that contain at least one of the tags that a user has searched for. I then order the returned documents by the number of tags that have been matched. For example, if a user searched for 'spicy French vegetarian' then the documents whose 'tags' field contains all three of those tags appears first, followed by the documents whose 'tags' field contains two of those tags etc.
I am currently sorting manually using:
const userSearchedTags: string[] = [...] // array of tags searched for by user
recipes.sort((r1, r2) => {
const r1Tags: string[] = r1.get('tags');
const r2Tags: string[] = r2.get('tags');
const r1TagMatchQuant = r1Tags.filter(r1Tag => userSearchedTags.includes(r1Tag)).length;
const r2TagMatchQuant = r2Tags.filter(r2Tag => userSearchedTags.includes(r2Tag)).length;
return r2TagMatchQuant - r1TagMatchQuant;
});
But I want to paginate this query, which requires them to be ordered by Firebase so I thought I might be able to use the orderBy clause in my query so that Firebase gives me the documents back already ordered by number of matched tags. If this worked then I would be able to use the startAfter() clause to paginate my query. I attempted this:
FirebaseFirestore.instance
.collection('recipes')
.where('tags', arrayContainsAny: userSearchedTags)
.orderBy('tags')
.get();
But it does not return the documents ordered by number of matched tags. I thought it might be returning the documents ordered by the length of the 'tags' array on each document, but further testing has shown it is not doing that either. So my question is how is Firebase ordering these documents when I pass 'tags' (a field that is an array) to the orderBy clause?
Also, is there a way to get Firebase to return the documents to me ordered by the number of matched tags or is the way I am doing it now (manually sorting after pulling back all documents with at least one matching tag) the only way?
The order in which document are returned is typically the same as however the documents are present in the index that is being searched. Assuming your tags are strings, then according to the documentation on data types that'd be:
UTF-8 encoded byte order
So I expect it to be the order of that first tag that it matches, and definitely not the number of tags matched that you were hoping for. That type of results prioritization based on a calculated value is simply not something Firestore queries are made for as it would require that Firestore reorders the matches before returning them to you.
I had a collection with an embedMany attribute using strategy=set, so an ArrayCollection was stored. However we deleted some items from the array and now there are some documents with keys not sequential integers.
I need to solve this inconsistence, how can I do that?
You could use $type operator and query for all documents where your embedManyField is of type object. Once you have these documents, apply array_values to fields where array shall be stored and save them again. Also to avoid such situations in future you should change your collection's strategy to either setArray or atomicSetArray.
In my synfony 2 project, I'm filtering search results using a query builder. In my MongoDB i have some values in an array.
Query Bulider has the "in" operator that allows to query for values that equal one of many in an array. I wanted to perform the opposite operation, i.e. given a single value, query for entries in the data base that contain an array, that contains my value.
For instance, say I have this entry in my MongoDB:
{
"_id": 123,
"name": "John",
"countries_visited":
[
"Australia"
"Bulgaria",
"Canada"
]
}
And I want to query my database for persons who have visited "Canada". Right now, I'm using the where attribute as follows, but I'm looking for a better way to do this.
$qb->field('countries_visited')->where("function(){
return this.indexOf(".$countryName.") > -1
}");
edit:
The in and notIn operator receives an array as parameter and compares it against a single value in MongoDB. I need to provide a single parameter and apply it to an array field in MongoDB, hence "inverse in". I guess I need a contains operator if there's such a thing.
Interesting, MongoDB takes care of this automatically. If querying for a single value against an array field, Mongo will assume you want the check the array if it contains the value.
Taken from the docs:
Match an Array Element
Equality matches can specify a single element in the array to match. These specifications match if the array contains at least one element with the specified value.
So you should be able to do
$users = $dm->getRepository('User')->findOneBy([
'countries_visited' => 'Canada'
]);
or
$qb->field('countries_visited')->equals('Canada');
What I'm trying to do:
Filter a field of a collection that matches a given condition. Instead of returning every item in the field (which is an array of items), I only want to see matched items.
Similar to
select items from test where items.histPrices=[10,12]
It is also similar to what's found on the mongodb website here: http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields
Here's what I have been trying:
db.test.save({"name":"record", "items":[{"histPrices":[10,12],"name":"stuff"}]})
db.test.save({"name":"record", "items":[{"histPrices":[10,12],"name":"stuff"},
{"histPrices":[12,13],"name":"stuff"},{"histPrices":[11,14],"name":"stuff"}]})
db.test.find({},{"name":1,"items.histPrices":[10, 12]})
It will return all the objects that have a match for items.histPrices:[10,12], including ALL of the items in items[]. But I don't want the ones that don't match the condition.
From the comments left on Mongodb two years ago, the solution to get only the items with that histPrices[10,12] is to do it with javascript code, namely, loop through the result set and filter out the other items.
I wonder if there's a way to do that with just the query.
Your find query is wrong
db.test.find({},{"name":1,"items.histPrices":[10, 12]})
Your condition statement should be in the first part of the find statement.In your query {} means fetch all documents similar to this sql
select items from test (no where clause)
you have to change your mongodb find to
db.test.find({"items.histPrices":[10, 12]},{"name":1})
make it work
since your items is an array and if you wanted to return only the matching sub item, you have to use positional operator
db.test.find({"items.histPrices":[10, 12]},{"name":1,'items.$':1})
When working with arrays Embedded to the Document, the best approach is the one suggested by Chien-Wei Huang.
I would just add another aggregation, with the $group (in cases the document is very long, you may not want to retrieve all its content, only the array elements) Operator.
Now the command would look like:
db.test.aggregate({$match:{name:"record"}},
{$unwind:"$items"},
{$match {"items.histPrices":[10, 12]}},
{$group: {_id: "$_id",items: {$push: "$items"}}});)
If you are interested to return only one element from the array in each collection, then you should use projection instead
The same kind of issue solved here:
MongoDB Retrieve a subset of an array in a collection by specifying two fields which should match
db.test.aggregate({$unwind:"$items"}, {$match:{"items.histPrices":[10, 12]}})
But I don't know whether the performance would be OK. You have to verify it with your data.
The usage of $unwind
If you want add some filter condition like name="record", just add another $march at first, ex:
db.test.aggregate({$match:{name:"record"}}, {$unwind:"$items"}, {$match:{"items.histPrices":[10, 12]}})
https://jira.mongodb.org/browse/SERVER-828
Get particular element from mongoDB array
MongoDB query to retrieve one array value by a value in the array