Mongodb .aggregate not matching $or? - mongodb

Whether a user sends or receives a message, he/she should be able to archive the message for him/herself.
This .aggregate $or, however, only returns docs that the user is the recipient of ('to') that he/she has marked 'archive':'true'. It does accurately filter messages according to the 'value' Booleans.
Models.Messages.aggregate([
// Match documents
{ "$match": {
"$or" : [
{
"to": {
"$elemMatch": {
"username": 'userA',
"view.archive": true,
"view.bin": false
}
}
},
{
"from" : {
"$elemMatch" : {
"username" : 'userA',
"view.archive": true,
"view.bin": false
}
}
}
],
"$or": [
{ 'value.1': true },
{ 'value.2': true },
{ 'value.3': true },
{ 'value.4': false }
]
}},
// Unwind to de-normalize
{ "$unwind": "$to" },
{ "$unwind": "$from" },
// Match the array elements
{ "$match": {
"to.username": 'userA',
"to.view.archive": true,
"from.username": 'userA',
"from.view.archive": true
}},
// Group back all the elements of the original document
{ "$group": {
"_id": "$_id",
"from": { "$push": "$from" },
"to": { "$push": "$to" },
"message": { "$first": "$message" },
"updated": { "$first": "$updated" }
}},
// Sort by updated, most recent first (descending)
{"$sort": {"updated": -1}}
], function (err, docs) {
How could this be altered so that all the fields of both of the following messages are aggregated for userA:
{
"_id" : ObjectId("53e83867f316ea7f22fd3b2b"),
"updated" : ISODate("2014-08-11T03:29:06.000Z"),
"message" : "message1",
"value" : [
"1" : true,
"2" : false,
"3" : false,
"4" : false
]
"to" : [
{
"user" : ObjectId("53e835bd76e0d04318d8cc4e"),
"username" : "userA",
"_id" : ObjectId("53e83867f316ea7f22fd3b2c"),
"view" : {
"inbox" : false,
"outbox" : false,
"archive" : true,
"bin" : false
}
}
],
"from" : [
{
"user" : ObjectId("53e8360276e0d04318d8cc55"),
"username" : "userB",
"_id" : ObjectId("53e83867f316ea7f22fd3b2d"),
"view" : {
"inbox" : false,
"outbox" : true,
"archive" : false,
"bin" : false
}
}
],
"__v" : 5
}
{
"_id" : ObjectId("53e83867f316ea7f22fd3b2b"),
"updated" : ISODate("2014-08-11T03:29:06.000Z"),
"message" : "message2",
"value" : [
"1" : false,
"2" : true,
"3" : false,
"4" : false
]
"to" : [
{
"user" : ObjectId("53e8360276e0d04318d8cc55"),
"username" : "userB",
"_id" : ObjectId("53e83867f316ea7f22fd3b2c"),
"view" : {
"inbox" : true,
"outbox" : false,
"archive" : false,
"bin" : false
}
}
],
"from" : [
{
"user" : ObjectId("53e835bd76e0d04318d8cc4e"),
"username" : "userA",
"_id" : ObjectId("53e83867f316ea7f22fd3b2d"),
"view" : {
"inbox" : false,
"outbox" : true,
"archive" : true,
"bin" : false
}
}
],
"__v" : 5
}

You can't have two $or fields at the top level of your first $match object, so you need to wrap those in an $and array like the following if they both need to be satisfied:
{ "$match": {
$and: [{
"$or" : [
{
"to": {
"$elemMatch": {
"username": 'userA',
"view.archive": true,
"view.bin": false
}
}
},
{
"from" : {
"$elemMatch" : {
"username" : 'userA',
"view.archive": true,
"view.bin": false
}
}
}
]}, {
"$or": [
{ 'value.1': true },
{ 'value.2': true },
{ 'value.3': true },
{ 'value.4': false }
]
}
]
}}
There's a lot going on in your pipeline, so this may not be the only problem.

Related

MongoDB get subdocuments array from multiple documents into own array

I have the following document structure.
{
"_id" : "0026",
"description" : "test",
"name" : "test",
"options" : [
{
"_id" : "002",
"color" : true,
"visible" : true,
"label" : "sample",
},
{
"_id" : "003",
"color" : true,
"visible" : true,
"label" : "sample",
}
],
},
{
"_id" : "0027",
"description" : "test",
"name" : "test",
"options" : [
{
"_id" : "001",
"color" : true,
"visible" : true,
"label" : "sample",
},
{
"_id" : "002",
"color" : true,
"visible" : true,
"label" : "sample",
}
],
},
I am trying to return one array of only the 'options' subdocuments that match an options label regex search.
I think I'm pretty much there but I seem to getting back a separate array of options for each document, how do I return just one array of 'options'?
My current code is:
{
$match: {
options: {
$elemMatch: {
label: { $regex: '^sample', $options: 'i' },
},
},
},
},
{ "$unwind": '$options' },
{
$project: {
_id: 0,
options: 1,
},
},
The structure I would like back is just an array of options:
[
{
"_id" : "001",
"color" : true,
"visible" : true,
"label" : "sample",
},
{
"_id" : "002",
"color" : true,
"visible" : true,
"label" : "sample",
},
{
"_id" : "002",
"color" : true,
"visible" : true,
"label" : "sample",
},
{
"_id" : "003",
"color" : true,
"visible" : true,
"label" : "sample",
}
]
Ta.
Always the way, you post a question then finally get the answer yourself!
Just needed to add:
{ $replaceRoot: { newRoot: '$options' } }
as the last step.
Full answer:
{
$match: {
options: {
$elemMatch: {
label: { $regex: '^sample', $options: 'i' },
},
},
},
},
{ "$unwind": '$options' },
{
$project: {
_id: 0,
options: 1,
},
},
{ $replaceRoot: { newRoot: '$options' } },

MongoDb: Match value on foreign collection

I have the collection USER:
{
"_id" : ObjectId("5d64d2bf48dd17387d77d27a"),
"name" : "John",
"notifications" : {
"email" : false,
"sms" : true
}
},
{
"_id" : ObjectId("5da9586911e192081ee1c6be"),
"name" : "Mary",
"notifications" : {
"email" : false,
"sms" : false
}
}
And this other collection ALERT:
{
"_id" : ObjectId("5d54f04dbe5e6275e53a551e"),
"active" : true,
"user_id" : ObjectId("5d64d2bf48dd17387d77d27a")
},
{
"_id" : ObjectId("5d54f04dbe5e6275e53a551f"),
"active" : false,
"user_id" : ObjectId("5d64d2bf48dd17387d77d27a")
},
{
"_id" : ObjectId("5d54f04dbe5e6275e53a552e"),
"active" : true,
"user_id" : ObjectId("5da9586911e192081ee1c6be")
},
{
"_id" : ObjectId("5d54f04dbe5e6275e53a552f"),
"active" : true,
"user_id" : ObjectId("5da9586911e192081ee1c6be")
}
I want a MongoDB query that lists the documents on collection ALERT that have property "active" as TRUE and whose matching USER has element "sms" on property "notifications" as TRUE too.
You can use Uncorelated sub queries in $lookup
$match to get the "notifications.sms": true
$lookupto join two collections. We are assigning uId = _id from USER collection. Inside the pipeline, we use $match to find the active :true, and _id=uId
here is the script
db.USER.aggregate([
{
"$match": {
"notifications.sms": true
}
},
{
"$lookup": {
"from": "ALERT",
"let": {
uId: "$_id"
},
"pipeline": [
{
$match: {
$and: [
{
active: true
},
{
$expr: {
$eq: [
"$user_id",
"$$uId"
]
}
}
]
}
}
],
"as": "joinAlert"
}
}
])
Working Mongo playground

Updating single object in array in mongoDB

My collection is in below format,
{
"_id" : ObjectId("5d01fddd3f21c407582e578d"),
"device_name" : "Test Alarm Device",
"description" : "test",
"parameters" : [
{
"parameter_name" : "CO2",
"status" : "on"
},
{
"parameter_name" : "NO",
"status" : "on"
},
{
"parameter_name" : "O2",
"status" : "on"
}
}
Now I wish to overwrite(update) complete object where "parameter_name" : "O2".
For example, in new collection it will look like...
{
"parameter_name" : "O2",
"status" : "on",
"is_active": true,
"min": 5
}
Use $out to replace existing collection, $addFields to overwrite existing array and $map with $cond to update elements based on some criteria. To build a new object based on existing one you can use $mergeObjects, try:
db.collection.aggregate([
{
$addFields: {
parameters: {
$map: {
input: "$parameters",
in: {
$cond: [
{ "$eq": [ "$$this.parameter_name", "O2" ] },
{ $mergeObjects: [ "$$this", { "is_active": true, "min": 5 } ] },
"$$this"
]
}
}
}
}
},
{ $out: "collection" }
])
This will do, but I don't think it is the best solution:
db.collection.updateOne({
_id: ObjectId("5d01fddd3f21c407582e578d"),
{ $set: { "parameters" : [
{
"parameter_name" : "CO2",
"status" : "on"
},
{
"parameter_name" : "NO",
"status" : "on"
},
{
"parameter_name" : "O2",
"status" : "on",
"is_active": true,
"min": 5
}
]
}
}
)

Mongodb: is possible to returning other value from array with project using `$ne`?

Can mongo $ne return value in $project instead of true or false ?
The documents :
{
"_id" : ObjectId("5d1449db6f934a2926c175d1"),
"clients" : [
"82",
"85"
],
"roomId" : 1,
"message" : [
{
"time" : ISODate("2019-06-27T04:44:49.528Z"),
"status" : "SEND",
"text" : "Ellorad",
"sender" : "82",
"reciever" : "82",
"_id" : ObjectId("5d1449db6f934a2926c175d2")
},
{
"time" : ISODate("2019-06-27T04:44:49.528Z"),
"status" : "SEND",
"text" : "helas veronaaah",
"sender" : "82",
"reciever" : "85",
"_id" : ObjectId("5d1449ff6f934a2926c175d3")
}
]
}
What I tried :
Chat.aggregate([
{ $match: { clients: 82 } },
{
$project: {
_id: '$roomId',
receiver: { $ne: ['$clients', 82] },
message: { $slice: ['$message', -1] }
}
}
])
The result :
{
"message": [
{
"_id": 1,
"receiver": true
"message": [
{
"time": "2019-06-27T04:44:49.528Z",
"status": "SEND",
"text": "helas veronaaah",
"sender": "82",
"reciever": "85",
"_id": "5d1449ff6f934a2926c175d3"
}
]
}
]
}
So the receiver just returning true or false here, which is right considering its $ne, but the result that I want is, the receiver here to be '85' not true or false
Why dont you:
1) unwind the clients array, and
2) use $match in the next stage to exclude the undesired value(82 in this case),
3) use $project stage to get your desired result, clients will have your desired receiver.
Try this :
Chat.aggregate([
{ $match: { clients: 82 } },
{
$unwind : "clients"
},
{
$match : {
clients : {$ne : 82}
}
},
{
$project : {
_id: '$roomId',
receiver: "$clients",
message: { $slice: ['$message', -1] }
}
}
])

MongoDB .find with multiple $or statements, syntax error 'unexpected token ]'?

A user may send or receive a message, and should be able to archive his/her message regardless.
This .find should therefore return any docs where userA is in to.username or from.username, with view.archive:true. From there, the second $or should filter the results from the first $or to only include the matching docs where the message's value.1:true, value.2:true, or value.3:true
Right now, however, there is SyntaxError: Unexpected token ] for the closing ] on the first $or
Models.Messages.find(
{
"$or" : [
{
"to": {
"$elemMatch": {
"username": 'userA',
"view.archive": true,
"view.bin": true
}
}
},
{
"from" : {
"$elemMatch" : {
"username" : 'userA',
"view.archive": true,
"view.bin": true
}
}
}
],
"$or": [
{ 'value.1': true },
{ 'value.2': true },
{ 'value.3': true },
{ 'value.4': false }
]
}
).sort([['updated','descending']]).exec(function (err, messages) {
Both of these messages should be found for userA:
{
"_id" : ObjectId("53e83867f316ea7f22fd3b2b"),
"updated" : ISODate("2014-08-11T03:29:06.000Z"),
"message" : "message1",
"value" : [
"1" : true,
"2" : false,
"3" : false,
"4" : false
]
"to" : [
{
"user" : ObjectId("53e835bd76e0d04318d8cc4e"),
"username" : "userA",
"_id" : ObjectId("53e83867f316ea7f22fd3b2c"),
"view" : {
"inbox" : false,
"outbox" : false,
"archive" : true,
"bin" : false
}
}
],
"from" : [
{
"user" : ObjectId("53e8360276e0d04318d8cc55"),
"username" : "userB",
"_id" : ObjectId("53e83867f316ea7f22fd3b2d"),
"view" : {
"inbox" : false,
"outbox" : true,
"archive" : false,
"bin" : false
}
}
],
"__v" : 5
}
{
"_id" : ObjectId("53e83867f316ea7f22fd3b2b"),
"updated" : ISODate("2014-08-11T03:29:06.000Z"),
"message" : "message2",
"value" : [
"1" : false,
"2" : true,
"3" : false,
"4" : false
]
"to" : [
{
"user" : ObjectId("53e8360276e0d04318d8cc55"),
"username" : "userB",
"_id" : ObjectId("53e83867f316ea7f22fd3b2c"),
"view" : {
"inbox" : true,
"outbox" : false,
"archive" : false,
"bin" : false
}
}
],
"from" : [
{
"user" : ObjectId("53e835bd76e0d04318d8cc4e"),
"username" : "userA",
"_id" : ObjectId("53e83867f316ea7f22fd3b2d"),
"view" : {
"inbox" : false,
"outbox" : true,
"archive" : true,
"bin" : false
}
}
],
"__v" : 5
}
Thanks to #JohnnyHK for his answer to this question as it also applies here.
Was under the impression that $and was implied with .find in MongoDB, but when combining $or filters it appears to be necessary, as also pointed out by #soulcheck.
Models.Messages.find(
{
$and: [{
"$or" : [
{
"to": {
"$elemMatch": {
"username": 'userA',
"view.archive": true,
"view.bin": false
}
}
},
{
"from" : {
"$elemMatch" : {
"username" : 'userA',
"view.archive": true,
"view.bin": false
}
}
}
]}, {
"$or": [
{ 'value.1': true },
{ 'value.2': true },
{ 'value.3': true },
{ 'value.4': false }
]
}
]
}
).sort([['updated','descending']]).exec(function (err, messages) {