I have the following document:
{ arr : [1,2,3] }
And I have to compare it with the following:
a : [1,2]
b : [2,3,1]
c : [2,5,3,1]
I need to make a query that return arr only when it match all the elements of the query array.
In the example, it would be "b" and "c"
I have tried with $all like the following:
find(arr:{$all:a}) (the same for b and c)
but this does not work because it match "a" too. :(
Try this:
find( {$or:[
{ $and: [ { arr: 1 }, { arr: 2 }, {arr:{$size:2}} ] },
{ $and: [ { arr: 1 }, { arr: 2 }, { arr: 3 }, {arr:{$size:3}} ] },
{ $and: [ { arr: 1 }, { arr: 2 }, { arr: 3 }, { arr: 5 }, {arr:{$size:4}} ] }
]})
or maybe with the $all operator (i haven't tryied that before but should be ok):
find( {$or:[
{ $and: [ { arr: { $all: a } }, {arr:{$size: a.length }} ] },
{ $and: [ { arr: { $all: b } }, {arr:{$size: b.length }} ] },
{ $and: [ { arr: { $all: c } }, {arr:{$size: c.length }} ] }
]})
You can use the aggregation framework and its set operations to select arrays which are subsets of the query array:
> db.foo.insert({arr:[1,2,3]})
> db.foo.insert({arr:[3,2,1]})
> db.foo.insert({arr:[3,2,1,4]})
and then
db.foo.aggregate([
{ $project: { arr:'$arr', isIn: { $setIsSubset: ['$arr', [2,5,3,1]] }}},
{ $match: { isIn: true }}
])
returns
{ "_id" : ObjectId("5345beb536cdafd3eb4a6b16"), "arr" : [ 1, 2, 3 ], "isIn" : true }
{ "_id" : ObjectId("5345bec036cdafd3eb4a6b17"), "arr" : [ 3, 2, 1 ], "isIn" : true }
Note that this requires version 2.6+
Related
I am having a problem with array query in mongoDB
I have collection with documents format like so:
{
'data':
[
{ 'itv': [0, 1], 'name': 'a' },
{ 'itv': [6, 8], 'name': 'b' },
...
]
}
I want to query documents that data have at least one element that matches the condition itv[1]-itv[0] > n
You can calculate this either in the aggregation pipeline or using $expr with the query language, like so:
const n = 1;
db.collection.find({
$expr: {
$gt: [
{
$size: {
$filter: {
input: "$data",
cond: {
$gt: [
{
$subtract: [
{
"$arrayElemAt": [
"$$this.itv",
1
]
},
{
"$arrayElemAt": [
"$$this.itv",
0
]
}
]
},
n
]
}
}
}
},
0
]
}
})
Mongo Playground
My personal recommendation will be to pre-calculate the difference, this means the new schema will look a little something like:
{
'data':
[
{ 'itv': [0, 1], 'diff': 1, 'name': 'a' },
{ 'itv': [6, 8], 'diff': 2, 'name': 'b' },
...
]
}
Now you can query this with a simple find query:
const n = 1
db.collection.find({ 'data.diff': { '$gt': n }})
To retroactively update your collection you can use this update:
db.collection.updateMany({},
[
{
$set: {
data: {
$map: {
input: "$data",
in: {
$mergeObjects: [
"$$this",
{
diff: {
$subtract: [
{
$arrayElemAt: [
"$$this.itv",
1
]
},
{
$arrayElemAt: [
"$$this.itv",
0
]
}
]
}
}
]
}
}
}
}
}
])
Update Mongo Playground
I have a USER table with documents:
{
_id: 1,
name: 'funny-guy43',
image: '../../../img1.jpg',
friends: [2, 3]
},
{
_id: 2,
name: 'SurfinGirl3',
image: '../../../img2.jpg',
friends: []
},
{
_id: 3,
name: 'FooBarMan',
image: '../../../img3.jpg',
friends: [2]
}
friends is an array of USER _ids. (1) I want to get user by _id, (2) look at his friends and (3) query the USER table with the friend ids to return all friends.
for example, find user 1, query the table based on his friends 2 and 3, and return 2 and 3.
Can I do that in one transaction? Or do I query the table to get user array of friends, then query the table again with array of friends ids.
I'm using .Net Core if that matters.
I am very open to alternative approaches as well.
It is, in fact, possible to do this in one transaction. Or, to be more exact, in one aggregation.
I would first split the users into 2 different subsets, one called searched_user and the other other_users, where searched_user will have only the user we are searching for and other_users will have everyone else. We can do that using $facet. Here is the idea:
{
"$facet": {
"searched_user": [
{
$match: {
_id: 1
}
}
],
"other_users": [
{
$match: {
_id: {
$ne: 1
}
}
}
]
}
}
Once they are separated like this, we can search the other_users subset using the friend ids from the searched_user. So here is the full aggregation:
db.collection.aggregate([
{
"$facet": {
"searched_user": [
{
$match: {
_id: 1
}
}
],
"other_users": [
{
$match: {
_id: {
$ne: 1
}
}
}
]
}
},
{
"$unwind": "$searched_user"
},
{
$project: {
user_friends: {
$filter: {
input: "$other_users",
as: "other_users",
cond: {
$in: [
"$$other_users._id",
"$searched_user.friends"
]
}
}
}
}
}
])
Here we are looking for user 1 and the result will be user 1's friends.
[
{
"user_friends": [
{
"_id": 2,
"friends": [],
"image": "../../../img2.jpg",
"name": "SurfinGirl3"
},
{
"_id": 3,
"friends": [
2
],
"image": "../../../img3.jpg",
"name": "FooBarMan"
}
]
}
]
Playground: https://mongoplayground.net/p/-8pNnQXg8r6
You can achieve this by using lookup in aggregation, Tried it with MongoDB version v4.2.11.
db.users.aggregate([
{
'$match': {
'_id': 1,
}
},
{
'$lookup': {
'from' : 'users',
'let' : {
'friendIds': '$friends',
},
'pipeline': [
{
'$match':{
'$expr': {'$in': [ '$_id', '$$friendIds']}
}
}
],
'as': 'friendsArr'
}
}
])
Result:
[
{
"_id" : 1,
"name" : "funny-guy43",
"image" : "../../../img1.jpg",
"friends" : [
2,
3
],
"friendsArr" : [
{
"_id" : 2,
"name" : "SurfinGirl3",
"image" : "../../../img2.jpg",
"friends" : [ ]
},
{
"_id" : 3,
"name" : "FooBarMan",
"image" : "../../../img3.jpg",
"friends" : [
2
]
}
]
}
]
I'm trying to find specific element of array in my MongoDB document, but with all my tries query returns all array. How can I get the right object?
My collection's document looks like:
{
"Achievements": [
{
"AchievementID": 1,
"AchievementEventID": 0
},
{
"AchievementID": 2,
"AchievementEventID": 1
}
],
"Buildings": [
{
"BuildingID": 1,
"BuildingType": "type1"
},
{
"BuildingID": 2,
"BuildingType": "type1"
},
]
}
I tried to get only one element of my Achievements array:
db.data.find({'Achievements.AchievementEventID': 0})
I expected to get only element with AchievementEventID equal to 0:
{
"AchievementID": 1,
"AchievementEventID": 0
}
But I got whole Achievements array.
How I could get only specific element?
You can use the Aggregation query like in the following example. The sample achive collection has 3 documents:
{
"_id" : 1,
"Achievements" : [
{
"AchievementID" : 1,
"AchievementEventID" : 0
},
{
"AchievementID" : 2,
"AchievementEventID" : 1
}
],
"Buildings" : [
{
"BuildingID" : 1,
"BuildingType" : "type1"
},
{
"BuildingID" : 2,
"BuildingType" : "type1"
}
]
}
{
"_id" : 2,
"Achievements" : [
{
"AchievementID" : 2,
"AchievementEventID" : 2
}
],
"Buildings" : [
{
"BuildingID" : 2,
"BuildingType" : "type2"
}
]
}
{
"_id" : 3,
"Achievements" : [
{
"AchievementID" : 31,
"AchievementEventID" : 1
}
],
"Buildings" : [
{
"BuildingID" : 3,
"BuildingType" : "type3"
}
]
}
The Query:
db.achive.aggregate( [
{ $project: { Buildings: 0} },
{ $unwind: "$Achievements" },
{ $match: { "Achievements.AchievementEventID": { $eq: 1 } } }
])
=>
{ "_id" : 1, "Achievements" : { "AchievementID" : 2, "AchievementEventID" : 1 } }
{ "_id" : 3, "Achievements" : { "AchievementID" : 31, "AchievementEventID" : 1 } }
I added the _id field to identify the documents selected.
Updated Query:
db.achive.aggregate( [
{ $unwind: "$Achievements" },
{ $match: { "Achievements.AchievementEventID": { $eq: 1 } } },
{ $project: { AchievementID: "$Achievements.AchievementID", _id: 0} },
])
=>
{ "AchievementID" : 2 }
{ "AchievementID" : 31 }
You can use $elemMatch for that
db.data.find(
{ 'Achievements.AchievementEventID': 0},{Buildings:0, Achievements: {$elemMatch: {AchievementEventID: 0}}}
)
If the above solution doesn't for you then try the below one:
db.data.find({"Achievements.AchievementEventID": 0}, {Buildings: 0, 'Achievements.$': 1});
You can use aggregate instead of a find like this
db.data.aggregate(
[{'$unwind':'$Achievements'},{$match:{
'Achievements.AchievementEventID':0
}},{$project:{
'AchievementID':'$Achievements.AchievementID',
'AchievementEventID':'$Achievements.AchievementEventID'
}}]
);
I have a collection with array elements containing A,B or C values. I have to calculate a weight of each element value.
The logic of this weight is sample :
We give 1.0 as weight of the last element (dfferente to C), and others 0
If the last element of the array is C, we give it to the previous last element (different to C).
if there is 1 element in the array (A,B or C) we give it 1 as a weight.
This is how my collection looking like :
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
"myArray" : [
{
"value" : "C"
},
{
"value" : "A
},
{
"value" : "C"
},
{
"value" : "B"
},
{
"value" : "A"
},
{
"value" : "A"
}
]
}
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
"myArray" : [
{
"value" : "A"
},
{
"value" : "C"
},
{
"value" : "B"
},
{
"value" : "C"
},
{
"value" : "C"
}
]
}
I did some aggregation to make it happen but I can here just to affect 1 to the last (different to C)
and (if the last is C I give 1 to the last one -1) so I have to fixe the case of having C in the last and last-1 and last -2 .. last-n
so I have to fix it to affect 1 to the last one different to C
db.col.aggregate([{'$addFields':{
'myArray_temp':{
'$switch':{
'branches':[{
'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$eq':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
'then':{'$concatArrays':[
{'$map':{
'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},2]}]},
'as':'val',
'in':{'value':'$$val.value','weight':0 }
}},
[{'value':{'$arrayElemAt':['$myArray.value',-2]},'weight':1}],
[{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':0}]
]}
},
{
'case':{'$eq':[{'$size':'$myArray'},1]},
'then':{'$concatArrays':[
[{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
]}
},
{
'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$ne':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
'then':{'$concatArrays':[
{'$map':{
'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},1]}]},
'as':'val',
'in':{'value':'$$val.value','weight':0 }
}},
[{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':1}]
]}
}
],
'default':{'$concatArrays':[
[{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
]}
}
}
}}])
The results should be :
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
"myArray" : [
{
"value" : "C",
"weight": 0
},
{
"value" : "A" ,
"weight": 0
},
{
"value" : "C",
"weight": 0
},
{
"value" : "B" ,
"weight": 0
},
{
"value" : "A",
"weight": 0
},
{
"value" : "A",
"weight": 1
}
]
}
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
"total" : 4.5,
"myArray" : [
{
"value" : "A",
"weight": 0
},
{
"value" : "C",
"weight": 0
},
{
"value" : "B" ,
"weight": 1 // here we give 1 to the last differente to C
},
{
"value" : "C" ,
"weight": 0 // my code affect 1 here cause it find C in the last and affect to the last-1 element.
},
{
"value" : "C" ,
"weight": 0
}
]
}
We have to skip all the last (C) elements and give the 1 weight to the last not-C element.
Thank you in advance!
Longest aggregation in my career but seems to be working:
db.collection.aggregate([
{
$addFields : {
size: { $size: "$myArray" },
reversed: {
$reverseArray: "$myArray"
}
}
},
{
$addFields: {
otherThanC: {
$filter: {
input: "$reversed",
as: "item",
cond: { $ne: [ "$$item.value", "C" ] }
}
}
}
},
{
$addFields: {
firstOtherThanCIndex : {
$indexOfArray: [ "$reversed", { $arrayElemAt: [ "$otherThanC", 0 ] } ]
}
}
},
{
$unwind: {
path: "$reversed",
includeArrayIndex: "arrayIndex"
}
},
{
$addFields: {
weight: {
$switch: {
branches: [
{ case: { $eq: [ "$size", 1 ] }, then: 1 },
{ case: { $and: [ { $eq: [ "$arrayIndex", 0 ] }, { $eq: [ { $size: "$otherThanC" }, 0 ] } ] } , then: 1},
{ case: { $eq: [ "$arrayIndex", "$firstOtherThanCIndex" ] }, then: 1 }
],
default: 0
}
}
}
},
{
$group: {
_id: "$_id",
myArrayReversed: {
$push: {
value: "$reversed.value",
weight: "$weight"
}
}
}
},
{
$project: {
_id: 1,
myArray: { $reverseArray: "$myArrayReversed" }
}
}
])
Brief description of each pipeline stage:
We need to add two extra fields: $size of and array and second array with reversed items
Second and third steps are to find first (last) item not equal to C using $filter and $arrayElemAt which returns first matching index
Then we can $unwind our reversed array using special syntax which adds index of array when unwinding
That is the moment when we can calculate weight using $switch - simply setting 1 if array has one element or indexes are matching and zero otherwise
Then we just need to reshape the data: grouping back by _id and reversing the array
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/