How to ensure grouping via two separate criteria - mongodb

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"},
}}
)

Related

How to get last document of each day in MongoDB collection?

I have a model Entry to which includes details of a hospital at a particular time. The data looks like this:
{
"_id": "5ef9c7337874820008c1a026",
"date": 1593427763640,
//... some data
"hospital": {
"_id": "5ef8d06630c364000840bb6d",
"name": "City Hospital",
//... some data
},
}
I want to get the last query of each day grouped by the hospital ID. In MySQL, it can be achieved using INNER JOIN. How can I do it using MongoDB?
Given a day, calculate start and end of a day.
This is to be used for filtering records, $match
start_of_day_ephocs=
end_of_day_ephocs=
Aggregate Query
sort by date, Group by hospital id,and select first document
db.Entry.aggregate(
[
{ "$match": { "date": {"$gte":start_of_day_ephocs,"$lte":end_of_day_ephocs }} },
{ "$sort": { "date": -1 } },
{
$group:
{
"_id": "$hospital._id",
"last_document": { "$first": "$$ROOT" }
}
}
]
)
Consider a sales collection with the following documents:
{ "_id" : 1, "item" : "abc", "date" : ISODate("2014-01-01T08:00:00Z"), "price" : 10, "quantity" : 2 }
{ "_id" : 2, "item" : "jkl", "date" : ISODate("2014-02-03T09:00:00Z"), "price" : 20, "quantity" : 1 }
{ "_id" : 3, "item" : "xyz", "date" : ISODate("2014-02-03T09:05:00Z"), "price" : 5, "quantity" : 5 }
{ "_id" : 4, "item" : "abc", "date" : ISODate("2014-02-15T08:00:00Z"), "price" : 10, "quantity" : 10 }
{ "_id" : 5, "item" : "xyz", "date" : ISODate("2014-02-15T09:05:00Z"), "price" : 5, "quantity" : 10 }
{ "_id" : 6, "item" : "xyz", "date" : ISODate("2014-02-15T12:05:10Z"), "price" : 5, "quantity" : 5 }
{ "_id" : 7, "item" : "xyz", "date" : ISODate("2014-02-15T14:12:12Z"), "price" : 5, "quantity" : 10 }
The following operation first sorts the documents by item and date, and then in the following $group stage, groups the now sorted documents by the item field and uses the $last accumulator to compute the last sales date for each item:
db.sales.aggregate(
[
{ $sort: { item: 1, date: 1 } },
{
$group:
{
_id: "$item",
lastSalesDate: { $last: "$date" }
}
}
]
)
The operation returns the following results:
{ "_id" : "xyz", "lastSalesDate" : ISODate("2014-02-15T14:12:12Z") }
{ "_id" : "jkl", "lastSalesDate" : ISODate("2014-02-03T09:00:00Z") }
{ "_id" : "abc", "lastSalesDate" : ISODate("2014-02-15T08:00:00Z") }
Resource

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.

Finding the collection details if one field is distinct

I have a database of teachers details as given
{ "_id" : ObjectId("5bcc0a44f2752576a8545d99"), "Teacher_id" : "Pic002", "Teacher_Name" : "Ravi Kumar", "Dept_Name" : "IT", "Salary" : 40000, "Status" : "A" }
{ "_id" : ObjectId("5bcc0a5af2752576a8545d9a"), "Teacher_id" : "Pic003", "Teacher_Name" : "Akshay", "Dept_Name" : "Comp", "Salary" : 25500, "Status" : "N" }
{ "_id" : ObjectId("5bcc0a85f2752576a8545d9b"), "Teacher_id" : "Pic003", "Teacher_Name" : "Akshay", "Dept_Name" : "Comp", "Salary" : 25500, "Status" : "N" }
{ "_id" : ObjectId("5bcc0a9af2752576a8545d9c"), "Teacher_id" : "Pic004", "Teacher_Name" : "Sumit", "Dept_Name" : "Mech", "Salary" : 35000, "Status" : "N" }
How would I list down complete details of a teacher whose Department Name is distinct?
Basically, I want to display the details of the first first and last document in this collection.
You can achieve this via this aggregation:
db.collection.aggregate([{
$group: {
_id: "$Dept_Name",
docs: {
$addToSet: "$$CURRENT"
},
count: {
$sum: 1
}
}
},
{
$match: {
"count": {
"$eq": 1
}
}
},
{
$unwind: "$docs"
},
{
$replaceRoot: {
newRoot: "$docs"
}
}
])
The idea is first to $group and at the same time keep the objects via $addToSet.
Then filter (via $match) on those which count is 1 and then $unwind & $replaceRoot.
See it working here

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 can I get the lowest values in a MongoDB collection?

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"
}
]
}