MongoDB - Match all document not containing a combination of value - arrays

Let's say I have three document structured like so :
{
"_id": 1,
"conditions": [
["Apple", "Orange"],
["Lemon"],
["Strawberry"]
]
},
{
"_id": 2,
"conditions": [
["Apple"],
["Strawberry"]
]
},
{
"_id": 3,
"conditions": [
["Apple", "Lime"]
]
}
And I have an array, I'll call it ARC for this example :
ARC = [
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
]
I would like to return all document in which all conditions subarray values can't be found in the ARC array.
For example, with the data above, the first document should be returned because :
The Apple AND Orange combination is not in the ARC array
Lemon is not in the ARC array
Strawberry is not in the ARC array
The second document shouldn't be returned because :
Apple is in the ARC array
And the third document shouldn't be returned because :
The Apple AND Lime combination is in the ARC array
I've tried
db.example.find({"conditions": {$not: {$elemMatch: {$all: [ARC]}}}})
But it seems way too simple.. So, as expected, it doesn't work.
I know mongoDB is pretty powerful with all the aggregation and stuff but I'm a bit lost.
Do you know if it's possible with a query alone and if so, what should I look for ?

The query below should solve your problem.
var ARC = [
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
];
db.test.find(
{ $expr: {
$eq: [
{ $filter: { input: "$conditions", as: "c", cond: { $setIsSubset: [ "$$c", ARC] } } },
[ ]
]
}
}
)
It's made up of lots of parts so I'll try to break it down a bit, The first part is $expr within a find (or can be used within a $match in an aggregation) this allows us aggregation expressions within the query. So this allows us to use a $filter.
The $filter expression allows us to filter down the arrays in the condition field to check if any are a subset of the array ARC passed in.
We can actually take that filter an execute it on its own using an aggregation query:
db.test.aggregate([
{ $project: {
"example" : { $filter: { input: "$conditions", as: "c", cond: { $setIsSubset: [ "$$c", ARC] } } }
} }])
{ "_id" : 1, "example" : [ ] }
{ "_id" : 2, "example" : [ [ "Apple" ] ] }
{ "_id" : 3, "example" : [ [ "Apple", "Lime" ] ] }
The last part of the query is the $eq which is taking the value that is created with the filter and then matching it against an empty array [ ].

This is an aggregation approach. You should use $setIsSubset.
Below should be helpful:
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
true,
{
$allElementsTrue: {
$map: {
input: "$conditions",
as: "c",
in: {
$not: {
$setIsSubset: [
"$$c",
[
"Apple",
"Lime",
"Banana",
"Avocado",
"Cherry"
]
]
}
}
}
}
}
]
}
}
}
])
MongoPlayGroundLink

Related

Remove only one of the selected elements from the array in mongodb

I have an array that is like [1,2,1,2,3,5,2]. And I want to remove only one element amongst the selected elements. I used $pull operator and it doesn't work as I required, it remove all elements I specified.
db.user.updateOne({_id: ...}, {$pull:{'array': 1}})
I tried it and give this result: [2,2,3,5,2].
is there any way to get the result: [2,1,2,3,5,2]
This feature does not exist (and won't), as you can see in this Jira. ticket they choose they won't do this.
Here is a hacky work around - the strategy will be to find the index of the first matching element and slice it out of the array, like so:
db.collection.update({},
[
{
"$set": {
"array": {
"$concatArrays": [
{
$cond: [
{
$gt: [
{
"$indexOfArray": [
"$array",
1
]
},
0
]
},
{
"$slice": [
"$array",
0,
{
"$indexOfArray": [
"$array",
1
]
}
]
},
[]
]
},
{
"$slice": [
"$array",
{
"$add": [
{
"$indexOfArray": [
"$array",
1
]
},
1
]
},
{
"$size": "$array"
}
]
}
]
}
}
}
])
Mongo Playground
Query
$reduce the array starting with {"n-ar": [], "found": false}
the first time you find it you ignore it, and you set found=true
else you just $concat to add the member to the new-ar
*it can be generalized, like remove the first 4 occurences, if integer is used as found
*its pipeline update requires MongoDB >= 4.2
Playmongo
update({},
[{"$set":
{"ar":
{"$getField":
{"field": "n-ar",
"input":
{"$reduce":
{"input": "$ar",
"initialValue": {"n-ar": [], "found": false},
"in":
{"$cond":
[{"$and":
[{"$eq": ["$$this", 1]}, {"$eq": ["$$value.found", false]}]},
{"n-ar": "$$value.n-ar", "found": true},
{"n-ar": {"$concatArrays": ["$$value.n-ar", ["$$this"]]},
"found": "$$value.found"}]}}}}}}}])

MongoDB: How to count number of values in key

I'm very new to MongoDB and I need help figuring out how to perform aggregation on a key in MongoDB and use that result to return matches.
For example, if I have a collection called Fruits with the following documents:
{
"id": 1,
"name": "apple",
"type": [
"Granny smith",
"Fuji"
]
}, {
"id": 2,
"name": "grape",
"type": [
"green",
"black"
]
}, {
"id": 3,
"name": "orange",
"type": [
"navel"
]
}
How do I write a query that will return the names of the fruits with 2 types, ie apple and grape?
Thanks!
Demo - https://mongoplayground.net/p/ke3VJIErhvb
use $size to get records with 2 number of type
https://docs.mongodb.com/manual/reference/method/db.collection.find/#mongodb-method-db.collection.find
The $size operator matches any array with the number of elements specified by the argument. For example:
db.collection.find({
type: { "$size": 2 } // match document with type having size 2
},
{ name: 1 } // projection to get name and _id only
)
To get the length of the array you should use $size operator in $project pipeline stage
So the pipeline $project stage should look like this
{
"$project": {
"name": "$name",
type: {
"$size": "$type"
}
}
}
Here is an working example of the same ⇒ https://mongoplayground.net/p/BmS9BGhqsFg

mongodb - Get one array from two arrays in collection

In my mongodb collection, I have sometimes two, sometimes one and sometimes null arrays on a document. Now I'd like to get one array over the whole collection with the values of these arrays.
The document looks like this:
{
"title" : "myDocument",
"listOne" : [
"valueOne",
"valueTwo"
],
"listTwo" : [
"abc",
"qwer"
]
},
{
"title" : "myDocumentTwo",
"listTwo" : [
"321"
]
},
{
"title" : "myDocumentAlpha",
"listOne" : [
"alpha",
"beta"
]
},
{
"title" : "myDocumentbeta"
}
And I expect the following output:
"combinedList" : [
"valueOne",
"valueTwo",
"abc",
"qwer",
"321",
"alpha",
"beta"
]
It's like every possible value from these twos array out of every document in this collection.
You can do this using aggregate and $concatArrays
db.collection.aggregate([
{
$project: {
combinedList: {
$concatArrays: [{$ifNull: ["$listOne", []]}, {$ifNull: ["$listTwo", []]}]
}
}
},
{ $unwind: "$combinedList" },
{ $group: { _id: null, combinedList: { $addToSet: "$combinedList"}}},
{ $project: { _id: 0, combinedList: 1 }}
])

Find array in array data in MongoDB

I want find in this document groups:
"document": {
"groups": [
{
"id": "5ccd5f7f34f82b0e3315b2f6"
},
{
"id": "73b43unbfkfmdmddfdf84jjk"
}
]
}
are contains some of my query array groups ID:
[ '5ccd5f7f34f82b0e3315b2f6',
'5cdeded7ace07216f5873b5d',
'5cdee5d114edac2cc00bb333' ]
A simple find query suffices:
db.collection.find({ 'groups.id' : {$in : [ '5ccd5f7f34f82b0e3315b2f6',
'5cdeded7ace07216f5873b5d',
'5cdee5d114edac2cc00bb333' ] }})

Mongodb: find documents with array where all elements exist in query array, but document array can be smaller

I have a Collection in my database where most documents have an array-field. These arrays contain exactly 2 elements. Now i want to find all documents where all of those array elements are elements of my query array.
Example Documents:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] },
{ a:["1","4"] }
Query array:
["1","2","3"]
The query should find the first 3 documents, but not the last one, since there is no "4" in my query array.
Expected Result:
{ a:["1","2"] },
{ a:["2","3"] },
{ a:["1","3"] }
Looking forward to a helpful answer :).
Since the size is static, you can just check that both elements are in [1,2,3];
db.test.find(
{ $and: [ { "a.0": {$in: ["1","2","3"] } },
{ "a.1": {$in: ["1","2","3"] } } ] },
{ _id: 0, a: 1 }
)
>>> { "a" : [ "1", "2" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "3" ] }
EDIT: Doing it dynamically is a bit more hairy, I can't think of a way without the aggregation framework. Just count matches as 0 and non matches as 1, and finally remove all groups that have a sum != 0;
db.test.aggregate(
{ $unwind: "$a" },
{ $group: { _id: "$_id",
a: { $push: "$a" },
fail: { $sum: {$cond: { if: { $or: [ { $eq:["$a", "1"] },
{ $eq:["$a", "2"] },
{ $eq:["$a", "3"] }]
},
then: 0,
else: 1 } } } } },
{ $match: { fail: 0 } },
{ $project:{ _id: 0, a: 1 } }
)
>>> { "a" : [ "1", "3" ] }
>>> { "a" : [ "2", "3" ] }
>>> { "a" : [ "1", "2" ] }
I also think, that it's impossible without the aggregation framework (if elements count is dynamic).
But I found out more universal way of doing that:
db.tests.aggregate({
$redact: {
$cond: {
if: {$eq: [ {$setIsSubset: [ '$a', [ "1", "2", "3" ] ]}]},
then: '$$KEEP',
else: '$$PRUNE'
}
}
})
I believe the answer to your problem is to use
$in
(from the docs:)
Consider the following example:
db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
This query selects all documents in the inventory collection where the qty field value is either 5 or 15. Although you can express this query using the $or operator, choose the $in operator rather than the $or operator when performing equality checks on the same field.
You can also do more complex stuff using arrays. Checkout:
http://docs.mongodb.org/manual/reference/operator/query/in/

Resources