MongoDB - Total number of objects in array vs field - arrays

I need to get total count of objects in the "photo" array field, and compare it to the value in "mls_media_count" field, so that I can detect when the values do not match.
open_houses:null
open_houses_lock:false
photos:Array
0:Object
1:Object
2:Object
3:Object
4:Object
5:Object
6:Object
7:Object
8:Object
9:Object
mls_media_count: 8
I have tried using $unwind, $project, $size, but haven't had luck getting it to work.
Thanks in advance!

Query
$size can give the size of the array, and we can compare it with the media_count
first query keeps the documents where size is not correct
second query keeps the documents where size is correct
Playmongo
aggregate(
[{"$match":
{"$expr": {"$ne": [{"$size": "$photos"}, "$mls_media_count"]}}}])
Playmongo
aggregate(
[{"$match":
{"$expr": {"$eq": [{"$size": "$photos"}, "$mls_media_count"]}}}])

Thank you for response! This got me on right track and i ended up writing it like this:
[{
$match: {version: '5'
}
}, {
$project: {
item: -1,
Photos: {
$cond: {
'if': {
$isArray: '$photos'
},
then: {
$size: '$photos'
},
'else': 'NA'
}
},
mls_number: 1,
mls_media_count: 1,
'system.kw_photo_count': 1
}
}, {
$match: {
$expr: {
$ne: [
'$Photos',
'$mls_media_count'
]
}
}
}, {
$count: 'list_id'
}]

Related

Mongo filter documents where a given value is same as minimum value in array

A collection called bookstore is as below (sample 2 documents):
[{
"bookname": "hi",
"orders" : [
{"rate" : 1},
{"rate" : 2}
]
},
{
"bookname":"hello",
"orders" : [
{"rate" : 3},
{"rate" : 2},
{"rate" : 5}
]
}]
I want to return documents where the minimum rate value from orders array is equal to value number 2.
Output: Entire document with all fields where bookname:hello as the minimum rate value in orders array is 2 and not the document with bookname:hi as the minimum rate is 1 there.
How to I achieve this with mongo db query?
I tried this:
db.collection.aggregate([
{
$unwind: "$orders"
},
{
$group: {
_id: "$_id",
minRateDoc: {
$min: "$orders.rate"
}
}
},
{
"$match": {
"minRateDoc": 2
}
}
])
It returns output as:
[{
_id: ObjectId of 2nd document,
"minRateDoc": 2
}
]
But to return all fields I don't want to use projection as in reality there are many fields I want to return. Also is there other way to return documents where minimum rate value is number 2.
You can directly apply $min to the array orders.rate and compare to 2 to get the documents you want.
db.collection.find({
$expr: {
$eq: [
2,
{
$min: "$orders.rate"
}
]
}
})
Mongo Playground
You could query for documents which contain an order whose rate is 2, and which do NOT contain an order whose rate is less than 2. This can be achieved as follows:
db.collection.find(
{
$and: [
{ "orders.rate": 2 },
{ "orders.rate": { $not: { $lt: 2 } } }
]
}
)

Filter by object array element and return only the value of a field in the array object MongoDB

I'm using MongoDB 5.0.10 and the aggregation pipeline. I am trying to find out if there is a way to filter an array of objects AND return only the value of a field within the returned array object. I can easily filter the array based on a field value within the array. I currently can achieve what I want in 2 stages. I'm just trying to see if I can return a value within the same stage. I'm using a projection and $arrayElemAt to filter the array. Then I perform another projection to get the value I want.
Sample document:
{
"_id": 1,
"lastOpenedList": [
{
"_id": 4,
"dateTime": {
"$date": {
"$numberLong": "1659970718606"
}
}
}
]
}
First stage:
$project: {
'lastOpened':{$arrayElemAt: [
{$filter: {
input: "$lastOpenedList",
cond: {
$eq:[
"$$this._id", '4'
]
}
}},0
]}
}
Second stage:
$project: {
'lastOpened': '$lastOpened.dateTime'
}
Result:
{
"_id": 1,
"lastOpened":
{
"$date": {
"$numberLong": "1659970718606"
}
}
}
Query
filter can't change the member only keep it or not
we have 2 options $map + $filter => map keep those we want, and null in others, and then with $filter remove those nulls
other option is $reduce, and keep only those we want, in general $reduce is the most powerful
here $reduce, and if match keep it
Playmongo
aggregate(
[{"$project":
{"_id": 0,
"lastOpened":
{"$reduce":
{"input": "$lastOpenedList",
"initialValue": null,
"in":
{"$cond":
[{"$eq": ["$$this.dateTime", 2]}, "$$this.dateTime",
"$$value"]}}}}}])

MongoDB Aggregation: How to return only the values that don't exist in all documents

Lets say I have an array ['123', '456', '789']
I want to Aggregate and look through every document with the field books and only return the values that are NOT in any documents. For example if '123' is in a document, and '456' is, but '789' is not, it would return an array with ['789'] as it's not included in any books fields in any document.
.aggregate( [
{
$match: {
books: {
$in: ['123', '456', '789']
}
}
},
I don't want the documents returned, but just the actual values that are not in any documents.
Here's one way to scan the entire collection to look for missing book values.
db.collection.aggregate([
{ // "explode" books array to docs with individual book values
"$unwind": "$books"
},
{ // scan entire collection creating set of book values
"$group": {
"_id": null,
"allBooksSet": {
"$addToSet": "$books" // <-- generate set of book values
}
}
},
{
"$project": {
"_id": 0, // don't need this anymore
"missing": { // use $setDifference to find missing values
"$setDifference": [
[ "123", "456", "789" ], // <-- your values go here
"$allBooksSet" // <-- the entire collection's set of book values
]
}
}
}
])
Example output:
[
{
"missing": [ "789" ]
}
]
Try it on mongoplayground.net.
Based on #rickhg12hs's answer, there is another variation replacing $unwind with $reduce, which considered less costly. Two out of Three steps are the same:
db.collection.aggregate([
{
$group: {
_id: null,
allBooks: {$push: "$books"}
}
},
{
$project: {
_id: 0,
allBooksSet: {
$reduce: {
input: "$allBooks",
initialValue: [],
in: {$setUnion: ["$$value", "$$this"]}
}
}
}
},
{
$project: {
missing: {
$setDifference: [["123","456", "789"], "$allBooksSet"]
}
}
}
])
Try it on mongoplayground.net.

Mongo DB find value in array of multiple nested arrays

I need to check if an ObjectId exists in a non nested array and in multiple nested arrays, I've managed to get very close using the aggregation framework, but got stuck in the very last step.
My documents have this structure:
{
"_id" : ObjectId("605ce5f063b1c2eb384c2b7f"),
"name" : "Test",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("6058e94c3994d04d2863962e")
],
"variations" : [
{
"varName" : "Var1",
"attrs" : [
ObjectId("6058e94c3994d04d28639616"),
ObjectId("6058e94c3994d04d28639627"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
{
"varName" : "Var2",
"attrs" : [
ObjectId("60591791d4d41d0a6817d22a"),
ObjectId("60591791d4d41d0a6817d255"),
ObjectId("6058e94c3994d04d28639622"),
ObjectId("60591791d4d41d0a6817d23f")
],
},
],
"storeId" : "9acdq9zgke49pw85"
}
Let´s say I need to check if this if this _id exists "6058e94c3994d04d28639616" in all arrays named attrs.
My aggregation query goes like this:
db.product.aggregate([
{
$match: {
storeId,
},
},
{
$project: {
_id: 0,
attrs: 1,
'variations.attrs': 1,
},
},
{
$project: {
attrs: 1,
vars: '$variations.attrs',
},
},
{
$unwind: '$vars',
},
{
$project: {
attr: {
$concatArrays: ['$vars', '$attrs'],
},
},
},
]);
which results in this:
[
{
attr: [
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
6058e94c3994d04d2863962e,
6058e94c3994d04d28639616,
6058e94c3994d04d28639627,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f
]
},
{
attr: [
60591791d4d41d0a6817d22a,
60591791d4d41d0a6817d255,
6058e94c3994d04d28639622,
60591791d4d41d0a6817d23f,
6058e94c3994d04d28639624,
6058e94c3994d04d28639627,
6058e94c3994d04d28639628,
6058e94c3994d04d2863963e
]
}
]
Assuming I have two products in my DB, I get this result. Each element in the outermost array is a different product.
The last bit, which is checking for this key "6058e94c3994d04d28639616", I could not find a way to do it with $group, since I dont have keys to group on.
Or with $match, adding this to the end of the aggregation:
{
$match: {
attr: "6058e94c3994d04d28639616",
},
},
But that results in an empty array. I know that $match does not query arrays like this, but could not find a way to do it with $in as well.
Is this too complicated of a Schema? I cannot have the original data embedded, since it is mutable and I would not be happy to change all products if something changed.
Will this be very expensive if I had like 10000 products?
Thanks in advance
You are trying to compare string 6058e94c3994d04d28639616 with ObjectId. Convert the string to ObjectId using $toObjectId operator when perform $match operation like this:
{
$match: {
$expr: {
$in: [{ $toObjectId: "6058e94c3994d04d28639616" }, "$attr"]
}
}
}

How to $match an item from the list

I am trying to display the items, that have specific name brand.
This is how that field looks like:
"brand" : [
[
"Samsung",
"Iphone",
"Huawei"
]
]
Tried that query, but I get 0 results:
db.collection.aggregate([{$match: { brand: "Samsung" }}])
Any ideas, what's wrong?
Your data model is an array of arrays so you have to deal with two dimensions. You can use $map along with $in first and then use $anyElementTrue to see if there's any sub-array matching your condition:
db.collection.aggregate([
{
$match: {
$expr: {
$anyElementTrue: {
$map: {
input: "$brand",
in: { $in: [ "Samsung", "$$this" ] }
}
}
}
}
},
{
$project: {
_id: 1,
color: 1
}
}
]);
Mongo Playground
EDIT: use $project to display only certain fields

Resources