MongoDB Atlas search: sort by "searchScore" - mongodb

I run the following aggregation pipeline and it works well:
[
{
$search: {
text: {
query: text,
path: 'name',
fuzzy: {
maxEdits: 2,
prefixLength: 0,
maxExpansions: 256,
},
},
},
},
{
$limit: 10,
},
{
$project: {
_id: 1,
name: 1,
score: { $meta: 'searchScore' },
},
},
]
I can see that the score-field is present in my result and is correct. Now I want to sort the documents retrieved by the $meta-property searchScore. However, adding this step at the end of the pipeline:
{
$sort: { score: { $meta: 'searchScore' }, _id: 1 },
}
Yields the following error:
MongoError: $meta sort by 'searchScore' metadata is not supported
How do I achieve this?

You need to add a new field that contains the result of $meta before sorting (which you already do) and then sort on that field, so you new pipeline would be:
...
{
$project: {
_id: 1,
name: 1,
score: { $meta: 'searchScore' }, // you are already adding the field here.
},
},
{
$sort: {
score: -1, // use the new computed field here.
_id: 1
}
}

Related

Updating data with $set and $sum mongodb

I was trying to do update and sum up the column value.
await single_sku_db.collection("test").updateOne(
{ _id: ObjectId(id) },
{
$push: {
report: {
$each: [
{
name: name,
views,
},
],
$position: 0,
},
},
$set: {
report_status: "Completed",
total_views: { $sum: "$report.views"},
},
}
I cant sum the report.views like this, will get this error.
the dollar ($) prefixed field ‘’ is not valid for storage.
Is there anyway to do this without using aggregate?
One option is to replace the $set with $inc for this:
await single_sku_db.collection("test").updateOne(
{ _id: ObjectId(id) },
{
$push: {
report: {
$each: [
{
name: name,
views,
},
],
$position: 0,
},
},
$set: {report_status: "Completed"},
$inc: {total_views: views},
})
See how it works on the playground example

Mongoose aggregate using $addFields and $match by added field

I am trying to run the aggregate query in Mongo using $addFields and $match
.aggregate([
{
$addFields: {
level: { $sum: '$members.level' },
},
},
{
$match: {
level: { $gte: level }
},
},
{
$project: {
_id: 0,
logo: 1,
name: 1,
level: 1,
id: '$_id',
joinType: 1,
countryId: 1,
minimumJoinLevel: 1,
membersCount: { $size: '$members' },
},
},
])
The issue is that level is not an indexed field and has been calculated in the query
My question is: how I can run this query efficiently, avoid "COLLSCAN" and make it "IXSCAN" execution

MongoDB get size of unwinded array

I'm trying to return size of 'orders' and sum of 'item' values for each 'order' for each order from documents like the example document:
orders: [
{
order_id: 1,
items: [
{
item_id: 1,
value:100
},
{
item_id: 2,
value:200
}
]
},
{
order_id: 2,
items: [
{
item_id: 3,
value:300
},
{
item_id: 4,
value:400
}
]
}
]
I'm using following aggregation to return them, everything works fine except I can't get size of 'orders' array because after unwind, 'orders' array is turned into an object and I can't call $size on it since it is an object now.
db.users.aggregate([
{
$unwind: "$orders"
},
{
$project: {
_id: 0,
total_values: {
$reduce: {
input: "$orders.items",
initialValue: 0,
in: { $add: ["$$value", "$$this.value"] }
}
},
order_count: {$size: '$orders'}, //I get 'The argument to $size must be an array, but was of type: object' error
}
},
])
the result I expected is:
{order_count:2, total_values:1000} //For example document
{order_count:3, total_values:1500}
{order_count:5, total_values:2500}
I found a way to get the results that I wanted. Here is the code
db.users.aggregate([
{
$project: {
_id: 1, orders: 1, order_count: { $size: '$orders' }
}
},
{ $unwind: '$orders' },
{
$project: {
_id: '$_id', items: '$orders.items', order_count: '$order_count'
}
},
{ $unwind: '$items' },
{
$project: {
_id: '$_id', sum: { $sum: '$items.value' }, order_count: '$order_count'
}
},
{
$group: {
_id: { _id: '$_id', order_count: '$order_count' }, total_values: { $sum: '$sum' }
}
},
])
output:
{ _id: { _id: ObjectId("5dffc33002ef525620ef09f1"), order_count: 2 }, total_values: 1000 }
{ _id: { _id: ObjectId("5dffc33002ef525620ef09f2"), order_count: 3 }, total_values: 1500 }

MongoDB multiple levels embedded array query

I have a document like this:
{
_id: 1,
data: [
{
_id: 2,
rows: [
{
myFormat: [1,2,3,4]
},
{
myFormat: [1,1,1,1]
}
]
},
{
_id: 3,
rows: [
{
myFormat: [1,2,7,8]
},
{
myFormat: [1,1,1,1]
}
]
}
]
},
I want to get distinct myFormat values as a complete array.
For example: I need the result as: [1,2,3,4], [1,1,1,1], [1,2,7,8]
How can I write mongoDB query for this?
Thanks for the help.
Please try this, if every object in rows has only one field myFormat :
db.getCollection('yourCollection').distinct('data.rows')
Ref : mongoDB Distinct Values for a field
Or if you need it in an array & also objects in rows have multiple other fields, try this :
db.yourCollection.aggregate([{$project :{'data.rows.myFormat':1}},{ $unwind: '$data' }, { $unwind: '$data.rows' },
{ $group: { _id: '$data.rows.myFormat' } },
{ $group: { _id: '', distinctValues: { $push: '$_id' } } },
{ $project: { distinctValues: 1, _id: 0 } }])
Or else:
db.yourCollection.aggregate([{ $project: { values: '$data.rows.myFormat' } }, { $unwind: '$values' }, { $unwind: '$values' },
{ $group: { _id: '', distinctValues: { $addToSet: '$values' } } }, { $project: { distinctValues: 1, _id: 0 } }])
Above aggregation queries would get what you wanted, but those can be tedious on large datasets, try to run those and check if there is any slowness, if you're using for one-time then if needed you can consider using {allowDiskUse: true} & irrespective of one-time or not you need to check on whether to use preserveNullAndEmptyArrays:true or not.
Ref : allowDiskUse , $unwind preserveNullAndEmptyArrays

Mongo group aggregation with priority of record

I am trying to perform a MongoDB 3.6 aggregation and I can't figure out the right way.
The problem is following. After performing several aggregation steps I end up with result set like this:
[
{ _id: { month: 1, type: 'estimate' }, value: 50 },
{ _id: { month: 2, type: 'estimate' }, value: 40 },
{ _id: { month: 3, type: 'estimate' }, value: 35 },
{ _id: { month: 3, type: 'exact' }, value: 33.532 },
{ _id: { month: 4, type: 'estimate' }, value: 10 },
{ _id: { month: 4, type: 'exact' }, value: 11.244 },
]
It contains values grouped by month. Value for every month can be 'estimated' or 'exact'. Now I would like to reduce this result to achieve this:
[
{ _id: { month: 1 }, value: 50 },
{ _id: { month: 2 }, value: 40 },
{ _id: { month: 3 }, value: 33.532 },
{ _id: { month: 4 }, value: 11.244 },
]
Basically I want to use the value of type 'exact' whenever it's possible and only fallback to 'estimate' value in months where the 'exact' is not known.
Any help or tip will be greatly appreciated. I would like to perform that aggregation in the DB not on server.
You can simply $sort by type and then take use $first in next $group stage which will give you exact if exists and estimate otherwise. Try:
db.col.aggregate([
{
$sort: { "_id.type": -1 }
},
{
$group:{
_id: "$_id.month",
value: { $first: "$value" }
}
},
{
$sort: { _id: 1 }
}
])
Prints:
{ "_id" : 1, "value" : 50 }
{ "_id" : 2, "value" : 40 }
{ "_id" : 3, "value" : 33.532 }
{ "_id" : 4, "value" : 11.244 }
So sorting by type is considered as prioritizing here since we know that lexically exact will be before estimate. You can also be more explicit and add extra field called weight (evaluated using $cond) operator and then sort by that weight:
db.col.aggregate([
{
$addFields: {
weight: { $cond: [ { $eq: [ "$_id.type", "exact" ] }, 2, 1 ] }
}
},
{
$sort: { "weight": -1 }
},
{
$group:{
_id: "$_id.month",
value: { $first: "$value" }
}
},
{
$sort: { _id: 1 }
}
])