How to use aggregate in mongoosejs with this example - node.js

I am here to ask a question about mongo aggregate function to achieve this example.
Scenario
I have 3 Mongo Schema i.e House, family and educations which as :
House: {
_id: ObjectId,
state: Number,
houseNumber: Number
}
Family: {
_id: ObjectId,
houseId: ObjectId,//ref: house
name: String,
gender: String
}
Education: {
_id: ObjectId,
familyId: ObjectId,//ref: Family
level: String, //might be one of ["primary","secondary","higher_secondary"]
}
Expected Output:
{
state1: {
primary: {
male: 3,
female: 4
},
secondary: {
male: 4,
female: 8
}
},
state2: {
primary: {
male: 5,
female: 4
},
secondary: {
male: 4,
female: 6
}
}
}
I want to group all the education level by gender and then ward.
What I did:
I am newbie in mongo world and recently shifted from sql to no-sql. I had done this:
let edu = await Education.find({level: "primary"}).populate({
path: "family",
match: {gender: "male"},
select: "house",
populate: {
path: "house",
match: {state: 1},
select: "_id"
}
});
let count = (await edu.filter(each => !isEmpty(each.family) && !isEmpty(each.family.house)).length) || 0;
By doing this I get count of male member who has studied primary from state 1. but I cannot afford to call this function one by one for each data.
As requestd the sample data are:
house = [
{
_id: AA1,
state: 1,
houseNumber: 101
},
{
_id: AA2,
state: 1,
houseNumber: 102
},
{
_id: AA3,
state: 2,
houseNumber: 201
}
];
family = [
{
_id: BB1,
houseId: AA1, //ref: house
name: "John",
gender: "male"
},
{
_id: BB2,
houseId: AA1, //ref: house
name: "Solena",
gender: "female"
},
{
_id: BB3,
houseId: AA2, //ref: house
name: "Efrain",
gender: "male"
},
{
_id: BB4,
houseId: AA3, //ref: house
name: "Naruto",
gender: "male"
}
];
education = [
{
_id: CC1,
familyId: AA1, //ref: Family
level: "primary"
},
{
_id: CC2,
familyId: AA2, //ref: Family
level: "secondary"
},
{
_id: CC3,
familyId: AA3, //ref: Family
level: "primary"
},
{
_id: CC4,
familyId: AA4, //ref: Family
level: "secondary"
}
];
P.S expected output is not relevant output to the sample data. And ObjectId has been replaced with some unique reference.
Any lead from here guyz?

You can use below aggregation query in 4.x version.
Query the family collection and join to education collection to get the level followed by join to house collection to get the state.
Once you have all the data group by state, level and gender to count all the matches followed by other groups for formatting result. Last stage to promote the aggregated result into its own document.
Last two groups formatting the results from previous stage into named key value document. First group to format the results into gender and count grouped by state. Second group to format the previously combined gender and count with education key.
Finally replace root stage to format the combined gender, count and education doc with state key.
Also added output after each stage for clarity.
db.family.aggregate(
[
{"$lookup":{
"from":"education",
"localField":"_id",
"foreignField":"familyId",
"as":"education"
}},
{"$unwind":"$education"},
{"$lookup":{
"from":"house",
"localField":"houseId",
"foreignField":"_id",
"as":"state"
}},
{"$unwind":"$state"},
{"$group":{
"_id":{
"state":"$state.state",
"education":"$education.level",
"gender":"$gender"
},
"count":{"$sum":1}
}},//{"_id":{"state" :1,"education" :"secondary","gender":"female"},"count":1}
{"$group":{
"_id":{"state":"$_id.state","education":"$_id.education"},
"gandc":{"$mergeObjects":{"$arrayToObject":[[["$_id.gender","$count"]]]}}
}},//{"_id":{"state":1,"education":"primary"},"gandc":{"male":2}}
{"$group":{
"_id":"$_id.state",
"egandc":{"$mergeObjects":{"$arrayToObject":[[["$_id.education","$gandc"]]]}}
}},//{"_id":1,"egandc":{"primary":{"male":2},"secondary":{"female":1}}}
{"$replaceRoot":{"newRoot":{"$arrayToObject":[[[{"$toString":"$_id"},"$egandc"]]]}}} ])
])
//{"1":{"primary":{"male" : 2 },"secondary":{"female":1}}}

Related

How to query on a Mongodb document and on a reference object at a time

I’m trying to create a filter to manage orders (seller)
orders are composed of "price", "country", "product type",
and seller and customer informations
order: {
_id:""
price: $750
country:"Italy"
customer: objectID
Seller: objectID
productType: "smartphone"
}
customer: {
_id:""
location:"Italy",
paymentType:"paypal"
languages:"it"
}
Seller: {
_id:"278"
location:"UK"
languages:"en"
}
let’s say the seller( _id: 278) wants to find all his orders:
order with a minimum price of $500 and a maximum price of $800
and order in the country "italy" or "belgium"
and order with customers who have paid with "paypal" or "stripe"
and order with customer who speaks English or Italian
So I made this request:
Order.find({ $and:[
seller: 278,
price: {$gte: 500, $lte: 800},
country: { $or:["Italy","belgium"] },
customer: { paymentType: { $or:["paypal","stripe"] },
languages:{ $or:["en","it"] }
]})
and of course I have an error :
" Cast to ObjectId failed for value " paymentType: { $or:["paypal","stripe"]
at path "customer " for model Order"
I don’t know what to put because I’m not targeting any particular customer I don’t need to target client id I don’t understand
Use the $in operator. You don't need $and:
Order.find({
seller: 278,
price: {$gte: 500, $lte: 800},
country: { $in: ["Italy","belgium"] },
"customer.paymentType": { $in: ["paypal","stripe"] },
languages:{ $in: ["en","it"] }
})

TypeORM - Get data from many-to-many relationship

I've got 2 tables (News, Tags) with a relationship like this:
1 news can have many tags
1 tags can belong to many news
This is how I implemented their relationship:
News:
#Entity({ name: "news" })
export class News extends Serializable {
#PrimaryGeneratedColumn('increment')
news_id: number;
#Column({name: "title", type: "varchar", length: 300})
title: string;
#Column({name: "content", type: "varchar", length: 5000})
content: string;
#Column({name: "create_date"})
create_date: Date;
#ManyToMany(() => Tags)
#JoinTable()
tagsnews: Tags[]
}
Tags:
#Entity({ name: "tags" })
export class Tags extends Serializable {
#PrimaryGeneratedColumn('increment')
tag_id: number;
#Column({name: "name", type: "varchar", length: 300})
name: string;
}
Using ManyToMany will create a table 'news_tagsnews_tags' that stores data like this:
Everything's ok but when I want to fetch a news's data and its tags:
dataObject = await getRepository(News).createQueryBuilder("news")
.leftJoinAndSelect("news.tagsnews", "tags")
.where("news.news_id = :newsid", {newsid: params.news_id})
.getRawMany();
The result returns on many rows instead of 1 row, just like this:
RowDataPacket {
news_news_id: 1,
news_title: 'Essence of Paris 1',
news_create_date: 2021-10-08T04:58:31.000Z,
tags_tag_id: 3,
tags_name: 'france',
},
RowDataPacket {
news_news_id: 1,
news_title: 'Essence of Paris 1',
news_create_date: 2021-10-08T04:58:31.000Z,
tags_tag_id: 3,
tags_name: 'travel',
},
RowDataPacket {
news_news_id: 1,
news_title: 'Essence of Paris 1',
news_create_date: 2021-10-08T04:58:31.000Z,
tags_tag_id: 3,
tags_name: 'beautiful',
}
]
What I want is something like this:
RowDataPacket {
news_news_id: 1,
news_title: 'Essence of Paris 1',
news_create_date: 2021-10-08T04:58:31.000Z,
tags_tag_id: 3,
tags: [{
tag_id: 1,
tag_name: france
}, {
tag_id: 2,
tag_name: travel
}, {
tag_id: 3,
tag_name: beautiful
}
]
}
]
I changed to getRawOne() but it doesn't help at all, it returns only 1 first record. Can someone help me on this matter?
Try with getMany() instead of .getRawMany(), it works for me !

How to fetch and group data from mongodb collection where each object have same value in a group

Already reviewed questions and solutions: Question 1 and Question 2 but not getting clear understanding regarding select data and group them.
MongoDB schema and data I have:
Currency Model
[ { currency: 'USD',
status: 1,
user_id: '123',
price: 43.67,
quantity: 22019 },
{ currency: 'USD',
status: 1,
user_id: '234',
price: 43.69,
quantity: 22019,
},
{ currency: 'USD',
status: 1,
user_id: '456',
price: 43.67,
quantity: 8,
}... more 100 data ]
What I want as a result:
Want to merge Quantity value for 1st and 3rd record as it has same price.
The same price containing record quantity get merged.
Is it possible to do with query/ filter/ projection? And how can we Effectively get data and merge based on the same price condition i.e. Total quantity is 22019 + 8 having price 43.67.
You can try something like this:
db.sales.aggregate(
[
{
$group:
{
_id: "$price",
quantity: { $sum: "$quantity" },
count: { $sum: 1 }
}
}
]
)
As solution given by #prasad_ in comment section,
- Got sum of quantity according to price group using aggregation in mongoose:
CurrencyModel.aggregate([
{ $group:
{ _id: "$price",
qty: { $sum: "$quantity" }
}
}
]).then(data => {console.log(data);})

How can I select document with conditional subdocs?

I need to find documents with Mongoose that contains at least one subdocument, respecting the $elemMatch condition.
My main doc sample is like this:
{
desc: 'Sample',
user: 10,
prices: [{ code: 1, price: 10 }, { code: 2, price: 0 }]
}
I need the documents that contains at least one subdoc with price bigger than 0. If doc no have price bigger than 0, the main doc may be discarded.
With $elemMatch, I can filter the subdocs, but not the main doc:
Prod.find({ user: 10}, { prices: { $elemMatch: { price: { $gt: 0 } } } })
With this, I have the document:
[{
desc: 'Sample',
user: 10,
prices: []
}]
When should it be:
[]
How can I do this?

Execute query and grab result in two subsections mongodb

I'm using mongoose to deal with my database.
I have the following model:
var DeviceSchema = mongoose.Schema({
type: Number,
pushId: String
});
The type attribute can be either 0 or 1.
I want to execute a query that grab all documents and retrieve the result in the following format:
{
fstType: [
{
_id: "545d533e2c21b900000ad234",
type: 0,
pushId: "123"
},
{
_id: "545d533e2c21b900000ad235",
type: 0,
pushId: "124"
},
],
sndType: [
{
_id: "545d533e2c21b900000ad236",
type: 1,
pushId: "125"
},
{
_id: "545d533e2c21b900000ad237",
type: 1,
pushId: "126"
},
]
}
Is that possible? I want to do that in one single query.
Thanks.
Is that possible? I want to do that in one single query.
Yes. It is possible. You can achieve the desired result, through the following aggregation pipeline operations.
Sort by the type parameter in ascending order.
Group records together having the same type, construct an array of
documents for each group. After this stage, only two records will be
present, each with an attribute called items, which is an array of
records for each group.
Since our records are sorted by type, the first group will contain
records with type 0, and the second with type 1.
At last we merge the groups and give them each a name, based on their type.
var model = mongoose.model('collection',DeviceSchema);
model.aggregate([
{$sort:{"type":-1}},
{$group:{"_id":"$type",
"items":{$push:"$$ROOT"},
"type":{$first:"$type"}}},
{$project:{"items":{$cond:[{$eq:["$type",0]},
{"firstType":"$items"},
{"secondType":"$items"}]}}},
{$group:{"_id":null,
"firstType":{$first:"$items.firstType"},
"secondType":{$last:"$items.secondType"}}},
{$project:{"_id":0,"firstType":1,"secondType":1}}
], function (err, result) {
if (err) {
console.log(err);
return;
}
console.log(result);
});
o/p:
{ firstType:
[ { _id: '545d533e2c21b900000ad234', type: 0, pushId: '123' },
{ _id: '545d533e2c21b900000ad235', type: 0, pushId: '124' } ],
secondType:
[ { _id: '545d533e2c21b900000ad236', type: 1, pushId: '125' },
{ _id: '545d533e2c21b900000ad237', type: 1, pushId: '126' } ] }

Resources