Here's my problem: I am trying to query subdocument bulk by it's property name. I can't find any operator to do that.
I have tried using $match like this - $match: { 'bulk.name': 'xyz' } but it doesn't work.
const query = [
{
$match: { user },
},
{
$group: {
_id: { bulk: '$bulk', status: '$status' },
count: { $sum: 1 },
},
},
{
$group: {
_id: '$_id.bulk',
status: { $addToSet: { status: '$_id.status', count: '$count' } },
},
},
{
$lookup: {
from: 'bulks',
localField: '_id',
foreignField: '_id',
as: 'bulk',
},
},
{
$replaceRoot: { newRoot: { $mergeObjects: [{ $arrayElemAt: ['$bulk', 0] }, '$$ROOT'] } },
},
{ $project: { bulk: 0 } },
]
sample data
items:
[{
_id: 6040 d71f40f98b3e3cc518c9,
type: 'Gateway',
name: 'Lite Bulk Group Ultra FE Dupe__3/4/2021, 12:36:38 PM',
description: '',
message: '{{name}} {{Message-optional}} {{another-one}} {{another-two}}',
group: 6017 b457f5105418b8099ab6,
user: 5 fc871135fd04f562aed3527,
schedule: 6040 d71f40f98b3e3cc518c2,
createdAt: 2021 - 03 - 04 T12: 48: 31.661 Z,
updatedAt: 2021 - 03 - 04 T12: 48: 31.661 Z,
__v: 0,
status: [Array]
}]
Related
I have a section of my aggregate query that I cannot get to work for the life of me. I am running 6.0. The section of the query with the issue looks like this:
$project: {
_id: 0,
games: {
$sortArray: {
input: '$games',
sortBy: { date: -1 }
}
},
total: { $size: '$games' }
}
For some reason the $sortArray function is not working for me in that I am getting no output from the query at all. The query, however, will work if I remove this sort like this:
$project: {
_id: 0,
games: 1,
total: { $size: '$games' }
}
After studying the $sortArray documentation, I believe that I am implementing this into the pipeline correctly. Can anyone identify what the issue is? Here is the full pipeline for context:
const pipeline = [
{
$match: { _id: ObjectID( user_id ) }
},
{
$lookup: {
from: 'game',
localField: '_id',
foreignField: 'player_id',
pipeline: pipelineFilters,
as: 'owned_games'
}
},
{
$lookup: {
from: 'viewers',
pipeline: [
{ $match: { email: user_email } },
{
$lookup: {
from: 'games',
localField: 'game_id',
foreignField: '_id',
pipeline: pipelineFilters,
as: 'games'
}
},
{
$project: {
game: { $arrayElemAt: [ '$games', 0 ] }
}
},
{
$replaceRoot: {
newRoot: '$game'
}
}
],
as: 'viewing_games'
}
},
{
$project: {
games: {
$concatArrays: [ '$viewing_games', '$owned_games' ]
}
}
},
{
$project: {
_id: 0,
games: {
$sortArray: {
input: '$games',
sortBy: { date: -1 }
}
},
total: { $size: '$games' }
}
}
];
and example of the document structure right before the final $project:
{
_id: new ObjectId("6359ac2149c98388770fb2b3"),
games: [
{
_id: new ObjectId("63595544435af1b923d1bda1"),
name: 'game 1',
owner_id: new ObjectId("63595544435af1b923d1bd98"),
date: 2022-10-26T15:41:56.584Z,
status: 'draft',
createdAt: 2022-10-26T15:41:56.599Z,
updatedAt: 2022-10-26T15:41:56.599Z,
__v: 0
},
{
_id: new ObjectId("63595544435af1b923d1bd99"),
name: 'game 2',
owner_id: new ObjectId("63595544435af1b923d1bd8b"),
date: 2011-10-05T14:48:00.000Z,
status: 'draft',
createdAt: 2022-10-26T15:41:56.585Z,
updatedAt: 2022-10-26T15:41:56.585Z,
__v: 0
},
{
_id: new ObjectId("63595544435af1b923d1bd9b"),
name: 'game 3',
owner_id: new ObjectId("63595544435af1b923d1bd8b"),
date: 1990-01-01T01:22:00.000Z,
status: 'draft',
createdAt: 2022-10-26T15:41:56.588Z,
updatedAt: 2022-10-26T15:41:56.588Z,
__v: 0
},
{
_id: new ObjectId("63595544435af1b923d1bd9d"),
name: 'game 4',
owner_id: new ObjectId("63595544435af1b923d1bd8b"),
date: 2500-10-05T14:48:00.000Z,
status: 'draft',
createdAt: 2022-10-26T15:41:56.592Z,
updatedAt: 2022-10-26T15:41:56.592Z,
__v: 0
},
{
_id: new ObjectId("63595544435af1b923d1bd9f"),
name: 'game 5',
owner_id: new ObjectId("63595544435af1b923d1bd8b"),
date: 1995-12-25T01:22:00.000Z,
status: 'draft',
createdAt: 2022-10-26T15:41:56.595Z,
updatedAt: 2022-10-26T15:41:56.595Z,
__v: 0
}
]
}
We were using mongo:latest tag when launching our docker container which doesn't actually pull the latest mongo image...
Conclusion: assumptions are bad
I have a dataset like this:
{
ip: 1.1.1.1,
process: 123,
type: failure,
date: 2021-04-01
},
{
ip: 1.1.1.2,
process: 124,
type: failure,
date: 2021-03-01
},
{
ip: 1.1.1.1,
process: 123,
type: failure,
date: 2021-02-01
},
{
ip: 1.1.1.1,
process: 123,
type: success,
date: 2021-01-01
}
How can I get the consecutive failure count for a given ip address and process? For example, given the dataset above, if I was to check how many times ip 1.1.1.1 has failed for process 123 before the last success I should get 2. However if the success record was the last record then I should get 0.
What I have so far is:
activityLog.find([
{
$match: {
ip: "1.1.1.1",
process: "123"
},
},
{
$sort: {
date: -1,
},
},
{
$limit: 10,
},
{
$project: {
_id: 0,
type: 1,
},
},
]);
This gives me a list of all types - sorted and matched
You can do the followings in an aggregation pipeline:
find the previous success record
do a self-lookup to find the fail records after the previous success record
do the count of the lookup of step 2
db.collection.aggregate([
{
$match: {
ip: "1.1.1.1",
process: "123",
type: "success"
}
},
{
"$sort": {
date: -1
}
},
{
$limit: 1
},
{
"$lookup": {
"from": "collection",
let: {
ip: "$ip",
process: "$process",
date: "$date"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$ip",
"$$ip"
]
},
{
$eq: [
"$process",
"$$process"
]
},
{
$lt: [
"$date",
"$$date"
]
},
{
$eq: [
"$type",
"success"
]
}
]
}
}
},
{
$sort: {
date: -1
}
},
{
$limit: 1
}
],
"as": "lastSuccess"
}
},
{
"$unwind": {
path: "$lastSuccess",
preserveNullAndEmptyArrays: true
}
},
{
"$lookup": {
"from": "collection",
let: {
ip: "$ip",
process: "$process",
date: {
$ifNull: [
"$lastSuccess.date",
ISODate("9999-12-31")
]
}
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$ip",
"$$ip"
]
},
{
$eq: [
"$process",
"$$process"
]
},
{
$gte: [
"$date",
"$$date"
]
},
{
$eq: [
"$type",
"failure"
]
}
]
}
}
}
],
"as": "previousFailures"
}
},
{
"$addFields": {
"lastFailuresCount": {
$size: "$previousFailures"
}
}
}
])
Here is the Mongo playground for your reference
I'm trying to aggregate results but I need to include only 1 result per date/day. Here's how my query currently looks:
.aggregate([
{
$lookup: {
from: 'community_stats',
as: 'stats',
let: { id: '$_id' },
pipeline: [
{
$match: {
$expr: { $eq: ['$community', '$$id'] },
},
},
{ $sort: { date: -1 } },
{ $limit: 5 },
],
},
},
])
Which returns an array like this:
stats: [
{
_id: new ObjectId("613d42f20e8023815b8f0ad3"),
community: new ObjectId("6138ef0a00895c22c6cd3b68"),
data: [Object],
date: 2021-09-11T23:59:46.998Z
},
{
_id: new ObjectId("613b784e564ee6ad3e2dbb14"),
community: new ObjectId("6138ef0a00895c22c6cd3b68"),
data: [Object],
date: 2021-09-10T15:22:54.764Z
},
{
_id: new ObjectId("6139f9487e4c964ef9dafd11"),
community: new ObjectId("6138ef0a00895c22c6cd3b68"),
data: [Object],
date: 2021-09-09T12:08:40.198Z
},
{
_id: new ObjectId("6139f6eac570e66aa60a8b5f"),
community: new ObjectId("6138ef0a00895c22c6cd3b68"),
data: [Object],
date: 2021-09-09T11:58:34.463Z
}
]
Does anybody know how this can be done?
You just want to add a $group stage where you group the matches by date, like so:
db.collection.aggregate([
{
$lookup: {
from: "community_stats",
as: "stats",
let: {
id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$community",
"$$id"
]
}
}
},
{
$group: {
_id: {
year: {
"$year": "$date"
},
month: {
"$month": "$date"
},
day: {
"$dayOfMonth": "$date"
},
},
first: {
$first: "$$ROOT"
}
}
},
{
"$replaceRoot": {
"newRoot": "$first"
}
},
{
$sort: {
date: -1
}
},
{
$limit: 5
}
]
}
}
])
Mongo Playground
In my example is used $first to select the first document from each day, you can however use different operators / logic if it needs to be changed.
I want to aggregate over a collection where a type is given. The types come from query string and the can be day, month or year. Depending on what type the users chooses I want to group by.
For example: If the user chooses "month" I want to group by month.
Event.aggregate([
{
$lookup: { from: Product.collection.name, localField: 'product', foreignField: '_id', as: 'product' }
},
{
$group: {
_id: { $month: { date: "$date" } },
price: { $sum: "$price" },
result: { $mergeObjects: { name: "$product.name", _id: "$product._id" } },
count: { $sum: 1 }
},
},
]).then(response => {
console.log(response)
res.send(response)
})
I can not figure it out how to find a clean solution.
So far the only way I found was to use if conditional before Model.aggregate([])...
if (req.query.dateAvailability && req.query.dateAvailability === 'month') {
Event.aggregate([
{
$lookup: { from: Product.collection.name, localField: 'product', foreignField: '_id', as: 'product' }
},
{
$group: {
_id: { $month: { date: "$date" } },
price: { $sum: "$price" },
result: { $mergeObjects: { name: "$product.name", _id: "$product._id" } },
count: { $sum: 1 }
},
},
]).then(response => {
console.log(response)
res.send(response)
})
} else if (req.query.dateAvailability && req.query.dateAvailability === 'day') {
Event.aggregate([
{
$lookup: { from: Product.collection.name, localField: 'product', foreignField: '_id', as: 'product' }
},
{
$group: {
_id: { $dateToString: { format: "%d-%m-%Y", date: "$date" } },
price: { $sum: "$price" },
result: { $mergeObjects: { name: "$product.name", _id: "$product._id" } },
count: { $sum: 1 }
},
},
]).then(response => {
console.log(response)
res.send(response)
})
} else if (req.query.dateAvailability && req.query.dateAvailability === 'year') {
Event.aggregate([
{
$lookup: { from: Product.collection.name, localField: 'product', foreignField: '_id', as: 'product' }
},
{
$group: {
_id: { $year: { date: "$date" } },
price: { $sum: "$price" },
result: { $mergeObjects: { name: "$product.name", _id: "$product._id" } },
count: { $sum: 1 }
},
},
]).then(response => {
console.log(response)
res.send(response)
})
}
Model Event:
const EventSchema = new Schema({
client: {
type: [{
type: Schema.Types.ObjectId,
ref: 'Client'
}]
},
product: {
type: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}]
},
date: {
type: Date,
maxlength: 64,
lowercase: true,
trim: true
},
place: {
type: String,
maxlength: 1200,
minlength: 1,
},
price: {
type: Number
},
comment: {
type: String,
maxlength: 12000,
minlength: 1,
},
status: {
type: Number,
min: 0,
max: 1,
default: 0,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value'
}
},
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
},
{
timestamps: true
},
);
There's no magic solution to remove the use of logic, In cases like this it will always be required.
However we can make the code a little sexier:
let groupCond;
if (req.query.dateAvailability && req.query.dateAvailability === 'month') {
groupCond = { $month: { date: "$date" } };
} else if (req.query.dateAvailability && req.query.dateAvailability === 'day') {
groupCond = { $dateToString: { format: "%d-%m-%Y", date: "$date" } };
} else if (req.query.dateAvailability && req.query.dateAvailability === 'year') {
groupCond = { $year: { date: "$date" } };
}
Event.aggregate([
{
$lookup: { from: Product.collection.name, localField: 'product', foreignField: '_id', as: 'product' }
},
{
$group: {
_id: groupCond,
price: { $sum: "$price" },
result: { $mergeObjects: { name: "$product.name", _id: "$product._id" } },
count: { $sum: 1 }
},
},
]).then(response => {
console.log(response)
res.send(response)
})
There is no magic bullet to your problem the logic has to happen somewhere. Either with an if statement outside the query or a $switch operator inside the query if you are using a version of mongodb 3.4 or greater.
{"$group": {
"_id":{
"$switch": {
"branches": [
{ "case":{ "$eq": [ { "$literal": "day" }, { "$literal": req.query.dateAvailability } ] },
"then": { $dateToString: { format: "%d-%m-%Y", date: "$date" } } },
{ "case":{ "$eq": [ { "$literal": "month" }, { "$literal": req.query.dateAvailability } ] },
"then": { $month: { date: "$date" } } },
{ "case":{ "$eq": [ { "$literal": "year" }, { "$literal": req.query.dateAvailability } ] },
"then": { $year: { date: "$date" } } }
],
"default": { ... default logic for when dateAvailability isn't set ... }
}
}
... rest of the group operation
} }
I'm having troubles with the following. I wonder if it's possible to do it with a single query.
So I have the following model :
const Analytics = new Schema({
createdAt: {
type: Date,
default: Moment(new Date()).format('YYYY-MM-DD')
},
loginTrack: [
{
user_id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users',
}
}
]
}, { collection: 'analytics' });
And the user model :
const UserSchema = new mongoose.Schema(
{
nickname: {
type: String,
required: true,
unique: true
},
instance: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Instances',
default: null
}}, {collection: 'users'});
I want to get the connected users for a specific instance at a specific date.
AnalyticsModel.aggregate([
{
$match: {
createdAt: { "$gte": moment(args.startDate).format('YYYY-MM-DD'), "$lt": moment(args.endDate).format('YYYY-MM-DD')}
}
},
{
"$project": {
users: { $size: "$loginTrack" },
"createdAt": 1,
"_id": 0
}
}, {
"$group": {
"_id": "$createdAt",
"count": { "$sum": "$users" }
}
}
This gets me
[ { _id: '2019-02-11', count: 3 },
{ _id: '2019-02-08', count: 6 },
{ _id: '2019-02-07', count: 19 },
{ _id: '2019-02-06', count: 16 } ]
The results expected will be the same but I want to filter on users that belongs to a specific instance
Is it possible to do it with a single query or I need to do a populate first before the aggregation ?
UPDATE
I did some progress on it, I needed to add a lookup and I think it's ok :
AnalyticsModel.aggregate([
{"$unwind": "$loginTrack"},
{
$lookup:
{
from: 'users',
localField:'loginTrack.user_id',
foreignField: '_id',
as: '_users'
}
},
{
$match: {
createdAt: { "$gte": new Date(args.startDate), "$lt": new Date(args.endDate)}
}
},
{
$project: {
_users: {
$filter: {
input: '$_users',
as: 'item',
cond: {
$and: [
{ $eq: ["$$item.instance", new ObjectId(args.instance_id)] }
]
}
}
},
"createdAt": 1,
"_id": 0
}
},
{
"$group": {
"_id": "$createdAt",
"count": { "$sum": { "$size": "$_users" } }
}
}
Also the dates were in string in the model.
The output is now :
[ { _id: 2019-02-11T00:00:00.000Z, count: 2 } ]