MongoDB Aggregation: calculation for every unique/distinct value - javascript

I got this data set from collection
{
item: 124001
price: 6
},
{
item: 124001
price: 6
},
{
item: 124121
price: 16
},
{
item: 124121
price: 13
},
{
item:n
price: x
}
from code:
let INDX = [xxx,xxx,xxx,xxx, ..n]
auctions.aggregate([
{
$match: { item: { $in: INDX }}
}
The problem is right after it, in the $group stage. For example I'd like to receive $min, $max or $avg 'price' for every unique/distinct item.
When I'm trying to use:
{
$group: {
min_1: { $min: "$price",}
}
}
I receive just $min from all data,
[ { _id: 0, min_1: 0 } ]
but I need something like:
{ _id: 124119, min_1: 66500 },
{ _id: 124437, min_1: 26398 }

Ok, here is a simple answer:
Just don't forget about _id field and use it, at $group stage, just like:
{
$group: {
_id: "$item",
min_1: {
$min: '$price',
}
}
}

Related

Push a document if not exist, update if exist in a nested array with mongoose

I have mongo document and when a new order appear, I need to push it or update (increment the quantity) if an order of a part_id already exist.
{
user_id: '13',
stock: [
{
part_id: 'P456',
quantity: 3
},
{
part_id: 'P905',
quantity: 8
}
]
}
I have a tried to use {upsert: true} and $inc but could not find the solution.
I have a tried to use {upsert: true} and $inc but could not find the solution
The upsert will not support in array of object, you can try 2 queries first find and second update,
Count documents using countDocuments:
let user_id = "13";
let part_id = "P456";
let hasDoc = await YourSchema.countDocuments({ user_id: user_id, "stock.part_id": part_id });
Check condition if document exists then increment quantity by one
Else push object in stock
// Document already exists, so increment it value
if (hasDoc > 0) {
await YourSchema.updateOne(
{ user_id: user_id, "stock.part_id": part_id },
{ $inc: { "stock.$.quantity": 1 } }
);
}
// Document not exists then add document
else {
await YourSchema.updateOne(
{ user_id: user_id },
{ $push: { stock: { part_id: part_id, quantity: 1 } } }
);
}
Second Option: You can update with aggregation pipeline starting from MongoDB 4.2,
$cond to check if part_id is in stock
yes, $map to iterate loop of stock and check condition if part_id match then add 1 in quantity otherwise return current object
no, add new object with part_id and quantity in stock using $concatArrays
let user_id = "13";
let part_id = "P456";
db.collection.update(
{ user_id: user_id },
[{
$set: {
stock: {
$cond: [
{ $in: [part_id, "$stock.part_id"] },
{
$map: {
input: "$stock",
in: {
$cond: [
{ $eq: ["$$this.part_id", part_id] },
{
part_id: "$$this.part_id",
quantity: { $add: ["$$this.quantity", 1] }
},
"$$this"
]
}
}
},
{ $concatArrays: ["$stock", [{ part_id: part_id, quantity: 1 }]] }
]
}
}
}]
)
Playground

how to use mongoose aggregation to get sum of two matching documents depending on field

I have two collections "Employee", "Office"
I am trying to find how many employees are in each area which contains office code. But there might be more than one office in the same area.
This is how my Office documents might look like
[
{
_id: "5b7d0f77e231b6b530b0ee5a",
code: "OB123456",
city: "Canmore"
// some other fields
},
{
_id: "5b7d0f77e531b6b530b0ee5b",
code: "OB858758",
city: "Vancouver"
},
{
_id: "5b7d0f77e531b6b530b0ee5d",
code: "EE858758",
city: "Vancouver"
},
]
this is how my Employee documents might look like
[
{
_id: "5b7d0f77e531b6b530b0edda",
name: 'Charlie',
office: {
code: 'OB123456'
// some other fields
}
},
{
_id: "5b7d0f73e531b6b530b0ee5b",
name: 'Bill',
office: {
code: 'EE858758'
}
},
{
_id: "5b7d0f77e531b6b530b0ee5n",
name: 'Echo',
office: {
code: 'OB123456'
}
},
];
I am looking into mongoose aggregate, and only tried
await Employee.aggregate([
{
$lookup: {
from: 'offices',
localField: 'office.code',
foreignField: 'code',
as: 'officeCode'
},
$group: {
_id: 'officeCode.city',
count: { $sum: 1 }
}
}
]);
which for sure does not work, I tried reading some of the aggregation documention but cannot come up with a good idea how to get this done
Thanks in advance for any suggestions or advices.
Sample output of what I am looking for
{
"Vancouver": 1,
"Canmore": 2
}
You have to start from office instead of employee, so you can create a list of code for each area (city), then lookup to map with your employees.
db.office.aggregate([
{
$group: {
_id: "$city",
codes: {
$addToSet: "$code"
}
}
},
{
$lookup: {
from: "employee",
localField: "codes",
foreignField: "office.code",
as: "employees"
},
},
{
$group: {
_id: null,
data: {
$push: {
k: "$_id",
v: {
$size: "$employees"
}
}
}
}
},
{
$replaceRoot: {
newRoot: {
"$arrayToObject": "$data"
}
}
}
])
The two last stages are here only to format your result as described in your expected output.
You can test it here

MongoDB agregation with filtering in array

I need to count records grouped by tags and have filtered bofore including in ones
// in db
{tags: ['video', 'Alex'], ... },
{tags: ['video', 'John'], ... },
{tags: ['video', 'John'], ... },
{tags: ['text', 'Alex'], ... },
{tags: ['text', 'John'], ... },
client.db('mydb').collection('Files').aggregate(
[
{ $group: { _id: { tags: '$tags' }, total: { $sum: 1 } } },
{ $match: { tags: 'video' } },
],
).toArray()
But sadly I got zero docs. If remove $group section I got 3 docs.
In original request I anticipated 2 docs
{ _id: ['video', 'Alex'], total: 1 },
{ _id: ['video', 'John'], total: 2 }
In aggregation the order of pipeline is important, as output of previous stage is fed to the next one.
Your query is almost there basis the expected output. Just move $match stage before the $group stage.
Query:
db.collection.aggregate([
{
$match: {
"tags": "video"
}
},
{
$group: {
_id: {
tags: "$tags"
},
total: {
$sum: 1
}
}
}
]);
Working Example

Mongo find by sum of subdoc array

I'm trying to find stocks in the Stock collection where the sum of all owners' shares is less than 100. Here is my schema.
const stockSchema = new mongoose.Schema({
owners: [
{
owner: {
type: Schema.Types.ObjectId,
ref: "Owner"
},
shares: {
type: Number,
min: 0,
max: 100
}
}
]
}
const Stock = mongoose.model("Stock", stockSchema);
I've tried to use aggregate but it returns a single object computed over all stocks in the collection, as opposed to multiple objects with the sum of each stock's shares.
stockSchema.statics.getUnderfundedStocks = async () => {
const result = await Stock.aggregate([
{ $unwind: "$owners" },
{ $group: { _id: null, shares: { $sum: "$owners.shares" } } },
{ $match: { shares: { $lt: 100 } } }
]);
return result;
};
So, rather than getting:
[ { _id: null, shares: 150 } ] from getUnderfundedStocks, I'm looking to get:
[ { _id: null, shares: 90 }, { _id: null, shares: 60 } ].
I've come across $expr, which looks useful, but documentation is scarce and not sure if that's the appropriate path to take.
Edit: Some document examples:
/* 1 */
{
"_id" : ObjectId("5ea699fb201db57b8e4e2e8a"),
"owners" : [
{
"owner" : ObjectId("5ea62a94ccb1b974d40a2c72"),
"shares" : 85
}
]
}
/* 2 */
{
"_id" : ObjectId("5ea699fb201db57b8e4e2e1e"),
"owners" : [
{
"owner" : ObjectId("5ea62a94ccb1b974d40a2c72"),
"shares" : 20
},
{
"owner" : ObjectId("5ea62a94ccb1b974d40a2c73"),
"shares" : 50
},
{
"owner" : ObjectId("5ea62a94ccb1b974d40a2c74"),
"shares" : 30
}
]
}
I'd like to return an array that just includes document #1.
You do not need to use $group here. Simply use $project with $sum operator.
db.collection.aggregate([
{ "$project": {
"shares": { "$sum": "$owners.shares" }
}},
{ "$match": { "shares": { "$lt": 100 } } }
])
Or even you do not need to use aggregation here
db.collection.find({
"$expr": { "$lt": [{ "$sum": "$owners.shares" }, 100] }
})
MongoPlayground

How do I group by in mongodb to produce a list of objects?

I have data like this,
{
meta: {
artist: "artist",
album: "album",
year: 2008
}
}
and I want to do the equivilent of an SQL group by on the artist to produce a list of objects with { album: album, year: year } using mongodb aggregation.
I have this query which is almost what I want but I dont think its the correct way of doing it.
db.pending.aggregate( [
{ $project:
{ 'meta.artist': 1, 'meta.album': 1, 'meta.year':1 }
},
{ $group:
{
'_id':
{ artist: '$meta.artist', album: '$meta.album',
year: { $year: '$meta.year'}
}
}
}
] )
I think you need the $addToSet operator:
db.test.aggregate(
{ $project:
{ 'meta.artist': 1
, 'meta.album': 1
, 'meta.year': 1 }
}
, { $group:
{ _id: '$meta.artist'
, albums: {
$addToSet: {
album: '$meta.album'
, year: '$meta.year' }
}
}
}
)
I think you can do the following by aggregation framework like this:
db.pending.aggregate(
{$group: {_id: '$artist', albums: {$push: {album: '$album', year: '$year'}}}},
{$sort: {_id: 1}}
);
Or you can do that by a map reduce function:
var mapFunction = function() {
emit(this.meta.artist, {album: this.meta.album, year: this.meta.year});
};
var reduceFunction = function(artistId, albumValues) {
var reducedObject = {
artisId: artistId,
albums: []};
albumValues.forEach( function(albumValue) {
reducedObject.albums.push(
{album: albumValue.album,
year: albumValue.year}
);
});
return reducedObject;
};
db.pending.mapReduce(
mapFunction,
reduceFunction,
{ out: { inline: 1 } }
)
I don't think you can do what you want by SQL group by query either. This will return you a result similar to this {_id: "artisName", values[{album: 'album1', year: 'year1'}, {album: 'album2', year: 'year2'}]}

Categories

Resources