mongodb query filter documents by array value or size - mongodb

I have a document that's look like this if it hasn't got any items in the itemList field:
{
"_id":{
"$oid":"62e12a0b73a8c3469e635d93"
},
"listName":"name of list",
"alloweUidList":[
{
"uid":"prQUKkIxljVqbHlCKah7T1Rh7l22",
"role":"creator",
"boolId": 1,
}
],
"itemList":[
],
"crDate":"2022-07-27 14:05",
"modDate":"2022-07-27 14:05",
"boolId":1
}
and looks like this if i have some elements in the itemList field:
{
"_id":{
"$oid":"62e12a0b73a8c3469e635d93"
},
"listName":"Kuli Gábor listája nr: 1",
"alloweUidList":[
{
"uid":"prQUKkIxljVqbHlCKah7T1Rh7l22",
"role":"creator",
"boolId": 1,
}
],
"itemList":[
{
"itemDetail":{
"itemName":"item 1 name",
"price":459,
},
"crDate":"2022-07-27 14:13",
"checkFlag":0,
"boolId":1,
"volume":1,
"modDate":null
},
{
"itemDetail":{
"itemName":"item 2 name",
"price":169,
},
"crDate":"2022-07-27 14:15",
"checkFlag":0,
"boolId":0,
"volume":1,
"modDate":"2022-07-27 14:16"
}
],
"crDate":"2022-07-27 14:05",
"modDate":"2022-07-27 14:05",
"boolId":1
}
I would like to find documents that has at least one element with boolId: 1 in the itemList array or the itemList array is empty. This query works only if i have item in my array with boolId: 1 but not works if the array is empty:
db.shoppingList.find(
{
"itemList.boolId": 1,
"alloweUidList.uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"alloweUidList.boolId": 1,
"boolId": 1
},
{
listName: 1,
alloweUidList: 1,
crDate: 1,
modDate: 1,
boolId: 1,
itemList: {
$elemMatch: {
$or: [
{boolId: 1},
{itemList:{$exists:true,$eq:[]}}
]
},
},
}
)
Also tried: {$size : 0} thats not works either.
Update, I Forget. If I have elements with boolId:1 and also with boolId:0 in itemList i only want to return values that has boolId:1 So if the document looks like this:
{
"_id":{
"$oid":"62e1855473a8c3469e635d94"
},
"listName":"name of list",
"alloweUidList":[
{
"uid":"prQUKkIxljVqbHlCKah7T1Rh7l22",
"role":"creator",
"boolId":1,
}
],
"itemList":[
{
"itemDetail":{
"itemName":"item name 1",
"price":459,
},
"crDate":"2022-07-27 20:35",
"checkFlag":0,
"boolId":1,
"volume":1,
"modDate":null
},
{
"itemDetail":{
"itemName":"item name 2",
"price":549,
},
"crDate":"2022-07-27 20:35",
"checkFlag":0,
"boolId":0,
"volume":1,
"modDate":"2022-07-27 20:35"
}
],
"crDate":"2022-07-27 20:34",
"modDate":"2022-07-27 20:34",
"boolId":1
}
I would like to get this:
{
"_id":{
"$oid":"62e1855473a8c3469e635d94"
},
"listName":"name of list",
"alloweUidList":[
{
"uid":"prQUKkIxljVqbHlCKah7T1Rh7l22",
"role":"creator",
"boolId":1,
}
],
"itemList":[
{
"itemDetail":{
"itemName":"item name 1",
"price":459,
},
"crDate":"2022-07-27 20:35",
"checkFlag":0,
"boolId":1,
"volume":1,
"modDate":null
}
],
"crDate":"2022-07-27 20:34",
"modDate":"2022-07-27 20:34",
"boolId":1
}
And if the itemList array is empty i would return the document with empty itemList array
clarification: if the document has only boolId:0 entries, the query should return the document with empty itemList array.

You can do an $or in $expr to cater to the 2 criteria.
db.collection.aggregate([
{
$match: {
"alloweUidList.uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"alloweUidList.boolId": 1,
"boolId": 1
}
},
{
$match: {
$expr: {
$or: [
// itemList is empty array
{
$eq: [
"$itemList",
[]
]
},
// itemList has more than 1 boolId:1 elem
{
$gt: [
{
$size: {
"$filter": {
"input": "$itemList",
"as": "i",
"cond": {
$eq: [
"$$i.boolId",
1
]
}
}
}
},
0
]
}
]
}
}
},
{
"$addFields": {
"itemList": {
"$filter": {
"input": "$itemList",
"as": "i",
"cond": {
$eq: [
"$$i.boolId",
1
]
}
}
}
}
}
])
Here is the Mongo Playground for your reference.

Shouldn't do filtering for at least one element with boolId: 1 in the itemList array or the itemList array is empty in the projection.
This "alloweUidList.boolId": 1 search criteria will lead to no document is returned as the attached documents do not contain alloweUidList.boolId property.
Use $expr operator to use the aggregation operators.
db.shoppingList.find({
$expr: {
$and: [
{
$or: [
{
$eq: [
{
$ifNull: [
"$itemList",
[]
]
},
[]
]
},
{
$in: [
1,
"$itemList.boolId"
]
}
]
},
{
$in: [
"prQUKkIxljVqbHlCKah7T1Rh7l22",
"$alloweUidList.uid"
]
},
{
$in: [
1,
"$alloweUidList.boolId"
]
},
{
$eq: [
"$boolId",
1
]
}
]
}
},
{
listName: 1,
alloweUidList: 1,
crDate: 1,
modDate: 1,
boolId: 1,
itemList: 1
})
Sample Mongo Playground

Related

MongoDB aggregate query - How to make array of array list in to single array

I have a document like this:
this is my example data is attached below,
[
{
"_id": ObjectId("6218b836405919280c209f7e"),
"projectId": ObjectId("6218a31f405919280c209e18"),
"accountId": ObjectId("621888e852bd8836c04b8f82"),
"personalIdRoot": [
{
"_id": ObjectId("6221e7514195b43f24c9953f"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c99540"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"personalIdFill": [
{
"_id": ObjectId("6221e7514195b43f24c9953d"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c9953e"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"personalIdCap": [
{
"_id": ObjectId("6221e7514195b43f24c9953b"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c9953c"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"aps": ObjectId("6218bc18405919280c209f8e"),
}
]
i used this example data for my aggregate query.
my aggregate query is attached below:
please find below code:
db.getCollection('funds').aggregate([
{
$match: {
accountId: ObjectId("621888e852bd8836c04b8f82"),
projectId: ObjectId("6218a31f405919280c209e18"),
aps: {
$in: [
ObjectId("6218bc18405919280c209f8e")
]
}
}
},
{
$facet: {
"results": [
{
$group: {
_id: 0,
values: { "$addToSet": "$personalIdRoot.welderId" },
values: { "$addToSet": "$personalIdFill.welderId" },
values: { "$addToSet": "$personalIdCap.welderId" }
}
},
],
}
}
])
my result:
/* 1 */
{
"results" : [
{
"_id" : 0.0,
"values" : [
[
ObjectId("6218b48c405919280c209f6c")
],
[
ObjectId("6218b2e4405919280c209f68")
],
[
ObjectId("6218b48c405919280c209f6c"),
ObjectId("621ef1e40bd3a220f487cd96")
]
]
}
]
}
But i need my result in a single query:
/* 1 */
{
"results" : [
{
"_id" : 0.0,
"values" : [
ObjectId("6218b48c405919280c209f6c"),
ObjectId("621ef1e40bd3a220f487cd96")
]
}
]
}
I have a result of array of array collections.
but i need it in a single array. like above result
Thanks in advance.
It sounds like what you want is:
db.collection.aggregate([
{
$match: {
accountId: ObjectId("621888e852bd8836c04b8f82"),
projectId: ObjectId("6218a31f405919280c209e18"),
aps: {
$in: [
ObjectId("6218bc18405919280c209f8e")
]
}
}
},
{
$project: {
values: {
$setUnion: [
"$personalIdCap.personalId",
"$personalIdFill.personalId",
"$personalIdRoot.personalId"
]
}
}
}
])
See how it works on the playground example

How to remove field conditionally mongoodb

I have a collection and its documents look like:
{
_id: ObjectId('111111111122222222223333'),
my_array: [
{
id: ObjectId('777777777788888888889999')
name: 'foo'
},
{
id: ObjectId('77777777778888888888555')
name: 'foo2'
}
//...
]
//more attributes
}
However, some documents have my_array: [{}] (with one element which is an empty array).
How can I add conditionally a projection or remove it?
I have to add it to a mongo pipeline at the end of the query, and I want to get my_array only when it has at least one element which is not an empty object. If there's an empty object remove it.
I tried with $cond and $eq in a projection stage but it is not supported. Any suggestion to solve this?
Suppose you have documents like this with my_array field:
{ "my_array" : [ ] }
{ "my_array" : [ { "a" : 1 } ] } // #(1)
{ "my_array" : null }
{ "some_fld" : "some value" }
{ "my_array" : [ { } ] }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] } // #(2)
And, the following aggregation will filter and the result will have the two documents (1) and (2):
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
}
}
])
This also works with a find method:
db.collection.find({
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
})
To remove the my_array field, from a document when its empty, then you try this aggregation:
db.collection.aggregate([
{
$addFields: {
my_array: {
$cond: [
{$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]},
"$my_array",
"$$REMOVE"
]
}
}
}
])
The result:
{ }
{ "my_array" : [ { "a" : 1 } ] }
{ }
{ "a" : 1 }
{ }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] }
You can't do that in a query, however in an aggregations you can add $filter to you pipeline, like so:
db.collection.aggregate([
{
$project: {
my_array: {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
}
])
Mongo Playground
However unless this is "correct" behavior I suggest you clean up your database, it's much simpler to maintain "proper" structure than to update all your queries everywhere.
You can use this update to remove these objects:
db.collection.update({
"myarray": {}
},
[
{
"$set": {
"my_array": {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
},
],
{
"multi": false,
"upsert": false
})
Mongo Playground

Display the conditionnal size of an array with the others fields of a mongodb document

I have a collection of fridges and I would like to have some fields from each fridge matching a condition plus the 'conditionnal size' of the items in this fridge.
This is an example of my DB :
db={
"fridges": [
{
_id: 1,
items: [
{
itemId: 1,
name:"beer"
},
{
itemId: 2,
name: "chicken"
}
],
brand:"Bosch",
size:195,
cooler:true,
color:"grey"
},
{
_id: 2,
items: [
{
itemId: 1,
name:"beer"
},
{
itemId: 2,
name: "chicken"
},
{
itemId: 3,
name: "lettuce"
}
],
brand:"Electrolux",
size:200,
cooler:true,
color:"white"
},
]
}
I want to get fridges with these mutuals conditions ('and' condition):
brand is $in ["Bosch","Samsung"]
color is $in ["grey","white"]
In addition :
The number of items with a name $in ["beer","lettuce"]
And finally :
Removing some fields like the size and items of the result.
In our example, the excepted output would be :
{
_id:1
itemsNumber:1,
brand:"Bosch",
cooler:true,
color:"grey"
}
Explanations :
We removed the field items and size, itemsNumber counts the number of beers and lettuce from items array. And we only keep the first fridge its brand is Bosch and it's grey.
This what I have so far :
db.fridges.aggregate([
{
"$match": {
$and: [
{
"brand": {
$in: [
"Bosch",
"Samsung"
]
}
},
{
"color": {
$in: [
"grey",
"white"
]
}
}
]
}
},
{
"$project": {
"itemsNumber": {
$size: "$items" // This is not good
},
brand: 1,
cooler: 1,
color: 1
}
}
])
Which returns me :
[
{
"_id": 1,
"brand": "Bosch",
"color": "grey",
"cooler": true,
"itemsNumber": 2
}
]
Counting the items matching with either beer or lettuce is my main problem.
This is an executable example.
Thanks in advance !
I found out how to make it work. Thank you #joe for suggesting to use filter this was indeed the solution.
Here is the complete query :
db.fridges.aggregate([
{
$match: {
$and: [
{
"brand": {
$in: [
"Bosch",
"Samsung"
]
}
},
{
"color": {
$in: [
"grey",
"white"
]
}
}
]
}
},
{
$project: {
"itemsNumber": {
"$filter": {
"input": "$items",
"as": "item",
"cond": {
$in: [
"$$item.name",
[
"beer",
"lettuce"
]
]
}
}
},
brand: 1,
cooler: 1,
color: 1
}
}
])
Runnable example.

in mongo how to match a field that is not list of empty elements?

I have field x which can be [[], [], ...] or ["", "", ....] I want to filter them out and keeps the document at least have 1 non-empty list or 1 non-empty string. for example [[], [1,2], [], ...]
This is an aggregation query which filters out collection documents with the array field x, having elements with all empty strings or all empty arrays.
db.collection.aggregate([
{
$addFields: {
filtered: {
$filter: {
input: "$x",
as: "e",
cond: {
$or: [
{ $and: [
{ $eq: [ { "$type": "$$e" }, "array" ] },
{ $gt: [ { $size: "$$e" }, 0 ] }
] },
{ $and: [
{ $eq: [ { "$type": "$$e" }, "string" ] },
{ $gt: [ { $strLenCP: "$$e" }, 0 ] }
] }
]
}
}
}
}
},
{
$match: {
$expr: { $gt: [ { $size: "$filtered" }, 0 ] }
}
},
{
$project: { filtered: 0 }
}
])
Reference: Various aggregation operators ($size, $type, $strLenCP, etc.) used.

Mongodb array $push and $pull

I was looking to pull some value from array and simultaneously trying to update it.
userSchema.statics.experience = function (id,xper,delet,callback) {
var update = {
$pull:{
'profile.experience' : delet
},
$push: {
'profile.experience': xper
}
};
this.findByIdAndUpdate(id,update,{ 'new': true},function(err,doc) {
if (err) {
callback(err);
} else if(doc){
callback(null,doc);
}
});
};
i was getting error like:
MongoError: exception: Cannot update 'profile.experience' and 'profile.experience' at the same time
I found this explanation:
The issue is that MongoDB doesn’t allow multiple operations on the
same property in the same update call. This means that the two
operations must happen in two individually atomic operations.
And you can read that posts:
Pull and addtoset at the same time with mongo
multiple mongo update operator in a single statement?
In case you need replace one array value to another, you can use arrayFilters for update.
(at least, present in mongo 4.2.1).
db.your_collection.update(
{ "_id": ObjectId("your_24_byte_length_id") },
{ "$set": { "profile.experience.$[elem]": "new_value" } },
{ "arrayFilters": [ { "elem": { "$eq": "old_value" } } ], "multi": true }
)
This will replace all "old_value" array elements with "new_value".
Starting from MongoDB 4.2
You can try to update the array using an aggregation pipeline.
this.updateOne(
{ _id: id },
[
{
$set: {
"profile.experience": {
$concatArrays: [
{
$filter: {
input: "$profile.experience",
cond: { $ne: ["$$this", delet] },
},
},
[xper],
],
},
},
},
]
);
Following, a mongoplayground doing the work:
https://mongoplayground.net/p/m1C1LnHc0Ge
OBS: With mongo regular update query it is not possible.
Since Mongo 4.2 findAndModify supports aggregation pipeline which will allow atomically moving elements between arrays within the same document. findAndModify also allows you to return the modified document (necessary to see which array elements were actually moved around).
The following includes examples of:
moving all elements from one array onto the end of a different array
"pop" one element of an array and "push" it to another array
To run the examples, you will need the following data:
db.test.insertMany( [
{
"_id": ObjectId("6d792d6a756963792d696441"),
"A": [ "8", "9" ],
"B": [ "7" ]
},
{
"_id": ObjectId("6d792d6a756963792d696442"),
"A": [ "1", "2", "3", "4" ],
"B": [ ]
}
]);
Example 1 - Empty array A by moving it into array B:
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696441") },
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, "$A" ] } } },
{ $set: { "A": [] } }
],
new: true
});
Resulting in:
{
"_id": {
"$oid": "6d792d6a756963792d696441"
},
"A": [],
"B": [
"7",
"8",
"9"
]
}
Example 2.a - Pop element from array A and push it onto array B
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"A": {$exists: true, $type: "array", $ne: [] }},
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ { $first: "$A" } ] ] } } },
{ $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
],
new: true
});
Resulting in:
{
"_id": {
"$oid": "6d792d6a756963792d696442"
},
"A": [
"2",
"3",
"4"
],
"B": [
"1"
]
}
Example 2.b - Pop element from array A and push it onto array B but in two steps with a temporary placeholder:
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"temp": { $exists: false } },
update: [
{ $set: { "temp": { $first: "$A" } } },
{ $set: { "A": { $slice: ["$A", 1, {$max: [{$subtract: [{ $size: "$A"}, 1]}, 1]}] } }}
],
new: true
});
// do what you need to do with "temp"
db.test.findAndModify({
query: { _id: ObjectId("6d792d6a756963792d696442"),
"temp": { $exists: true } },
update: [
{ $set: { "B": { $concatArrays: [ { $ifNull: [ "$B", [] ] }, [ "$temp" ] ] } } },
{ $unset: "temp" }
],
new: true
});