MongoDB $pull from array with filter - mongodb

I have User collection:
[
{
_id: '5b3935c2d4850aa2d9f0ae25',
feedBucket: [
{
_id: '2a3535c2d4852ba2d9f0ae52',
createdAt: '2018-06-30 21:52:22.681'
},
{
_id: '9f342c2d4852ba2d9f0ae52',
createdAt: '2018-06-30 21:52:22.681'
}
]
},
{
_id: '3d2343c2d4850aa2d9f0ae33',
feedBucket: [
{
_id: '2a3535c2d4852ba2d9f0ae52',
createdAt: '2018-02-30 21:52:22.681'
},
{
_id: '9f342c2d4852ba2d9f0ae52',
createdAt: '2018-06-30 21:52:22.681'
}
]
}
]
And I am trying to pull document with id - '9f342c2d4852ba2d9f0ae52' from users feedBucket.
await User.updateMany(
{},
{ $pull: { feedBucket: { _id: '9f342c2d4852ba2d9f0ae52' } } },
{ multi: true }
)
but it doesn't work, result is { n: 2, nModified: 0, ok: 1 }

If your feedBucket._id is ObjectId then you need to $pull it by converting to mongoose ObjectId
const user = await User.update(
{ },
{ $pull: { feedBucket: { _id: mongoose.Types.ObjectId('5b07fa836569d356ba5bd2fa') } } },
{ multi: true }
)

Related

MongoDB aggregate. Create new groups for non-existing items

My collection of documents contains information about users, their sessions and CRUD operations they performed during these sessions:
{
user_id: '1',
sessions: [
{
actions: [
{
type: 'create',
created_at: ISODate('2020-01-01T00:00:00'),
},
{
type: 'read',
created_at: ISODate('2022-01-01T00:00:00'),
},
{
type: 'read',
created_at: ISODate('2021-01-01T00:00:00'),
}
],
}
]
}
I need to get a summary for each user, which includes the amount of CRUD operations and the date of the last one:
{
user_id: '1',
actions: [
{
type: 'create',
last: ISODate('2020-01-01T00:00:00'),
count: 1,
},
{
type: 'read',
last: ISODate('2022-01-01T00:00:00'),
count: 2,
},
// Problematic part:
{
type: 'update',
last: null,
count: 0,
},
{
type: 'delete',
last: null,
count: 0,
},
]
}
I came up with this solution:
db.users.aggregate([
{$unwind:'$sessions'},
{$unwind:'$sessions.actions'},
{
$group:{
_id:{user_id:'$user_id', type:'$sessions.actions.type'},
last:{$max:'$sessions.actions.created_at'},
count:{$sum:1},
}
},
{
$group:{
_id:{user_id:'$_id.user_id'},
actions:{$push:{type:'$_id.type', last:'$last', count:'$count'}}
}
},
{
$project:{
_id:0,
user_id: '$_id.user_id',
actions: '$actions'
}
}
])
The problem here is that I cannot figure out, how can I add missing actions, like in 'update' and 'delete' in the example above
Try this,
db.collection.aggregate([
{
$unwind: "$sessions"
},
{
$unwind: "$sessions.actions"
},
{
$group: {
_id: {
user_id: "$user_id",
type: "$sessions.actions.type"
},
last: {
$max: "$sessions.actions.created_at"
},
count: {
$sum: 1
},
}
},
{
$group: {
_id: {
user_id: "$_id.user_id"
},
actions: {
$push: {
type: "$_id.type",
last: "$last",
count: "$count"
}
}
}
},
{
$project: {
_id: 0,
user_id: "$_id.user_id",
actions: {
"$function": {
"body": "function(doc) { const ops = {read:0, delete:0, update: 0, create: 0}; const actions = doc.actions; actions.forEach(action => { ops[action.type] = 1 }); Object.keys(ops).filter(key => ops[key] === 0).forEach(key => actions.push({count: 0, last: null, type: key})); return actions }",
"args": [
"$$ROOT"
],
"lang": "js"
}
},
}
},
])
Here, we use $function and provide a small JS function to populate the missing entries.
Playground link.

mongoDB Aggregate please.... list Duplicate Total

code currently in use
db.events.aggregate([
{ '$match':
{ thingId: 'node_0002',
eventState: 'test',
deviceId: {'$in': ['ARCT-1', 'ARCT-2', 'ARSS-1', 'ARSS-2', 'ARSW-1', 'ARCO-1']},
collectTimeText: {'$gte': '2022-04-01T00:00:00+09:00', '$lte': '2022-04-19T23:59:59+09:00' }}},
{ '$group': {
_id: {'$dateFromString': { dateString: { '$substr': [ '$collectTimeText', 0, 10 ] }}
},
list: { '$addToSet': {'deviceId':'$deviceId'} }},
},
{'$unwind':'$list'},
{ '$group': {
_id: {'$dateToString': {'format': "%Y-%m", date: "$_id"}
},
list: { '$push': '$list' }},
},
{'$sort': { _id: 1 } }], {})
result of using the code
{ _id: '2022-04',
list:
[ { deviceId: 'ARCT-2' },
{ deviceId: 'ARSS-1' },
{ deviceId: 'ARCT-1' },
{ deviceId: 'ARSW-1' },
{ deviceId: 'ARCO-1' },
{ deviceId: 'ARSS-2' },
{ deviceId: 'ARCT-2' },
{ deviceId: 'ARSS-1' },
{ deviceId: 'ARCT-1' },
{ deviceId: 'ARSW-1' },
{ deviceId: 'ARCO-1' },
{ deviceId: 'ARSS-2' },
{ deviceId: 'ARCT-2' },
{ deviceId: 'ARSS-1' },
{ deviceId: 'ARCT-1' },
{ deviceId: 'ARSW-1' },
{ deviceId: 'ARCO-1' },
{ deviceId: 'ARSS-2' } ] }
I want an output like the code below. Help
{ _id: '2022-04',
list:
{ 'ARCO-1': 3,
'ARCT-1': 3,
'ARSS-1': 3,
'ARCT-2': 3,
'ARSS-2': 3,
'ARSW-1': 3 } }
This is the code for generating statistics.
How do I get the results I want?
Did I write the code wrong in the first place?
When I added $project , I got the following result. How to check the count
db.events.aggregate([
{ '$match':
{ thingId: 'node_0002',
eventState: 'test',
deviceId: {'$in': ['ARCT-1', 'ARCT-2', 'ARSS-1', 'ARSS-2', 'ARSW-1', 'ARCO-1']},
collectTimeText: {'$gte': '2022-04-01T00:00:00+09:00', '$lte': '2022-04-19T23:59:59+09:00' }}},
{ '$group': {
_id: {'$dateFromString': { dateString: { '$substr': [ '$collectTimeText', 0, 10 ] }}
},
list: { '$addToSet': {'deviceId':'$deviceId'} }},
},
{'$unwind':'$list'},
{ '$group': {
_id: {'$dateToString': {'format': "%Y-%m", date: "$_id"}
},
list: { '$push': '$list' }},
},
{'$project':{
'list':{
'$arrayToObject':{
'$map': {
'input': '$list',
'as': 'el',
'in': {
'k': '$$el.deviceId',
'v': {'$sum':1}
}
}
}
}
}},
{'$sort': { _id: 1 } }], {})
Below is the result of adding $project.
{ _id: '2022-04',
list:
{ 'ARCO-1': 1,
'ARCT-1': 1,
'ARSS-1': 1,
'ARCT-2': 1,
'ARSS-2': 1,
'ARSW-1': 1 } }
I don't know what to do.... It seems like I'm almost there, but I don't know...
Maybe not all...?
$group twice and then $arrayToObject
db.collection.aggregate([
{
$group: {
_id: {
date: "$date",
deviceId: "$deviceId"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id.date",
list: {
$push: {
k: "$_id.deviceId",
v: "$count"
}
}
}
},
{
$set: {
list: {
$arrayToObject: "$list"
}
}
}
])
mongoplayground

Returning unique mongodb documents using distinct not working

Each message document has a messageTrackingId. I want to return all the messages, but exclude documents that have the same messageTrackingId. So for example, if I had 4 documents in my table and 3 of them have the same messageTrackingId value, then the Messages.find() should only return 2 documents.
I'm trying to use distinct to only return the unique documents so I don't get duplicates with the same messageTrackingId. Currently postman is returning no documents.
if I changed
Messages.find({query}).distinct('messageTrackingId')
to
Messages.find(query)
then it returns all the recipientId's documents. but when I add distinct, I get no results.
app.get('/api/messages',(req, res, next)=>{
query = {};
inbox = false;
messageId = false;
if(req.query.recipientId){
query = { recipientId: req.query.recipientId }
inbox = true;
Messages.aggregate(// Pipeline
[
// Stage 1
{
$group: {
_id: "$messageTrackingId",
message : { $addToSet: '$message' },
recipientId : { $addToSet: '$recipientId' },
creator : { $addToSet: '$creator' },
messageTrackingId : { $addToSet: '$messageTrackingId' },
}
},
// Stage 2
{
$project: {
_id: 1,
message: { $arrayElemAt: ["$message", 0 ] },
recipientId: { $arrayElemAt: ["$recipientId", 0 ] },
creator: { $arrayElemAt: ["$creator", 0 ] },
messageTrackingId: { $arrayElemAt: ["$messageTrackingId", 0 ] }
}
}
])
messages model
const mongoose = require("mongoose");
const uniqueValidator = require("mongoose-unique-validator");
const messagingSchema = mongoose.Schema({
creator: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
recipient: { type: String, required: true },
recipientId: { type: String, required: true },
message: { type: String, required: true },
subject: { type: String, required: true },
creationDate: { type: Date, required: true },
messageTrackingId: { type: String }
// readDate: { type: Date, required: true }
});
module.exports = mongoose.model("Messages", messagingSchema);
distinct will return distinct fields which is not what you want.
You will need to use aggregation and group by the messageTrackingId, then project grabbing the first message content etc you want:
Given sample data like:
{ "messageTrackingId" : 1, "message" : "hello" }
{ "messageTrackingId" : 1, "message" : "hello" }
{ "messageTrackingId" : 1, "message" : "bye" }
{ "messageTrackingId" : 2, "message" : "bye" }
{ "messageTrackingId" : 2, "message" : "bye" }
{ "messageTrackingId" : 1, "message" : "hello" }
In MongoDB:
db.getCollection("messages").aggregate(
// Pipeline
[
// Stage 1
{
$group: {
_id: "$messageTrackingId",
message : { $addToSet: '$message' }
}
},
// Stage 2
{
$project: {
_id: 1,
message: { $arrayElemAt: ["$message", 0 ] }
}
},
]);
To use in mongoose, simply using the aggregate function on your model:
Using Mongoose
const result = await Message.aggregate(// Pipeline
[
// Stage 1
{
$group: {
_id: "$messageTrackingId",
message : { $addToSet: '$message' }
}
},
// Stage 2
{
$project: {
_id: 1,
message: { $arrayElemAt: ["$message", 0 ] }
}
},
]);
UPDATE AFTER COMMENTS
If you need to query for a given messageTrackingId, then add $match as the first stage of the pipeline:
const result = await Message.aggregate(
[
{
$match: {
messageTrackingId: {$eq: 2}
}
},
{
$group: {
_id: "$messageTrackingId",
message : { $addToSet: '$message' }
}
},
{
$project: {
_id: 1,
message: { $arrayElemAt: ["$message", 0 ] }
}
},
]);

How to Combine GeoQuery with another separate query in MongoDB and paginate result?

let params = {
$or: [
{//STATUS1 PERSON
status: { '$in': ['Status1'] }, object: { '$in': ['Person'] },
geometry: {
$near: {
$maxDistance: 160000,
$geometry: {
type: "Point",
coordinates: [geoCoordinates[1], geoCoordinates[0]]
}
}
},
name: { '$in': req.user.names },
sessionStartTime: { "$gte": new Date() },
$and: [
{
user_id: '',
},
{
userID: { $exists: false }
}
]
},
{ //STATUS1 ALL OTHER ITEM TYPES
status: { '$in': ['Status1'] }, object: { '$in': ['Animal', 'Planet', 'Star'] },
name: { '$in': req.user.names },
sessionStartTime: { "$gte": new Date() },
$and: [
{
user_id: '',
},
{
userID: { $exists: false }
}
]
},
{ //ALL ITEM TYPES WITH STATUS2 AND STATUS3
status: { '$in': ['Status2', 'Status3'] }, object: { '$in': ['Person', 'Animal', 'Planet', 'Star'] },
user_id: req.user._id
}
]
}
var options = {
page: 1,
limit: 5
};
let foundItems = await User.paginate(params, options).then(function (result) {
});
With this query, I'm getting the following error:
MongoError: geoNear must be top-level expr
How do I include geometry query as one query among others and return the combined result with pagination ? Or is there a different way to achieve the same result?

MongoDB populate items before $pull condition

Users collection:
[
{
_id: '5b3935c2d4850aa2d9f0ae25',
feedBucket: ['5b37d16665c4127e7a088812', '5b3a19c5be4949b0d74476d4']
},
{
_id: '35c2d4850aa2d9f0ae25ab39',
feedBucket: ['5b37d16665c4127e7a088812', '5b3a19c5be4949b0d74476d4']
}
]
Post collection:
[
{
_id: '5b37d16665c4127e7a088812',
createdAt: 2018-07-01 23:12:50.232
},
{
_id: '5b3a19c5be4949b0d74476d4',
createdAt: 2018-05-01 23:12:50.232
},
]
I want to execute following:
const date = new Date()
date.setMonth(date.getMonth() - 1)
await User.updateMany(
{},
{ $pull: { feedBucket: { createdAt: { $lte: date } } } }
)
Is there any way to populate feedBucket items before $pull condition?
Or feedBucket shouldn't be an array of items ids, but should be an array of item documents?
You can try the code below:
const postsToPull = await Post.find({ createdAt: { $lte: date } });
await User.updateMany(
{},
{ $pull: { feedBucket: { $in: postsToPull } }
)
Another option is to store createdAt within feedBicket like
{
_id: ObjectId,
createdAt: Date
}
Then you can query it with
await User.updateMany(
{},
{ $pull: { feedBucket: { createdAt: { $lte: date } } } }
)