Mongoose aggregate does not work with $or - javascript

I am working on a Chat application. My schema looks like this:
{
from: String,
to: String,
message: String,
attachment: {
name: String,
size: Number,
type: String,
},
unread: Boolean,
sent: Date,
seen: Date,
}
The following code works and returns the latest messages:
Query 1:
ChatDB.aggregate([
{ $match: {
$or: [
{ from, to },
{ from: to, to: from },
],
}},
{ $sort: { sent: -1 }},
{ $limit: messageBatchSize },
{ $sort: { sent: 1 }},
]);
But, when I try to paginate by including a timestamp in the query, it does not work anymore:
Query 2:
ChatDB.aggregate([
{ $match: {
sent: { $lt: new Date(beforeTimestamp) },
$or: [
{ from, to },
{ from: to, to: from },
],
}},
{ $sort: { sent: -1 }},
{ $limit: messageBatchSize },
{ $sort: { sent: 1 }},
]);
If I remove the $or portion and keep only the timestamp check on sent, things work, but (of course) it returns results for all users, which is not what I want:
Query 3:
ChatDB.aggregate([
{ $match: {
sent: { $lt: new Date(beforeTimestamp) },
}},
{ $sort: { sent: -1 }},
{ $limit: messageBatchSize },
{ $sort: { sent: 1 }},
]);
At first I thought it has got to do something with not converting the ids from string to ObjectId and changed my code to use Types.ObjectId accordingly. But that did not help even. I mean, Query 1 works correctly without any conversion.
Any idea what is going on? My mongoose version:
"mongoose": "^5.8.2",
Edit:
I tried running the query in mongo console and it returned the results correctly:
> db.chats.aggregate([
... {
... $match: {
... $or: [
... { from: '5f0319f87278d056876952d5', to: 'org' },
... { to: '5f0319f87278d056876952d5', from: 'org' },
... ],
... sent: { $lt: new Date('2020-07-08T17:05:34.288Z') }
... }
... },
... { $sort: { sent: -1 }},
... { $limit: 20 },
... { $sort: { sent: 1 }}
... ]);

I feel kinda stupid for posting this in the first place.
The problem turned out to be that the values in from and to were of type Types.ObjectId because they were being retrieved from a different collection.
The values stored in ChatDB were strings. Because of this, the query from mongo console worked fine (because I was providing string correctly) and the one with mongoose in the code did not work.
However, I still don't know why Query 1 worked.

Related

Aggregate match doesn't work once I add more than 1 match?

I'm having some trouble with this aggregate function. It works correctly when I only have a single match argument (created_at), however when I add a second one (release_date) it never returns any results, even though it should. I've also tried the matches with the '$and' parameter with no luck.
Here is the code. Anyone know what I'm doing wrong?
Thanks!
db.collection('votes).aggregate([
{
$match: {
$and:
[
{ created_at: { $gte: ISODate("2021-01-28T05:37:58.549Z") }},
{ release_date: { $gte: ISODate("2018-01-28T05:37:58.549Z") }}
]
}
},
{
$group: {
_id: '$title',
countA: { $sum: 1 }
}
},
{
$sort: { countA: -1 }
}
])

Am I using Mongo's $and and $expr incorrectly?

Here is my query:
ctas.updateMany({
$and: [
{$expr: { $lt: ['$schedule.start', () => Date.now()] }},
{$expr: { $gt: ['$schedule.end', () => Date.now()] }}
]
},
{
$set: {isActive: true}
}).then(res => {
const { matchedCount, modifiedCount } = res;
console.log(`Successfully matched ${matchedCount} and modified ${modifiedCount} items.`)
}).catch(e => console.error(e));
I'm absolutely positive that start is less than Date.now() and end is greater than Date.now(), but I'm not getting any matches. Is my syntax wrong?
a snippet of my document in mongo:
schedule: {
start: 1642564718042,
end: 3285129434744
}
Edit: In case it makes a difference, I'm writing this code as a mongo scheduled trigger.
Update: If I replace the second expression with an obviously truth expression, { isActive: false }, it matches all the documents. Obviously Date.now()*2 (what I used to set schedule.end) is greater than Date.now(), so why is that second expression failing?
Missing $. And make sure your field paths are correct. $schedule.start and $schedule.end.
And another concern is that both schedule.start and schedule.end are with Timespan value. So you need to cast them to date via $toDate.
db.collection.update({
$and: [
{
$expr: {
$lt: [
{
$toDate: "$schedule.start"
},
new Date()
]
}
},
{
$expr: {
$gt: [
{
$toDate: "$schedule.end"
},
new Date()
]
}
}
]
},
{
$set: {
isActive: true
}
})
Sample Mongo Playground

Get some elements from an array mongoDB

In MongoDB shell version v4.4.6
the following code works perfectly.
db['pri-msgs'].findOne({tag:'aaa&%qqq'},{msgs:{$slice:-2}})
But in nodeJs mongoDB the following code doesn't work.
db.collection('pri-msgs').findOne({
tag: 'aaa&%qqq'
}, {
msgs: {
slice: -2
}
})
My document-->
{"_id":{"$oid":"60c4730fadf6891850db90f9"},"tag":"aaa&%qqq","msgs":[{"msg":"abc","sender":0,"mID":"ctYAR5FDa","time":1},{"msg":"bcd","sender":0,"mID":"gCjgPf85z","time":2},{"msg":"def","sender":0,"mID":"lAhc4yLr6","time":3},{"msg":"efg","sender":0,"mID":"XcBLC2rGf","time":4,"edited":true},{"msg":"fgh","sender":0,"mID":"9RWVcEOlD","time":5},{"msg":"hij","sender":0,"mID":"TJXVTuWrR","time":6},{"msg":"jkl","sender":0,"mID":"HxUuzwrYN","time":7},{"msg":"klm","sender":0,"mID":"jXEOhARC2","time":8},{"msg":"mno","sender":0,"mID":"B8sVt4kCy","time":9}]}
Actually what I'm trying to do is Get last 2 itmes from msgs Array where time is greater than 'n'. Here 'n' is a number.
You can use aggregation-pipeline to get the results you are looking for. The steps are the following.
Match the documents you want by tag.
Unwind the msgs array.
Sort descending by msgs.time.
Limit first 2 elements.
Match the time you are looking for using a range query.
Group the documents back by _id.
Your query should look something like this:
db['pri-msgs'].aggregate([
{ $match: { tag: 'aaa&%qqq' } },
{ $unwind: '$msgs' },
{
$sort: {
'msgs.time': -1 //DESC
}
},
{ $limit: 2 },
{
$match: {
'msgs.time': {
$gt: 2 //n
}
}
},
{
$group: {
_id: '$_id',
tag: { $first: '$tag' },
msgs: {
$push: { msg: '$msgs.msg', sender: '$msgs.sender', mID: '$msgs.mID', time: '$msgs.time' }
}
}
}
]);

Returning count w/ data in MongoDB Aggregation

I've written a MongoDB aggregation query that uses a number of stages. At the end, I'd like the query to return my data in the following format:
{
data: // Array of the matching documents here
count: // The total count of all the documents, including those that are skipped and limited.
}
I'm going to use the skip and limit features to eventually pare down the results. However, I'd like to know the count of the number of documents returned before I skip and limit them. Presumably, the pipeline stage would have to occur somewhere after the $match stage but before the $skip and $limit stages.
Here's the query I've currently written (it's in an express.js route, which is why I'm using so many variables:
const {
minDate,
maxDate,
filter, // Text to search
filterTarget, // Row to search for text
sortBy, // Row to sort by
sortOrder, // 1 or -1
skip, // rowsPerPage * pageNumber
rowsPerPage, // Limit value
} = req.query;
db[source].aggregate([
{
$match: {
date: {
$gt: minDate, // Filter out by time frame...
$lt: maxDate
}
}
},
{
$match: {
[filterTarget]: searchTerm // Match search query....
}
},
{
$sort: {
[sortBy]: sortOrder // Sort by date...
}
},
{
$skip: skip // Skip the first X number of doucuments...
},
{
$limit: rowsPerPage
},
]);
Thanks for your help!
We can use facet to run parallel pipelines on the data and then merge the output of each pipeline.
The following is the updated query:
db[source].aggregate([
{
$match: {
date: {
$gt: minDate, // Filter out by time frame...
$lt: maxDate
}
}
},
{
$match: {
[filterTarget]: searchTerm // Match search query....
}
},
{
$set: {
[filterTarget]: { $toLower: `$${filterTarget}` } // Necessary to ensure that sort works properly...
}
},
{
$sort: {
[sortBy]: sortOrder // Sort by date...
}
},
{
$facet:{
"data":[
{
$skip: skip
},
{
$limit:rowsPerPage
}
],
"info":[
{
$count:"count"
}
]
}
},
{
$project:{
"_id":0,
"data":1,
"count":{
$let:{
"vars":{
"elem":{
$arrayElemAt:["$info",0]
}
},
"in":{
$trunc:"$$elem.count"
}
}
}
}
}
]).pretty()
I think I figured it out. But if someone knows that this answer is slow, or at least faulty in some way, please let me know!
It's to add a $group stage, passing null as the value, then pushing each document, $$ROOT, into the data array, and for each one, incrementing count by 1 with the $sum operator.
Then, in the next $project stage, I simply remove the _id property, and slice down the array.
db[source].aggregate([
{
$match: {
date: {
$gt: minDate, // Filter out by time frame...
$lt: maxDate
}
}
},
{
$match: {
[filterTarget]: searchTerm // Match search query....
}
},
{
$set: {
[filterTarget]: { $toLower: `$${filterTarget}` } // Necessary to ensure that sort works properly...
}
},
{
$sort: {
[sortBy]: sortOrder // Sort by date...
}
},
{
$group: {
_id: null,
data: { $push: "$$ROOT" }, // Push each document into the data array.
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
count: 1,
data: {
$slice: ["$data", skip, rowsPerPage]
},
}
}
]).pretty()

get the latest document after $group by

I'm using the official MongoDB driver for Node.js.
And this is how my message data is structured. As you can see, every post has a timestamp, an userId and the id of the topic.
[
{
"_id" : ObjectId("5b0abb48b20c1b4b92365145"),
"topicId" : "XN7iqmCFD4jpgJZ6f",
"timestamp" : 1527429960,
"user" : "5b0869636f4e363e300d105a",
"content" : "lorem ipsum"
}
]
Now I need to check if there are topics, which newest post (=highest timestamp) doesn't match my own ID.
With that I do know which topic has a new answer (which is not my own post).
So I started with this:
db.messages.find({
$query: {
user: { $ne: "myUserId" }
},
$orderby: {
timestamp: -1
}
}).limit(1).toArray()
My problem is, that I do not know how to group my query by the topicId. And somehow there seems to be wrong syntax in my attempt.
You have to use aggregate to group by topicId and then $sort for sorting according to time and then $limit to limit the query
db.messages.aggregate([
{ $match: { user: { $ne: "myUserId" } }},
{ $group: {
_id: "$topicId",
timestamp: { $first: "$timestamp"},
user: { $first: "$user" },
content: { $first: "$content" }
}},
{ $sort: { timestamp: -1 } },
{ $limit: 1 }
])
Use aggregate pipeline to do this instead of find.
1) $match $ne :"myUserId"
2) $sort timestamp:-1
3) $group topicId $first
A sample query I have written in the past..
{ "$match": { $and: [ {'latestTimeStamp': { "$gte": new Date('3/11/2018') }}, {'latestTimeStamp': { "$lte": new Date('4/12/2018') }} ]} },
{ $unwind: '$latestSev'},
{ $sort: {'latestSev.sevRating': -1}},
{ $group:{ _id:{_id:'$_id', latestTimeStamp:'$latestTimeStamp', latestLikeli:'$latestLikeli', latestControl:'$latestControl', residualRatingArray:'$residualRatingArray'},
latestMaxImpName: {$first:'$latestSev.impName'} ,
latestMaxSevRating: {$first:'$latestSev.sevRating'}
}
}

Categories

Resources