Having trouble wrapping my head around complex $group'ing/aggregation - javascript

I have a schema that is something like this:
{
_id: <objectid>
customer: <objectid>
employee: <objectid>
date: <Month/day/year>
amount: <Number>
}
Using angular, I'm trying to make a page that pulls that data and builds separate tables for each day. So something like I would have a tab for yesterday, that would open up a view for a table that has all of my employees listed and the sum of their for the day. Something like this:
[{
date: 10/29/2019
dataFromThisDate: [
{
employee: <name>
sumAmount: <sum(amount for this date)>
list: [<array of all of the transaction _ids
},
{
employee: <name 2>
//etc
}]
},
{
date: 10/30/2019
dataFromThisDate: //etc
}]
Basically as far as I've gotten is just:
MyCollection.aggregate(
[{
$group: {
_id: "$date"
}
}],function(err, result) { //blah }
)
But I'm not sure how to even do nested grouping (first by date, then by employee on that date). Just thinking through it, it feels like I would have to group by date, then pass on all the data to a new grouping pipeline?
Sorry I don't have more of what I've tried, this whole aggregation thing is just completely new to me and I can't find good examples that are similar enough to what I'm trying to do to learn from. I looked at the api docs for mongodb and I understand their basic examples and play around with them, but I'm just having a hard time coming up with how to do my more complex example.

You can try something like this. This uses two groups. First group by date and employee, summing the amount and adding the transaction ids. Second group by date and add the employees with their total amount and transactions list.
aggregate([{
$group: {
_id: {
date: "$date",
employee: "$employee"
},
amount: {
$sum: "$amount"
},
transactionIds: {
$push: "$_id"
}
}
}, {
$group: {
_id: "$_id.date",
dataFromThisDate: {
$push: {
employee: "$_id.employee",
sumAmount: "$amount",
list: "$transactionIds"
}
}
}
}])
Output
{
"_id": "12/21/2016",
"dataFromThisDate": [{
"employee": "employee1",
"sumAmount": 100,
"list": [ObjectId("58151e881ac3c9ce82782663")]
}, {
"employee": "employee2",
"sumAmount": 73,
"list": [ObjectId("58151e881ac3c9ce82782665"), ObjectId("58151e881ac3c9ce82782666")]
}]
}

Related

Mongodb Sum query returns nothing

I have this example of activities row collection
{
"_id" : ObjectId("5ec90b5258a37c002509b27d"),
"user_hash" : "asdsc4be9fe7xxx",
"type" : "Expense",
"name" : "Lorem",
"amount" : 10000,
"date_created" : 1590233938
}
I'd like to collect the sum amount of the activity with this aggregate code
db.activities.aggregate(
[
{
$group:
{
_id: "$id",
total: { $sum: "$amount" }
}
},
{
$match: { type: "Expense", "user_hash": "asdsc4be9fe7xxx" }
}
]
)
Expected result : {_id: null, total: xxxxx }
Actual result:
Any solution for this? Thank you in Advance
There're 2 problems with your query:
You making the sum aggregation on each individual document instead doing it on the whole collection because you specify _id: "$id", while you need to specify _id: null.
You're performing the match stage in the aggregating after the group stage. But you need to perform it before because after you group the result will be something like:
{
"_id": null,
"total": 15
}
As you can see this object doesn't have any of the fields that the original objects have therefore 0 results will be matched. The order of stages is important because essentially each stage performs some operation based on the result of the previous stage (there're some exceptions when mongodb automatically optimizes stages but different order in these stages doesn't produce different results).
So the query should be:
db.activities.aggregate(
[
{
$match: { type: "Expense", "user_hash": "asdsc4be9fe7xxx" }
},
{
$group:
{
_id: null,
total: { $sum: "$amount" }
}
},
]
)

Objection insertGraph, insert new and relate or relate to existing rows

So, I'm not super knowledge with MySQL relations, upserting and such. I'm looking for an explanation on how (if?) this is possible to do.
[
{
scheduledAt: '17:55',
league: { name: 'Champions League - Group Stage' }
},
{
scheduled_at: '19:45',
league: { name: 'Champions League - Group Stage' }
},
{
scheduled_at: '19:30',
league: { name: 'Primera B Metropolitana' },
},
{
scheduled_at: '21:00',
league: { name: 'Primera B Metropolitana' }
}
]
Say I wanted to insert this graph of data. The root objects are going into the fixtures table, and the league property is this relation in the Fixtures model.
{
league: {
relation: Model.BelongsToOneRelation,
modelClass: `${__dirname}/League`,
join: {
from: 'fixtures.league_id',
to: 'leagues.id'
}
}
}
So, currently if I use insertGraph to insert all this data. It's inserts into both the fixtures and leagues table and relates as you would expect.
{
"scheduled_at": "17:55",
"league": {
"name": "Champions League - Group Stage",
"created_at": "2018-10-03T13:02:03.995Z",
"id": 1
},
"league_id": 1
"created_at": "2018-10-03T13:02:04.042Z",
"id": 1
}
However if I insert the exact same league object, it will just create another duplicate league and fixture row with the next incremented ID (2 in this case).
Is it possible for it to find if a league exists with that name, and then use that row/ID as the league_id, like so:
{
"scheduled_at": "17.55",
"league_id": 1
"created_at": "2018-10-03T13:02:04.042Z",
"id": 2
}
Sorry if I've explained this horrendously. But I'm not so hot on the terminology so I don't know what I'm actually looking to do. I feel like this is a super easy thing, but maybe my structure or method is wrong.

MongoDB aggregate conditional push with fixed array length

Scenario: Members can choose (yes/no) from 4 different activities available.
Based on the following input,
[
{
name:"member1",
activity:"activity1",
selected:true
},
{
name:"member1",
activity: "activity3",
selected:false
},
{
name:"member2",
activity:"activity2",
selected:true
},
{
name:"member2",
activity: "activity4",
selected:false
}
]
need a result as follows, showing member's choice on all the 4 activities in the order of activity 1 to 4 (including the activities which the user has not made a decision yet)
[
{
name:"member1",
activities:[true,null,false,null]
},
{
name:"member2",
activities:[null,true,null,false]
}
]
I tried the following code,
db.collection("MemberActivities").aggregate(
[
{
$group:
{
_id: "$MemberName",
activities: { $push: "$selected"}
}
}
]
but, it contain only the activities the user has made a decision (yes/no).
[
{
_id:"member1",
activities:[true,false]
},
{
_id:"member2",
activities:[true,false]
} ]
Please guide on how to get desired result.

How do I properly map attributes of relations in sequelize.js?

I'm creating a recipe-database (commonly known as a cookbook) where I need to have a many-to-many relationship between ingredients and recipes and I'm using sequelize.js in combination with postgresql.
When an ingredient is added to a recipe I need to declare the correct amount of that ingredient that goes into the recipe.
I've declared (reduced example)
var Ingredient = sequelize.define('Ingredient', {
name: Sequelize.STRING
}, {
freezeTable: true
});
var Recipe = sequelize.define('Recipe', {
name: Sequelize.STRING
}, {
freezeTable: true
});
var RecipeIngredient = sequelize.define('RecipeIngredient', {
amount: Sequelize.DOUBLE
});
Ingredient.belongsToMany(Recipe, { through: RecipeIngredient });
Recipe.belongsToMany(Ingredient, {
through: RecipeIngredient,
as: 'ingredients'
});
My problem is with how data is returned when one my REST endpoints do
router.get('/recipes', function(req, res) {
Recipe.findAll({
include: [{
model: Ingredient,
as: 'ingredients'
}]
}).then(function(r) {
return res.status(200).json(r[0].toJSON());
})
});
The resulting JSON that gets sent to the client looks like this (timestamps omitted):
{
"id": 1,
"name": "Carrots",
"ingredients": [
{
"id": 1,
"name": "carrot",
"RecipeIngredient": {
"amount": 12,
"RecipeId": 1,
"IngredientId": 1
}
}
]
}
While all I wanted was
{
"id": 1,
"name": "Carrots",
"ingredients": [
{
"id": 1,
"name": "carrot",
"amount": 12,
}
]
}
That is, I want the amount field from the relation-table to be included in the result instead of the entire RecipeIngredient object.
The database generated by sequelize looks like this:
Ingredients
id name
1 carrot
Recipes
id name
1 Carrots
RecipeIngredients
amount RecipeId IngredientId
12 1 1
I've tried to provide an attributes array as a property to the include like this:
include: [{
model: Ingredient,
as: 'ingredients',
attributes: []
}]
But setting either ['amount'] or ['RecipeIngredient.amount'] as the attributes-value throws errors like
Unhandled rejection SequelizeDatabaseError: column ingredients.RecipeIngredient.amount does not exist
Obviously I can fix this in JS using .map but surely there must be a way to make sequelize do the work for me?
I am way late to this one, but i see it been viewed quite a bit so here is my answer on how to merge
attributes
Some random examples in this one
router.get('/recipes', function(req, res) {
Recipe.findAll({
include: [{
model: Ingredient,
as: 'ingredients',
through: {
attributes: ['amount']
}
}]
})
.then(docs =>{
const response = {
Deal: docs.map(doc =>{
return{
cakeRecipe:doc.recipe1,
CokkieRecipe:doc.recipe2,
Apples:doc.ingredients.recipe1ingredient
spices:[
{
sugar:doc.ingredients.spice1,
salt:doc.ingredients.spice2
}
]
}
})
}
})
res.status(200).json(response)
})
You can use sequelize.literal. Using Ingredient alias of Recipe, you can write as follows. I do not know if this is the right way. :)
[sequelize.literal('`TheAlias->RecipeIngredient`.amount'), 'amount'],
I tested with sqlite3. Received result with alias "ir" is
{ id: 1,
name: 'Carrots',
created_at: 2018-03-18T04:00:54.478Z,
updated_at: 2018-03-18T04:00:54.478Z,
ir: [ { amount: 10, RecipeIngredient: [Object] } ] }
See the full code here.
https://github.com/eseom/sequelize-cookbook
I've gone over the documentation but I couldn't find anything that seems like it would let me merge the attributes of the join-table into the result so it looks like I'm stuck with doing something like this:
router.get('/recipes', function(req, res) {
Recipe.findAll({
include: [{
model: Ingredient,
as: 'ingredients',
through: {
attributes: ['amount']
}
}]
}).then(function(recipes) {
return recipes[0].toJSON();
}).then(function(recipe) {
recipe.ingredients = recipe.ingredients.map(function(i) {
i.amount = i.RecipeIngredient.amount;
delete i.RecipeIngredient;
return i;
});
return recipe;
}).then(function(recipe) {
return res.status(200).json(recipe);
});
});
Passing through to include lets me filter out which attributes I want to include from the join-table but for the life of me I could not find a way to make sequelize merge it for me.
The above code will return the output I wanted but with the added overhead of looping over the list of ingredients which is not quite what I wanted but unless someone comes up with a better solution I can't see another way of doing this.

Appending an entry in one to many embedded document mongodb

I am new to MongoDB and I came across the following use case:
Lets say I have my mongodb document like this:
{
_id: "joe",
name: "Joe Bookreader",
numbers: [
{
mobile: 741134217,
},
{
home: 123452411
}
]
}
Now I need to two perform two operations:
Add a new number {office:112342282}
Delete users home number
I believe that we can do this in Mongo, but I am not getting the syntax anywhere, neither I could find it in the MongoDB documentation.
P.S. I am doing this using Monk Library, monk specific syntax would be of great help. But otherwise also it would help me!
What you want is Mongo's $pull and $push operator
You should be able to do it by doing the following:
db.User.update({_id: 'joe'}, {$push: {numbers: {office: 112342282}}, $pull: {numbers: {home: 123452411}}});
Unfortunately, Mongo doesn't let you operate on the same field with both the $push and $pull operators at the same time (see this issue). So it really needs to be:
db.User.update({_id: 'joe'}, { $push: { numbers: { office: 112342282}}})
db.User.update({_id: 'joe'}, { $pull: { numbers: {home: 123452411}}})
Using monk's style:
var users = db.get('users');
users.update({_id: 'joe'}, { $push: { numbers: { office: 112342282}}})
users.update({_id: 'joe'}, { $pull: { numbers: {home: 123452411}}})

Categories