How can I get the lowest values in a MongoDB collection? - mongodb

I have a MongoDB collection called product which has the following documents as seen below.
{
"product" : "Milk",
"barcode" : 12345,
"price" : 100,
"store" : "BestBuy"
},
{
"product" : "Milk",
"barcode" : 12345,
"price" : 100,
"store" : "WalMart"
},
{
"product" : "Milk",
"barcode" : 12345,
"price" : 130,
"store" : "Target"
},
{
"product" : "Milk",
"barcode" : 12345,
"price" : 500,
"store" : "Game"
}
I wish to query the collection and only return documents that have the lowest price e.g
{
product: "Milk",
barcode: 12345,
price: 100,
store: "BestBuy"
}
{
product: "Milk",
barcode: 12345,
price: 100,
store: "WalMart"
}
But when I run my aggregation query:
db.test.aggregate([{$match:{barcode:1234}},{$group: {_id:"$name", price: {$min:"$price"} } }])
It only returns one document.

You need to $group your documents by "price". From there, you $sort them by "_id" in ascending order and use $limit to return the first document which nothing other than the document with the minimum value.
db.products.aggregate([
{ "$group": {
"_id": "$price",
"docs": { "$push": "$$ROOT" }
}},
{ "$sort": { "_id": 1 } },
{ "$limit": 1 }
])
which produces something like:
{
"_id" : 100,
"docs" : [
{
"_id" : ObjectId("574a161b17569e552e35edb5"),
"product" : "Milk",
"barcode" : 12345,
"price" : 100,
"store" : "BestBuy"
},
{
"_id" : ObjectId("574a161b17569e552e35edb6"),
"product" : "Milk",
"barcode" : 12345,
"price" : 100,
"store" : "WalMart"
}
]
}

Related

MongoDB: Remove field in array with $lookup localField

I am beginner with MongoDB. I use $lookup in aggregation and use localField to get reference document.
db.orders.insert([
{ "_id" : 1, "item" : ['almonds','pecans','bread'], "price" : 12, "quantity" : 2 },
{ "_id" : 2, "item" : ['cashews','catty'], "price" : 20, "quantity" : 1 }
])
I tried to use $lookup and localField in aggregation but I can't find way to remove field _id and description
db.inventory.insert([
{ "_id" : 1, "sku" : "almonds", description: "product 1", "instock" : 120 },
{ "_id" : 2, "sku" : "bread", description: "product 2", "instock" : 80 },
{ "_id" : 3, "sku" : "cashews", description: "product 3", "instock" : 60 },
{ "_id" : 4, "sku" : "pecans", description: "product 4", "instock" : 70 },
{ "_id" : 5, "sku": "catty", description: "Incomplete", "instock" : 100 },
{ "_id" : 6 }
])
Expected results:
[
{
"_id" : 1,
"item" : [
{ "sku" : "almonds", "instock" : 120 },
{ "sku" : "pecans", "instock" : 70 },
{ "sku" : "bread", "instock" : 80 }
],
"price" : 12,
"quantity" : 2
},
{
"_id" : 2,
"item" : [
{ "sku" : "cashews", "instock" : 60 },
{ "sku" : "catty", "instock" : 100 }
],
"price" : 20,
"quantity" : 1
}
]
You can try lookup with aggregation pipeline,
$lookup join with inventory collection
$match to match is inventory sku in item array
$project to display required fields
db.orders.aggregate([
{
$lookup: {
from: "inventory",
as: "item",
let: { i: "$item" },
pipeline: [
{ $match: { $expr: { $in: ["$sku", "$$i"] } } },
{
$project: {
_id: 0,
sku: 1,
instock: 1
}
}
]
}
}
])
Playground

Whats the alternative to $replaceRoot on mongoDB? $replaceRoot is incompatible with documentDB

The problem: I'm trying to make a query on MongoDB, but I'm using the DocumentDb from amazon, where some operations are no supported. I wanted to find an alternative to get the same result, if possible. Basically I want to change the root of the result, instead of being the first entity, I need it to be some merging of some values in different levels of the document.
So, I have the following structure in my collection:
{
"_id" : ObjectId("5e598bf4d98f7c70f9aa3b58"),
"status" : "active",
"invoices" : [
{
"_id" : ObjectId("5e598bf13b24713f50600375"),
"value" : 1157.52,
"receivables" : [
{
"situation" : {
"status" : "active",
"reason" : []
},
"rec_code" : "001",
"_id" : ObjectId("5e598bf13b24713f50600374"),
"expiration_date" : ISODate("2020-03-25T00:00:00.000Z"),
"value" : 1157.52
}
],
"invoice_code" : 9773,
"buyer" : {
"legal_name" : "test name",
"buyer_code" : "223132165498797"
}
},
],
"seller" : {
"code" : "321654897986",
"name" : "test name 2"
}
}
What I want to achieve is to list all "receivables" like this, where the _id is the _id of the receivable:
[{
"_id" : ObjectId("5e598bf13b24713f50600374"),
"situation" : {
"status" : "active",
"reason" : []
},
"rec_code" : "001",
"expiration_date" : ISODate("2020-03-25T00:00:00.000Z"),
"value" : 1157.52,
"status" : "active",
"seller" : {
"cnpj" : "321654897986",
"name" : "test name 2"
},
"invoice_code" : 9773.0,
"buyer" : {
"legal_name" : "test name",
"cnpj" : "223132165498797"
}
}]
This I can do with $replaceRoot in with the query below on MongoDB, but using documentDB I can't use $replaceRoot or $mergeObjects. Do you know how can I get the same result with other operators?:
db.testCollection.aggregate([
{ $unwind: "$invoices" },
{ $replaceRoot: {
newRoot: {
$mergeObjects: ["$$ROOT","$invoices"]}
}
},
{$project: {"_id": 0, "value": 0, "created_at": 0, "situation": 0}},
{ $unwind: "$receivables" },
{ $replaceRoot: {
newRoot: {
$mergeObjects: ["$receivables", "$$ROOT"]
}
}
},
{$project:{"created_at": 0, "receivables": 0, "invoices": 0}}
])
After going through mongodb operations, I could get a similar result fro what I wanted with the following query without $replaceRoot. It turns out it was a better query, I think:
db.testCollection.aggregate([
{$unwind: "$invoices"},
{$project : {
created_at: 1,
seller: "$seller",
buyer: "$invoices.buyer",
nnf: "$invoices.nnf",
receivable: '$invoices.receivables'
}
},
{$unwind: "$receivable"},
{$project : {
_id: '$receivable._id',
seller: 1,
buyer: 1,
invoice_code: 1,
receivable: 1,
created_at: 1,
}
},
{$sort: {"created_at": -1}},
])
This query resulted in the following structure list:
[{
"created_at" : ISODate("2020-03-06T09:47:26.161Z"),
"seller" : {
"name" : "Test name",
"cnpj" : "21231232131232"
},
"buyer" : {
"cnpj" : "21322132164654",
"legal_name" : "Test name 2"
},
"invoice_code" : 66119,
"receivable" : {
"rec_code" : "001",
"_id" : ObjectId("5e601bb5efff82b92935bad4"),
"expiration_date" : ISODate("2020-03-17T00:00:00.000Z"),
"value" : 6540.7,
"situation" : {
"status" : "active",
"reason" : []
}
},
"_id" : ObjectId("5e601bb5efff82b92935bad4")
}]
Support for $replaceRoot was added to Amazon DocumentDB in January 2021.

Problems aggregating MongoDB

I am having problems aggregating my Product Document in MongoDB.
My Product Document is:
{
"_id" : ObjectId("5d81171c2c69f45ef459e0af"),
"type" : "T-Shirt",
"name" : "Panda",
"description" : "Panda's are cool.",
"image" : ObjectId("5d81171c2c69f45ef459e0ad"),
"created_at" : ISODate("2019-09-17T18:25:48.026+01:00"),
"is_featured" : false,
"sizes" : [
"XS",
"S",
"M",
"L",
"XL"
],
"tags" : [ ],
"pricing" : {
"price" : 26,
"sale_price" : 8
},
"categories" : [
ObjectId("5d81171b2c69f45ef459e086"),
ObjectId("5d81171b2c69f45ef459e087")
],
"sku" : "5d81171c2c69f45ef459e0af"
},
And my Category Document is:
{
"_id" : ObjectId("5d81171b2c69f45ef459e087"),
"name" : "Art",
"description" : "These items are our artsy options.",
"created_at" : ISODate("2019-09-17T18:25:47.196+01:00")
},
My aim is to perform aggregation on the Product Document in order to count the number of items within each Category. So I have the Category "Art", I need to count the products are in the "Art" Category:
My current aggregate:
db.product.aggregate(
{ $unwind : "$categories" },
{
$group : {
"_id" : { "name" : "$name" },
"doc" : { $push : { "category" : "$categories" } },
}
},
{ $unwind : "$doc" },
{
$project : {
"_id" : 0,
"name" : "$name",
"category" : "$doc.category"
}
},
{
$group : {
"_id" : "$category",
"name": { "$first": "$name" },
"items_in_cat" : { $sum : 1 }
}
},
{ "$sort" : { "items_in_cat" : -1 } },
)
Which does actually work but not as I need:
{
"_id" : ObjectId("5d81171b2c69f45ef459e082"),
"name" : null, // Why is the name of the category no here?
"items_in_cat" : 4
},
As we can see the name is null. How can I aggregate the output to be:
{
"_id" : ObjectId("5d81171b2c69f45ef459e082"),
"name" : "Art",
"items_in_cat" : 4
},
We need to use $lookup to fetch the name from Category collection.
The following query can get us the expected output:
db.product.aggregate([
{
$unwind:"$categories"
},
{
$group:{
"_id":"$categories",
"items_in_cat":{
$sum:1
}
}
},
{
$lookup:{
"from":"category",
"let":{
"id":"$_id"
},
"pipeline":[
{
$match:{
$expr:{
$eq:["$_id","$$id"]
}
}
},
{
$project:{
"_id":0,
"name":1
}
}
],
"as":"categoryLookup"
}
},
{
$unwind:{
"path":"$categoryLookup",
"preserveNullAndEmptyArrays":true
}
},
{
$project:{
"_id":1,
"name":{
$ifNull:["$categoryLookup.name","NA"]
},
"items_in_cat":1
}
}
]).pretty()
Data set:
Collection: product
{
"_id" : ObjectId("5d81171c2c69f45ef459e0af"),
"type" : "T-Shirt",
"name" : "Panda",
"description" : "Panda's are cool.",
"image" : ObjectId("5d81171c2c69f45ef459e0ad"),
"created_at" : ISODate("2019-09-17T17:25:48.026Z"),
"is_featured" : false,
"sizes" : [
"XS",
"S",
"M",
"L",
"XL"
],
"tags" : [ ],
"pricing" : {
"price" : 26,
"sale_price" : 8
},
"categories" : [
ObjectId("5d81171b2c69f45ef459e086"),
ObjectId("5d81171b2c69f45ef459e087")
],
"sku" : "5d81171c2c69f45ef459e0af"
}
Collection: category
{
"_id" : ObjectId("5d81171b2c69f45ef459e086"),
"name" : "Art",
"description" : "These items are our artsy options.",
"created_at" : ISODate("2019-09-17T17:25:47.196Z")
}
{
"_id" : ObjectId("5d81171b2c69f45ef459e087"),
"name" : "Craft",
"description" : "These items are our artsy options.",
"created_at" : ISODate("2019-09-17T17:25:47.196Z")
}
Output:
{
"_id" : ObjectId("5d81171b2c69f45ef459e087"),
"items_in_cat" : 1,
"name" : "Craft"
}
{
"_id" : ObjectId("5d81171b2c69f45ef459e086"),
"items_in_cat" : 1,
"name" : "Art"
}

Use $gt operator to return document values among all the objects

This is one of many similar objects in shopping list collection. How do I do a query to get the list of only the "name" of people buying more than 2 "Noodles"?
Please help me figure this out, thanks in advance.
I assume this should have the $gt operator but I am not sure how to execute it correctly.
{
"_id" : ObjectId("591422529f75f9119575c1d8"),
"name" : "Hisham",
"age" : 20,
"address" : {
"house" : "HomeName",
"street" : "Fairyland",
"city" : "Faketon",
"pincode" : 000000
},
"itemlist" : [
{
"iname" : "Soap",
"quantity" : 2,
"price" : 10,
"rate" : 20,
"itemID" : "1"
},
{
"iname" : "Mirror",
"quantity" : 1,
"price" : 600,
"rate" : 600,
"itemID" : "4"
},
{
"iname" : "Noodles",
"quantity" : 4,
"price" : 50,
"rate" : 200,
"itemID" : "5"
},
{
"iname" : "Plug",
"quantity" : 2,
"price" : 50,
"rate" : 100,
"itemID" : "6"
}
]
}
you can achieve this with the aggregation framework like this :
db.collection.aggregate([
{
$unwind:"$itemlist"
},
{
$match:{
"itemlist.iname":"Noodles"
}
},
{
$group:{
_id:"$itemlist.iname",
name:{
$first:"$name"
},
count:{
$sum:1
}
}
},
{
$match:{
count:{
$gte:2
}
}
}
])
How it works:
unwind the itemlist array with $unwind
keep only Noodles item
count occurence of Noodles using $group
keep only document where count >= 2
You can select the all documents that match your criteria using the $elemMatch operator in the $match. From there all you need is a $group stage.
db.collection.aggregate([
{ "$match": {
"itemlist": {
"$elemMatch": {
"quantity": { "$gt": 2 },
"iname": "Noodles"
}
}
}},
{ "$group": { "_id": null, "names": { "$push": "$name" } } }
])

How to ensure grouping via two separate criteria

Expanded from How to average the summed up values in mongodb?
Using MongoDB 2.4.8,
I have the following records
{
"category" : "TOYS",
"price" : 12,
"status" : "online",
"_id" : "35043"
}
{
"category" : "TOYS",
"price" : 13,
"status" : "offline",
"_id" : "35044"
}
{
"category" : "TOYS",
"price" : 22,
"status" : "online",
"_id" : "35045"
}
{
"category" : "BOOKS",
"price" : 13,
"status" : "offline",
"_id" : "35046"
}
{
"category" : "BOOKS",
"price" : 17,
"status" : "online",
"_id" : "35047"
}
{
"category" : "TOYS",
"price" : 19,
"status" : "unavailable",
"_id" : "35048"
}
{
"category" : "BOOKS",
"price" : 10,
"status" : "unavailable",
"_id" : "35049"
}
{
"category" : "BOOKS",
"price" : 17,
"status" : "unavailable",
"_id" : "35050"
}
I want to find the average price of all categories whose status is online OR offline and total price within a category is more than 50.
Toys offline and Toys online are considered two separate categories.
I adapted the answer given.
db.items.aggregate([
{$match:
{
$or: [
{status:"online"},
{status:"offline"}
]
}
},
{$group :
{
_id: "$category",
total_price: {$sum:"$price"},
}
},
{$match:
{
total_price:{$gt:50}
}
},
{$group :
{
_id: "1",
avg_price: {$avg:"$total_price"},
}
},
]);
But I believe this query I adapted grouped categories of the same name together which is not what I am looking for.
If online and offline are the only values for status, you can remove the initial $match step. If it is needed, it would be more appropriate to use the $in operator as these values could be found in the same index (if one existed).
I think the only step you are missing is that you can $group by multiple fields (i.e. category and status):
db.items.aggregate(
// If 'online' and 'offline' are the only possible status values, this may be unnecessary
{ $match: {
'status' : { $in: [ 'online', 'offline' ] }
}},
// Group by category & status
{ $group: {
_id: { category: "$category", status: "$status" },
total_price: { $sum: "$price" },
}},
// Only find groups where total_price is > 50
{ $match: {
total_price: { $gt:50 }
}},
// Find the average price for the group
{ $group : {
_id: null,
avg_price: {$avg:"$total_price"},
}}
)