Mongo Filter Similar Documents - mongodb

Let's say I have this collection:
Cards:
[
{orientation: "portrait", size: "45", columns: 2, type: 1, _id: "c5ea1968-6ab5-4b05-80a9-db9dabe29dde"},
{orientation: "landscape", size: "45", columns: 3, type: 1, _id: "37186b8e-4033-46c6-8b2e-82ee45f96904"},
{orientation: "portrait", size: "45", columns: 2, type: 1, _id: "8f49a3ff-2859-4027-b644-88ac0949808d"},
{orientation: "portrait", size: "45", columns: 2, type: 2, _id: "862717f4-2ef7-4839-947a-02771338c38c"},
{orientation: "portrait", size: "45", columns: 2, type: 3, _id: "e1a93b10-dfcb-42a2-955a-ee14d539624f"}
]
And I need a result like this:
CardList:
[
{ type: 1,
list: [
{orientation: "landscape", size: "45", columns: 3, type: 1, _id: "1_landscape_2"},
{orientation: "portrait", size: "45", columns: 2, type: 1, _id: "1_portrait_2"}
]
},
{ type: 2,
list: [
{orientation: "portrait", size: "45", columns: 2, type: 2, _id: "2_portrait_2"}
],
{ type: 3,
list: [
{orientation: "portrait", size: "45", columns: 2, type: 3, _id: "3_portrait_2"}
]
}
];
So, group by common attribute type, and not repeating documents with same attributes orientation and columns, for example.
I was able to group by type:
{ _id : "$type", list: { $push: "$$ROOT" } }
But, how would be the next stages?

You can use two $group stages like this:
First group to get the elements where orientation, columns and type are the same. So, that is, create the "list" objects.
With that objects, another $group by the type and add result to an array
db.collection.aggregate([
{
"$group": {
"_id": {
"orientation": "$orientation",
"columns": "$columns",
"type": "$type"
},
"list": {
"$first": "$$ROOT"
}
}
},
{
"$group": {
"_id": "$_id.type",
"list": {
"$push": "$list"
}
}
}
])
Example here
Note how the first $group uses $first to avoid repeated values (each type has one single elements with the same orientation and columns. And the second $group uses $push to generate the list.
Also, to get your desired _id output (and assuming is compound by type_orientation_columns) in the list field you can do this:
Add orientation and columns in the second $group to keep as auxiliar values.
Use $project to get desired values
db.collection.aggregate([
{
"$group": {
"_id": {
"orientation": "$orientation",
"columns": "$columns",
"type": "$type"
},
"list": {
"$first": "$$ROOT"
}
}
},
{
"$group": {
"_id": "$_id.type",
"list": {
"$push": "$list"
},
"orientation": {
"$first": "$_id.orientation"
},
"columns": {
"$first": "$_id.columns"
}
}
},
{
"$project": {
"_id": 0,
"type": "$_id",
"list": {
"orientation": 1,
"size": 1,
"columns": 1,
"type": 1,
"_id": {
"$concat": [
{"$toString": "$_id"},
"_",
{"$toString": "$orientation"},
"_",
{"$toString": "$columns"}
]
}
}
}
}
])
Example here

Related

mongodb query to find the min price in array of objects

My documents:
[{
"title": "lenovo x-100",
"brand": "lenovo",
"category": "laptops",
"variant": [{
"price": 30000,
"RAM": "4GB",
"storage": "256GB",
"screen": "full hd",
"chip": "i3"
}, {
"price": 35000,
"RAM": "8GB",
"storage": "512GB",
"screen": "full hd",
"chip": "i5"
}, {
"price": 40000,
"RAM": "12GB",
"storage": "2TB",
"screen": "uhd",
"chip": "i7"
}],
"salesCount": 32,
"buysCount": 35,
"viewsCount": 60
},
{
"title": "samsung12",
"brand": "lenovo",
"category": "mobile phones",
"variant": [{
"price": 11000,
"RAM": "4GB",
"ROM": "32GB"
}, {
"price": 16000,
"RAM": "6GB",
"ROM": "64GB"
}, {
"price": 21000,
"RAM": "8GB",
"ROM": "128GB"
}],
"salesCount": 48,
"buysCount": 39,
"viewsCount": 74
}
Expected output
{
_id:"lenovo",
minPrice:1100
}
I have tried this method of aggregation
[{
$match: {
brand: 'lenovo'
}
}, {
$group: {
_id: '$brand',
prices: {
$min: '$variant.price'
}
}
}, {
$unwind: {
path: '$prices'
}
}, {
$group: {
_id: '$_id',
minPrice: {
$min: '$prices'
}
}
}]
I want to find the minimum price based on the brand, this query is returning the expected output but is there any better way to get the expected outcome because using $unwind operator in quite expensive in the sense it may take longer execution time, hoping for positive response.Thanks in advance.
You can use $reduce to replace the second $group stage.
$match
$group - Push variant.price into new array and results nested array of array.
$project:
3.1. $reduce - Use to flatten the nested array from the result 2 by $concat the arrays into one.
3.2. $min - Select min value from the result 3.1.
db.collection.aggregate([
{
$match: {
brand: "lenovo"
}
},
{
$group: {
_id: "$brand",
prices: {
$push: "$variant.price"
}
}
},
{
$project: {
_id: 1,
minPrice: {
$min: {
"$reduce": {
"input": "$prices",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
}
}
}
}
])
Sample Mongo Playground

Query maximum N records of each group base on a condition in MongoDB?

I have a question regarding querying data in MongoDB. Here is my sample data:
{
"_id": 1,
"category": "fruit",
"userId": 1,
"name": "Banana"
},
{
"_id": 2,
"category": "fruit",
"userId": 2,
"name": "Apple"
},
{
"_id": 3,
"category": "fresh-food",
"userId": 1,
"name": "Fish"
},
{
"_id": 4,
"category": "fresh-food",
"userId": 2,
"name": "Shrimp"
},
{
"_id": 5,
"category": "vegetable",
"userId": 1,
"name": "Salad"
},
{
"_id": 6,
"category": "vegetable",
"userId": 2,
"name": "carrot"
}
The requirements:
If the category is fruit, returns all the records match
If the category is NOT fruit, returns maximum 10 records of each category grouped by user
The category is known and stable, so we can hard-coded in our query.
I want to get it done in a single query. So the result expected should be:
{
"fruit": [
... // All records of
],
"fresh-food": [
{
"userId": 1,
"data": [
// Top 10 records of user 1 with category = "fresh-food"
]
},
{
"userId": 2,
"data": [
// Top 10 records of user 2 with category = "fresh-food"
]
},
...
],
"vegetable": [
{
"userId": 1,
"data": [
// Top 10 records of user 1 with category = "vegetable"
]
},
{
"userId": 2,
"data": [
// Top 10 records of user 2 with category = "vegetable"
]
},
]
}
I've found the guideline to group by each group using $group and $slice, but I can't apply the requirement number #1.
Any help would be appreciated.
You need to use aggregation for this
$facet to categorize incoming data, we categorized into two. 1. Fruit and 2. non_fruit
$match to match the condition
$group first group to group the data based on category and user. Second group to group by its category only
$objectToArray to make the object into key value pair
$replaceRoot to make the non_fruit to root with fruit
Here is the code
db.collection.aggregate([
{
"$facet": {
"fruit": [
{ $match: { "category": "fruit" } }
],
"non_fruit": [
{
$match: {
$expr: {
$ne: [ "$category", "fruit" ]
}
}
},
{
$group: {
_id: { c: "$category", u: "$userId" },
data: { $push: "$$ROOT" }
}
},
{
$group: {
_id: "$_id.c",
v: {
$push: {
uerId: "$_id.u",
data: { "$slice": [ "$data", 3 ] }
}
}
}
},
{ $addFields: { "k": "$_id", _id: "$$REMOVE" } }
]
}
},
{ $addFields: { non_fruit: { "$arrayToObject": "$non_fruit" } }},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ "$$ROOT", "$non_fruit" ]
}
}
},
{ $project: { non_fruit: 0 } }
])
Working Mongo playground

Comparing 2 fields from $project in a mongoDB pipeline

In a previous post I created a mongodb query projecting the number of elements matching a condition in an array. Now I need to filter this number of elements depending on another field.
This is my db :
db={
"fridges": [
{
_id: 1,
items: [
{
itemId: 1,
name: "beer"
},
{
itemId: 2,
name: "chicken"
}
],
brand: "Bosch",
size: 195,
cooler: true,
color: "grey",
nbMax: 2
},
{
_id: 2,
items: [
{
itemId: 1,
name: "beer"
},
{
itemId: 2,
name: "chicken"
},
{
itemId: 3,
name: "lettuce"
}
],
brand: "Electrolux",
size: 200,
cooler: true,
color: "white",
nbMax: 2
},
]
}
This is my query :
db.fridges.aggregate([
{
$match: {
$and: [
{
"brand": {
$in: [
"Bosch",
"Electrolux"
]
}
},
{
"color": {
$in: [
"grey",
"white"
]
}
}
]
}
},
{
$project: {
"itemsNumber": {
$size: {
"$filter": {
"input": "$items",
"as": "item",
"cond": {
$in: [
"$$item.name",
[
"beer",
"lettuce"
]
]
}
}
}
},
brand: 1,
cooler: 1,
color: 1,
nbMax: 1
}
}
])
The runnable example.
Which gives me this :
[
{
"_id": 1,
"brand": "Bosch",
"color": "grey",
"cooler": true,
"itemsNumber": 1,
"nbMax": 2
},
{
"_id": 2,
"brand": "Electrolux",
"color": "white",
"cooler": true,
"itemsNumber": 2,
"nbMax": 2
}
]
What I expect is to keep only the results having a itemsNumber different from nbMax. In this instance, the second fridge with _id:2 would not match the condition and should not be in returned. How can I modify my query to get this :
[
{
"_id": 1,
"brand": "Bosch",
"color": "grey",
"cooler": true,
"itemsNumber": 1,
"nbMax": 2
}
]
You can put a $match stage with expression condition at the end of your query,
$ne to check both fields should not same
{
$match: {
$expr: { $ne: ["$nbMax", "$itemsNumber"] }
}
}
Playground

Can we push object value into $project using mongodb

db.setting.aggregate([
{
$match: {
status: true,
deleted_at: 0,
_id: {
$in: [
ObjectId("5c4ee7eea4affa32face874b"),
ObjectId("5ebf891245aa27c290672325")
]
}
}
},
{
$lookup: {
from: "site",
localField: "_id",
foreignField: "admin_id",
as: "data"
}
},
{
$project: {
name: 1,
status: 1,
price: 1,
currency: 1,
numberOfRecord: {
$size: "$data"
}
}
},
{
$sort: {
numberOfRecord: 1
}
}
])
how to push the currency into price object using project please guide thanks a lot, also eager to know what is difference between $addtoSet and $push, what is good option to opt it from project or fix it from $addField
https://mongoplayground.net/p/RiWnnRtksb4
Output should be like this:
[
{
"_id": ObjectId("5ebf891245aa27c290672325"),
"currency": "USD",
"name": "Menz",
"numberOfRecord": 0,
"price": {
"numberDecimal": "20",
"currency": "USD",
},
"status": true
},
{
"_id": ObjectId("5c4ee7eea4affa32face874b"),
"currency": "USD",
"name": "Dave",
"numberOfRecord": 2,
"price": {
"numberDecimal": "10",
"currency": "USD"
},
"status": true
}
]
You can insert a field into an object with project directly, like this (field price):
$project: {
name: 1,
status: 1,
price: {
numberDecimal: "$price.numberDecimal",
currency: "$currency"
},
numberOfRecord: {
$size: "$data"
}
}
By doing it with project, there is no need to use $addField.
For the difference between $addToSet and $push, read this great answer.
You can just set the object structure while projecting, so in this case there's no need for either $push or $addToSet.
{
$project: {
name: "1",
status: 1,
price: {
currency: "$currency",
numberDecimal: "$price.numberDecimal"
},
currency: 1,
numberOfRecord: {
$size: "$data",
}
}
}
Now the difference between $push and $addToSet is pretty trivial and derived from the name, $push saves all items while $addToSet will just create a set of them, for example:
input:
[
//doc1
{
item: 1
},
//doc2
{
item: 2
},
//doc3
{
item: 1
}
]
Now this:
{
$group: {
_id: null,
items: {$push: "$item"}
}
}
Will result in:
{_id: null, items: [1, 2, 1]}
While:
{
$group: {
_id: null,
items: {$addToSet: "$item"}
}
}
Will result in:
{_id: null, items: [1, 2]}

MongoDB multiple counts, single document, arrays

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here