$project in $lookup mongodb - mongodb

I have a query, that use $lookup to "join" two models, after this i use $project to select olny the fields that i need, but my $project brings an arrray of objects (user_detail) that contains more data that i need. I want only two fields (scheduleStart and scheduleEnd) of my result.
My query:
User.aggregate([{
$match: {
storeKey: req.body.store,
}
},
{
$group: {
_id: {
id: "$_id",
name: "$name",
cpf: "$cpf",
phone: "$phone",
email: "$email",
birthday: "$birthday",
lastName: "$lastname"
},
totalServices: {
$sum: "$services"
},
}
},
{
$lookup: {
from: "schedules",
localField: "_id.phone",
foreignField: "customer.phone",
as: "user_detail"
}
},
{
$project: {
_id: 1,
name: 1,
name: 1,
cpf: 1,
phone: 1,
email: 1,
birthday: 1,
totalServices: 1,
totalValue: { $sum : "$user_detail.value" },
count: {
$sum: 1
},
user_detail: 1
}
},
Result of query:
count: 1
totalServices: 0
totalValue: 73
user_detail: Array(2)
0:
...
paymentMethod: 0
paymentValue: "0"
scheduleDate: "2018-10-02"
scheduleEnd: "2018-10-02 08:40"
scheduleStart: "2018-10-02 08:20"
status: 3
store: "5b16cceb56a44e2f6cd0324b"
updated: "2018-11-27T13:30:21.116Z"
1:
...
paymentMethod: 0
paymentValue: "0"
scheduleDate: "2018-11-27"
scheduleEnd: "2018-11-27 00:13"
scheduleStart: "2018-11-27 00:03"
status: 2
store: "5b16cceb56a44e2f6cd0324b"
updated: "2018-11-27T19:33:39.498Z"
_id:
birthday: "1992-03-06"
email: "csantosgrossi#gmail.com"
id: "5bfed8bd70de7a383855f09e"
name: "Chris Santos G"
phone: "11969109995"
...
Result that i need:
count: 1
totalServices: 0
totalValue: 73
user_detail: Array(2)
0:
scheduleEnd: "2018-10-02 08:40"
scheduleStart: "2018-10-02 08:20"
1:
scheduleEnd: "2018-11-27 00:13"
scheduleStart: "2018-11-27 00:03"
_id:
birthday: "1992-03-06"
email: "csantosgrossi#gmail.com"
id: "5bfed8bd70de7a383855f09e"
name: "Chris Santos G"
phone: "11969109995"
...
How can i do this with my query?

You can use $lookup 3.6 syntax to $project the fields inside the $lookup pipeline
User.aggregate([
{ "$lookup": {
"from": "schedules",
"let": { "id": "$_id.phone" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$customer.phone", "$$id"] }}},
{ "$project": { "scheduleStart": 1, "scheduleEnd": 1 }}
],
"as": "user_detail"
}}
])

For version of mongo version > 3.6 this query should work for you:
User.aggregate([{
$match: {
storeKey: req.body.store,
}
},
{
$group: {
_id: {
id: "$_id",
name: "$name",
cpf: "$cpf",
phone: "$phone",
email: "$email",
birthday: "$birthday",
lastName: "$lastname"
},
totalServices: {
$sum: "$services"
},
}
},
{
$lookup: {
from: "schedules",
localField: "_id.phone",
foreignField: "customer.phone",
as: "user_detail"
}
},
{
$project: {
_id: 1,
name: 1,
name: 1,
cpf: 1,
phone: 1,
email: 1,
birthday: 1,
totalServices: 1,
totalValue: { $sum : "$user_detail.value" },
count: {
$sum: 1
},
user_detail: {
scheduleEnd: 1,
scheduleStart: 1,
}
}
},

Related

mongodb - Merge object arrays based on key

In a mongodb database, I have the following data:
// db.people
[
{
_id: ObjectId("..."),
id: 111111111,
name: "George",
relatedPeople: [{ id: 222222222, relation: "child" }],
// A bunch of other data I don't care about
},
{
_id: ObjectId("..."),
id: 222222222,
name: "Jacob",
relatedPeople: [{ id: 111111111, relation: "father" }],
// A bunch of other data I don't care about
},
{
_id: ObjectId("..."),
id: 333333333,
name: "some guy",
relatedPeople: [],
// A bunch of other data I don't care about
},
]
I would like to query the people, and select only the fields I've shown, but have extra data in relatedPeople (id + relation + name)
So the desired output would be:
[
{
_id: ObjectId("..."),
id: 111111111,
name: "George",
relatedPeople: [{ id: 222222222, relation: "child", name: "Jacob" }],
},
{
_id: ObjectId("..."),
id: 222222222,
name: "Jacob",
relatedPeople: [{ id: 111111111, relation: "father", name: "George" }],
},
{
_id: ObjectId("..."),
id: 333333333,
name: "some guy",
relatedPeople: [],
},
]
I can get something close, with this query:
db.people.aggregate([
// { $match: { /** ... */ }, },
{
$lookup: {
from: "people",
let: { relatedPeopleIds: "$relatedPeople.id" },
pipeline: [
{ $match: { $expr: { $in: ["$id", "$$relatedPeopleIds"] } } },
{
$project: {
id: 1,
name: 1,
},
},
],
as: "relatedPeople2",
},
},
{
$project: {
id: 1,
name: 1,
relatedPeople: 1,
relatedPeople2: 1,
}
}
]);
But the data is split between two fields. I want to merge each object in the arrays by their id, and place the result array in relatedPeople
I found this question, but that merge is done over a range and uses $arrayElementAt which I can't use
I also tried looking at this question, but I couldn't get the answer to work (Kept getting empty results)
You can add one step using $arrayElementAt with $indexOfArray:
db.people.aggregate([
// { $match: { /** ... */ }, },
{$project: {id: 1, name: 1, relatedPeople: 1}},
{$lookup: {
from: "people",
let: { relatedPeopleIds: "$relatedPeople.id" },
pipeline: [
{ $match: { $expr: { $in: ["$id", "$$relatedPeopleIds"] } } },
{
$project: {
id: 1,
name: 1,
},
},
],
as: "relatedPeople2",
},
},
{$set: {
relatedPeople: {$map: {
input: "$relatedPeople",
in: {$mergeObjects: [
"$$this",
{$arrayElemAt: [
"$relatedPeople2",
{$indexOfArray: ["$relatedPeople2.id", "$$this.id"]}
]}
]}
}}
}},
{$unset: "relatedPeople2"}
])
See how it works on the playground example

Nodejs MongoDb add column to query that is a count of specific records

Considering the following document structure:
{_id: 1, name: 'joe', snapshot: null, age: 30}
{_id: 2, name: 'joe', snapshot: 'snapshot1', age: 30}
{_id: 3, name: 'joe', snapshot: 'snapshot15', age: 30}
{_id: 4, name: 'joe', snapshot: 'snapshot23', age: 30}
How would I perform a query that groups on the name field and adds an additional field that is a count of the remaining records containing subtree: 'additionalinfo'. It would look like this:
{_id: 1, name: 'joe', snapcount: 3, age: 30}
I've been able to group using aggregations but I can't quite get it like this.
My own solution:
I ultimately restructured my data to look like this instead:
{
_id: 1,
name: 'joe',
snapshots: [
{name: 'snap17', id: 1},
{name: 'snap15', id: 2},
{name: 'snap14', id: 3}
],
age: 30
}
This allows me to just check snapshots.length to solve my original problem. However; the answers in this post where very helpful and answered the original question.
Adding another aggregation query to do it: playground link: try it
db.collection.aggregate([
{
$match: {
"snapshot": {
$exists: true,
$ne: null
}
}
},
{
$group: {
_id: "$name",
snapcount: {
$sum: 1
},
age: {
"$first": "$age"
},
name: {
"$first": "$name"
}
}
},
{
"$unset": "_id"
}
])
Based on the comments, the query worked for OP:
db.collection.aggregate([
{
$match: {
"snapshot": {
$exists: true,
$ne: null
}
}
},
{
$group: {
_id: "$name",
snapcount: {
$sum: 1
},
age: {
"$first": "$age"
},
name: {
"$first": "$name"
},
id: {
"$first": "$_id"
}
}
},
{
"$unset": "_id"
}
])
Here's one way you could do it.
db.collection.aggregate([
{
"$group": {
"_id": "$name",
"name": {"$first": "$name"},
"age": {"$first": "$age"},
"snapcount": {
"$sum": {
"$cond": [
{"$eq": [{"$type": "$snapshot"}, "string"]},
1,
0
]
}
}
}
},
{"$unset": "_id"}
])
Try it on mongoplayground.net.

mongo nested aggregation with join

I've following tenant collection:
{id: 1, name: "T1", type: "DEFAULT", state: "ACTIVE"},
{id: 2, name: "T2", type: "DEFAULT", state: "DISABLED"},
{id: 3, name: "T3", type: "STANDARD", state: "ACTIVE"},
{id: 4, name: "T4", type: "TRIAL", state: "DELETED"},
{id: 5, name: "T5", type: "DEFAULT", state: "DISABLED"}
and then second collection with options:
{id:1, tenantId: 1, opt: "OPERATING"},
{id:2, tenantId: 2, opt: "OPERATING"},
{id:3, tenantId: 3, opt: "POSTPONED"},
{id:4, tenantId: 4, opt: "DELETED"},
{id:5, tenantId: 5, opt: "POSTPONED"}
Id' like to aggregate this collections to get umber of tenant types grouped with number of operations, but I'd like to remove all DELETED tenants and all DELETED options from search. Something like this:
{type: "DEFAULT", count: 3, opts: {operating: 2, postponed: 1}}
{type: "STANDARD", count: 1, opts: {postponed: 1}}
Grouping the tenants is fine, but I don't know what should I use for that next grouping of options.
db.tenant.aggregate([
{$match: { state: {$ne: "DELETED"}}},
{$lookup: {
from: "option",
localField: "_id",
foreignField: "tenantId",
as: "options"
}},
{$group {
_id: "$type",
count: {$sum: 1}
}}
])
$group by type and get group of ids
$lookup with pipeline match $in condition for tenantId
$group by opt and get count of option
$project to show fields in k and v format
$project to show required fields, $size to count total tenant and $arrayToObject convert opts array to object
db.tenant.aggregate([
{ $match: { state: { $ne: "DELETED" } } },
{
$group: {
_id: "$type",
ids: { $push: "$id" }
}
},
{
$lookup: {
from: "options",
let: { ids: "$ids" },
pipeline: [
{ $match: { opt: { $ne: "DELETED" }, $expr: { $in: ["$tenantId", "$$ids"] } } },
{
$group: {
_id: "$opt",
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
k: "$_id",
v: "$count"
}
}
],
as: "opts"
}
},
{
$project: {
_id: 0,
type: "$_id",
count: { $size: "$ids" },
opts: { $arrayToObject: "$opts" }
}
}
])
Playground

Mongoose: $sum in $project return only zero

I have a query using $lookup that "join" two models and $project to select all fields that i need only, and in that $project I need to $sum a value called totalValue but only return zero:
My query
User.aggregate([{
$match: {
storeKey: req.body.store,
}
},
{
$group: {
_id: {
id: "$_id",
name: "$name",
cpf: "$cpf",
phone: "$phone",
email: "$email",
birthday: "$birthday",
lastName: "$lastname"
},
totalServices: {
$sum: "$services"
},
}
},
{
$lookup: {
from: "schedules",
"let": { "id": "$_id.phone" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$customer.phone", "$$id"] }}},
{ "$project": { "scheduleStart": 1, "scheduleEnd": 1 }}
],
"as": "user_detail"
}
},
{
$project: {
_id: 1,
name: 1,
name: 1,
cpf: 1,
phone: 1,
email: 1,
birthday: 1,
totalServices: 1,
totalValue: { $sum : "$user_detail.value" }, // here only return zero
count: {
$sum: 1
},
user_detail: 1
}
},
You need to $project your value field in the user_details projection to get it in the next aggregation stage
{ "$project": { "scheduleStart": 1, "scheduleEnd": 1, "value": 1 }}

MongoDB - Help needed to make some aggregation

I am having a bad time trying to do an aggregation in MongoDB.
I need to cross some infos from each user and as a final result I want a list of users (where there is only one object for each user) and for each object there is some lists with distinct information.
1 - The createdAtList array must be ordered from the oldest to the newest date. The sumOfTotal means the current position total summed up with the previous sumOfTotal (Exemplified in the code below), not just the sum of the total's
2 - The categotyList must be ordered like: category1, category2, category3 ...
3 - The desired final result must be ordered like: user1, user2, user3 ...
Basically I need some help to do the following:
//List of docs from my collection:
[
{
_id: "doc1",
user: "user1",
category: "category1",
createdAt: "2018-01-01T00:00:00.000Z"
},
{
_id: "doc2",
user: "user1",
category: "category2",
createdAt: "2017-12-12T00:00:00.000Z",
},
{
_id: "doc3",
user: "user1",
category: "category1",
createdAt: "2017-12-12T00:00:00.000Z",
},
{
_id: "doc4",
user: "user1",
category: "category2",
createdAt: "2018-01-01T00:00:00.000Z"
},
{
_id: "doc5",
user: "user1",
category: "category3",
createdAt: "2017-11-11T00:00:00.000Z"
}
]
//Desired result:
{
user: "user1",
createdAtList: [ //list ordered by createdAt
{
createdAt: "2017-11-11T00:00:00.000Z",
total: 1,
sumOfTotal: 0
}
{
createdAt: "2017-12-12T00:00:00.000Z",
total: 2,
sumOfTotal: 3 //summed up with the previous
}
{
createdAt: "2018-01-01T00:00:00.000Z",
total: 2,
sumOfTotal: 5 //summed up with the previous
}
],
categotyList: [ //list ordered by category
{
category: "category1",
total: 2
},
{
category: "category2",
total: 2
},
{
category: "category3",
total: 1
}
]
},
...
Is possible to do this in the same aggregate?
I do not think it really makes sense to have the createdAtList.sumOfTotal field. I do not think the fields in an array should be dependent upon a particular order of the elements. If you want some field to contain the sum of the createdAtList.total field, I think there should only be one field (outside of the array). That being said, here is the query I came up with to give you the desired results (using "users" as the name of the collection):
db.users.aggregate([
{
$group: {
_id: {
user: "$user",
createdAt: "$createdAt"
},
total: { $sum: 1 },
category: { $push: "$category" }
}
},
{
$project: {
_id: 0,
user: "$_id.user",
createdAt: "$_id.createdAt",
total: "$total",
category: 1
}
},
{ $unwind: "$category" },
{
$group: {
_id: {
user: "$user",
category: "$category"
},
catTotal: { $sum: 1 },
createdAtList: {
$push: {
createdAt: "$createdAt",
total: "$total"
}
}
}
},
{
$project: {
_id: 0,
user: "$_id.user",
createdAtList: 1,
category: "$_id.category",
catTotal: 1
}
},
{ $unwind: "$createdAtList" },
{
$group: {
_id: "$user",
createdAtList: {
$addToSet: "$createdAtList"
},
categoryList: {
$addToSet: {
category: "$category",
total: "$catTotal"
}
}
}
},
{ $unwind: "$createdAtList" },
{ $sort: { "createdAtList.createdAt": 1 } },
{
$group: {
_id: "$_id",
createdAtList: {
$push: "$createdAtList"
},
categoryList: {
$first: "$categoryList"
}
}
},
{ $unwind: "$categoryList" },
{ $sort: { "categoryList.category": 1 } },
{
$group: {
_id: "$_id",
createdAtList: {
$first: "$createdAtList"
},
categoryList: {
$push: "$categoryList"
}
}
},
{
$project: {
_id: 0,
user: "$_id",
createdAtList: 1,
sumOfTotal: { $sum: "$createdAtList.total" },
categoryList: 1
}
},
{ $sort: { user: 1 } },
]).pretty()