I'm coding a complex query in javascript.
and I'm using aggregation.
I'm using two tables. invoice, travel
this is my code.
Invoice.aggregate([
// filter the documents from invoice of only 2016
{
$match: {
executed: {
$gte: startDate,
$lte: endDate
},
"modelHolder.name": 'Travel'
}
},
// $lookup is working alone, it is not taking the input from function 1 of aggregate
{
$lookup: {
from: "travels",
localField: "modelHolder.id",
foreignField: "_id",
as: "dataFromTravels"
}
},
// filter by date of reservation and date of arrival
$match: { $or:
[
{
'dataFromTravels.from.date': {
$gte: departDate, $lte: endDate
}
},
{
'dataFromTravels.to.date': {
$lte: arrivalDate
},
}
]
}
},
{
$limit: 2
}
// 2nd function to work on the first function output as the input
], function (err, result) {
if (err) {
return console.log(('ERROR', err));
//next(err);
} else {
console.log('Result', result);
// res.json(result);
return;
}
});
// console.log('invoice !');
// console.log(invoice._id);
self.resume();
})
.on('error', function (err) {
console.error('Error occurred while streaming invoices', err);
})
.on('end', function () {
console.log('Successfully displayed invoices');
cb();
});
I want to have the list of cars and count how many times we used each of them in the period restricted in the match functions.
I added this.
{
$group: { "_id": "$dataFromTravels.car.plateNumber", "count": { $sum: 1 } }
},
the car is under travel table.
and I just got an array of 3 cars. or I have hundreds of cars in the same period.
How could I manage this?
thanks for you suggestions.
When you make a join query using mongodb. The target collection is stored as an array. so when you make use of lookup
{
$lookup: {
from: "travels",
localField: "modelHolder.id",
foreignField: "_id",
as: "dataFromTravels"
}
}
The output of the query will be like:-
{
"_id": ObjectId("554dc5e937c1482c491d9d36"),
// invoice data
"dataFromTravels": [
// travel data
]
}
And with mongodb aggregation you cannot use $match over nested documents.
So, in the query to match
db.invoice.aggregate([
{
$match: {
executed: {
$gte: ISODate(startDate),
$lte: ISODate(endDate)
},
"modelHolder.name": 'Travel'
}
},
{
$lookup: {
from: "travels",
localField: "modelHolder.id",
foreignField: "_id",
as: "dataFromTravels"
}
}, {
$unwind: "$dataFromTravels"
},{
$match: {
$or: [{
'dataFromTravels.from.date': {
$gte: ISODate(startDate),
$lte: ISODate(endDate)
}
}, {
'dataFromTravels.to.date': {
$lte: ISODate(startDate)
},
}]
}
}, {
$limit: 2
}
]);
Now, the dataFromTravels will be available as an object & you can use $match over the from & to properties
Related
I'm new to MongoDB aggregation. I am not getting desired output
The output I'm getting from aggregation:-
[
{tweet:{key:value}},
{tweet:{key:value}},
{tweet:{key:value}},
{tweet:{key:value}},
]
but I want the following output from the pipeline:-
[
{key:value},
{key:value},
{key:value},
]
and lastly, pipeline I'm running:-
const pipeline = [[
{
$match: {
$expr: {
$in: [
Mongoose.Types.ObjectId(userid), '$likedBy.user'
]
}
}
}, {
$lookup: {
from: 'tweets',
localField: 'tweet',
foreignField: '_id',
as: 'tweet'
}
}, {
$unwind: {
path: '$tweet'
}
}, {
$lookup: {
from: 'users',
localField: 'tweet.user',
foreignField: '_id',
as: 'user'
}
}, {
$unwind: {
path: '$user'
}
}, {
$addFields: {
'tweet.user': '$user'
}
},
{
$addFields: {
'tweet.isLiked': true,
}
},{
$project:{
tweet:1,
}
},
]
];
const likedTweets = await TweetLike.aggregate(pipeline)
I know I can do this with javascript but I want to do it with the pipeline
You can replace your last project stage with the following to achieve what you need:
{$project:{key:"$tweet.key"}}
Answering my own question
i wanted to return sub-document so i found this https://stackoverflow.com/a/43411988/12332711
all i had to do is use
{
$replaceRoot: {newRoot: "$tweet"}
}
it worked for me
I'm using such aggregation to sort all products by deep nested field ObjectId.
At first I populate catalogProduct field.
Then populate category inside catalogProduct.
Sort all data by category Id (return product if ids arr includes category._id)
Sort in reverse order, returns page and limit by 8 for pagination.
Then getting total count of all sorted products without paginatin and limit.
const sortedProducts = await StorageModel.aggregate([
// Unite products arr and totalCount of sorted products
{$facet: {
"sortedProducts": [
// populate catalogProduct ref by Id
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct" },
// populate category ref by Id inside catalogProduct object
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct.category" },
// returns product, if ids arr includes a catalogProduct.category._id
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// sort in reverse order
{ $sort: { _id: -1 } },
// returns only *page
{ $skip: (page - 1) * 8 },
/// limit result by 8
{ $limit: 8 },
],
// total count for pagination, the same operations
"totalCount": [
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
{ $unwind: "$catalogProduct" },
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
{ $unwind: "$catalogProduct.category" },
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// get total count of sorted data, without limit and pagination
{$count : "totalCount"},
]
}},
]);
products = sortedProducts[0].sortedProducts
totalProducts = sortedProducts[0].totalCount.totalCount
I'm getting such data:
[
{ sortedProducts: [ [Object], [Object] ], totalCount: [ [Object] ] }
]
And It's fine. But I think, that aggregation can be simplified, and i don't need to repeat operations to get total count, but I don't know how.
You can observe the starting stages until $match by catalogProduct.category._id is repeated in the 2 $facet. Therefore, you can simply factor them out, then put the afterwards stages into $facet respectively.
Below is my suggested version of your code:
StorageModel.aggregate([
{ $lookup: {
from: "catalogs",
localField: "catalogProduct",
foreignField: "_id",
as: "catalogProduct"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct" },
// populate category ref by Id inside catalogProduct object
{ $lookup: {
from: "categories",
localField: "catalogProduct.category",
foreignField: "_id",
as: "catalogProduct.category"
} },
// deconstruct this Arr, because we get only one Object
{ $unwind: "$catalogProduct.category" },
// returns product, if ids arr includes a catalogProduct.category._id
{ $match: {
"catalogProduct.category._id": { $in: ids }
} },
// Unite products arr and totalCount of sorted products
{$facet: {
"sortedProducts": [
// populate catalogProduct ref by Id
// sort in reverse order
{ $sort: { _id: -1 } },
// returns only *page
{ $skip: (page - 1) * 8 },
/// limit result by 8
{ $limit: 8 },
],
// total count for pagination, the same operations
"totalCount": [
// get total count of sorted data, without limit and pagination
{$count : "totalCount"},
]
}},
]);
I'm using the aggregate framework to query a collection and create an array of active players (up until the last $lookup) after which I'm trying to use $lookup and $pipeline to select all the players from another collection (users) that are not present inside the activeUsers array.
Is there any way of doing this with my current setup?
Game.aggregate[{
$match: {
date: {
$gte: ISODate('2021-04-10T00:00:00.355Z')
},
gameStatus: 'played'
}
}, {
$unwind: {
path: '$players',
preserveNullAndEmptyArrays: false
}
}, {
$group: {
_id: '$players'
}
}, {
$group: {
_id: null,
activeUsers: {
$push: '$_id'
}
}
}, {
$project: {
activeUsers: true,
_id: false
}
}, {
$lookup: {
from: 'users',
'let': {
active: '$activeUsers'
},
pipeline: [{
$match: {
deactivated: false,
// The rest of the query works fine but here I would like to
// select only the elements that *aren't* inside
// the array (instead of the ones that *are* inside)
// but if I use '$nin' here mongoDB throws
// an 'unrecognized' error
$expr: {
$in: [
'$_id',
'$$active'
]
}
}
},
{
$project: {
_id: 1
}
}
],
as: 'users'
}
}]
Thanks
For negative condition use $not before $in operator,
{ $expr: { $not: { $in: ['$_id', '$$active'] } } }
Suppose we have the query :
EightWeekGamePlan.aggregate(
[
{
$group: {
_id: {
LeadId: "$LeadId",
Week: "$Week",
InsertDate: "$InsertDate" , // I want to group by the date part
Status: "$Status"
},
count: { $count: 1 }
}
},
{
$lookup: {
from: "leads",
localField: "_id",
foreignField: "LeadId",
as: "Joined"
}
},
{ $unwind: "$Joined" },
{ $replaceRoot: { newRoot: { $mergeObjects: ["$Joined", "$$ROOT"] } } },
{ $sort: { total: -1 } }
],
function(err, results) {
if (err) {
console.log(err);
}
// ... do some manipulations ...
console.log(_filtered);
return res.json(_filtered);
}
);
I grouping by multiple fields and I want to take only the date part of InsertDate and disregard the time.
How can we do that ?
I believe your question is addressed in mongodb documentations under Group by Day of the Year:
https://docs.mongodb.com/manual/reference/operator/aggregation/group/
You have to convert the date into date-formatted string using $dateToString and add it to $group _id
_id : {$dateToString: { format: "%Y-%m-%d", date: "$InserDate" }}
I hope this helps!
Currently I have this function which retrieves all Tag documents used in all Page documents and counts the number of occurrences and attached them to each Tag document returned:
exports.getAll = (req, res, next) => {
const config = utils.prepareOptions(req.query);
async.waterfall([
(done) => {
Tag.count({}).exec((err, total) => {
return done(err, total);
});
}
], (err, total) => {
Page.aggregate([
// {
// $unwind: "$tags"
// },
{
$group: {
_id: "$tags",
occurrences: {
$sum: 1
}
}
},
{
$lookup: {
from: "tags",
localField: "_id", // this is supposely wrong but I can't prove it
foreignField: "_id",
as: "tagsData"
}
},
{
$unwind: "$tagsData"
},
{
$match: {
"tagsData.label": new RegExp(config.query, 'i')
}
},
{
$project: {
occurrences: "$occurrences",
tagData: '$tagsData'
}
},
{
$addFields: {
"tagData._id": "$_id",
"tagData.occurrences": "$occurrences"
}
},
{
$replaceRoot: {
newRoot: "$tagData"
}
},
{$sort: {[config.sortBy]: config.order}},
{$limit: config.limit},
{$skip: config.offset}
], (err, tags) => {
console.log(tags);
console.log(err);
console.log(total);
if (err) {
return next(err);
}
res.json({
error: null,
data: tags,
total: total,
results: tags.length
});
});
});
};
The problem with this is that a Tag doesn't necessarily have to be used with a Page at any given time. This results in a problem when calling this function because the Tags that aren't used are not returned.
So what I need to do is to also include every Tag that isn't used and set an occurrences: 0 on them.
So if I have 3 Tag documents:
[
{_id: 1203f8sad9sf, label: 'Tag 1'},
{_id: 1203asdf89sf, label: 'Tag 2'},
{_id: 1203asqw89sf, label: 'Tag 3'}
]
And a Page document:
{
_id: 90182312,
tags: [ObjectId(1203f8sad9sf), Object(1203asdf89sf)]
}
And Tag 1 and Tag 2 are part of the Page's tags array, how do I also return Tag 3 so that it is included in the tags that is returned from the aggregate method?
Given I understand your intention correctly and also assuming that you've got some code in place to make sure that there are no duplicate tag IDs in a page's tags field this query can be rewritten in a substantially more efficient and concise way:
db.getCollection('tags').aggregate([{
$lookup: {
from: "page",
localField: "_id",
foreignField: "tags",
as: "pageData"
}
}, {
$addFields: {
"occurrences": {
$size: "$pageData"
}
}
}])