MongoDb: Match value on foreign collection - mongodb

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

Related

How to join two table in mongodb with aggregate and lookup I have as array in second table?

I have two collections:
clients
{
"_id" : ObjectId("60d203145b7b6c19b00ba576"),
"isDeleted" : false,
"createdAt" : ISODate("2021-06-22T15:06:21.564Z"),
"status" : "live",
"customerGUID" : "C8B910A3F74E",
"apps" : [
{
"url" : "https://test.com",
"loginGUID" : "12324654",
"loginAPIAccessKey" : "B072-266C369743CA",
}
],
"avatar" : "",
"description" : "",
"firstName" : "firstname",
"lastName" : "lastname",
"email" : "company#test.com",
"companyName" : "Company name",
}
visitors
{
"_id" : ObjectId("60aed4e0d6e30865f060be3c"),
"lastName" : "CAIN",
"firstName" : "DAVID",
"loginAPIAccessKey" : "B072-266C369743CA",
"email" : "cainins#att.net",
"createdAt" : ISODate("2021-05-26T23:08:16.505Z"),
"activity" : []
}
I want all visitors which have active clients with isDeleted: false status. and the relationship between visitors and clients is visitors.loginAPIAccessKey and clients.apps.loginAPIAccessKey which is in an array of objects.
Can someone help me?
You have to make use of the $lookup stage with the pipeline option.
db.visitors.aggregate([
{
"$lookup": {
"from": "clients",
"let": {
"loginAPIAccessKey": "$loginAPIAccessKey"
},
"pipeline": [
{
"$unwind": "$apps"
},
{
"$match": {
"$expr": {
"$eq": [
"$apps.loginAPIAccessKey",
"$$loginAPIAccessKey"
],
},
"isDeleted": false,
},
},
],
"as": "matchedClients"
}
},
{
"$match": {
"matchedClients": {
"$ne": []
}
},
},
])

Not able to receive expected result in mongodb between collections

I'm trying to get a product list with logged user wishlist in the product list itself. I'm trying $lookup but not getting the expected result.
Product Document:
[
{
"_id" : ObjectId("6044351794bee8b6e0fce48f"),
"sku" : "P003474",
"name" : "Kitchen Wash"
},
{
"_id" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"sku" : "TS0012",
"name" : "T-shirt"
},
{
"_id" : ObjectId("608d20a90e629fcc0d3a93e1"),
"sku" : "Apple1101",
"name" : "Green Apple"
}
]
Wishlist Document:
wishlist:
[
{
"_id" : ObjectId("608d8af12b7556c445f5cd87"),
"product" : ObjectId("6044351794bee8b6e0fce48f"),
"user" : ObjectId("601fb31a60e0a024143e6ea3"),
"isLiked" : false
},
{
"_id" : ObjectId("608d8bad2b7556c445f5cd88"),
"product" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"user" : ObjectId("601fb31a60e0a024143e6ea3"),
"isLiked" : true
}
]
Expected Result:
Expected output:
[
{
"_id" : ObjectId("6044351794bee8b6e0fce48f"),
"sku" : "P003474",
"name" : "Kitchen Wash",
"isLiked": false
},
{
"_id" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"sku" : "TS0012",
"name" : "T-shirt",
"isLiked": true
},
{
"_id" : ObjectId("608d20a90e629fcc0d3a93e1"),
"sku" : "Apple1101",
"name" : "Green Apple"
}
]
The query I'm trying:
db.getCollection('products').aggregate([
{
$lookup: {
from: "wishlists",
localField: "_id",
foreignField: "product",
as: "product"
}
},
{
$unwind:"$product"
}
])
Not able to get the above-declared result
$unwind causing the issues, first problem is it will remove document when product lookup result empty/[] if you don't specify preserveNullAndEmptyArrays : true, see working behaviour in playground
use $lookup with pipeline and match product id and user id condition
i have changed as name to isLiked in $lookup
$addFields to add isLiked parameter it its present, $arrayElemAt to get isLiked element from first element
db.getCollection('products').aggregate([
{
$lookup: {
from: "wishlists",
let: { product: "$_id" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $eq: ["$$product", "$product"] } },
{ user: ObjectId("601fb31a60e0a024143e6ea3") }
]
}
}
],
as: "isLiked"
}
},
{
$addFields: {
isLiked: { $arrayElemAt: ["$isLiked.isLiked", 0] }
}
}
])
Playground

MongoDB aggregate two collections, return additional field as count

(See edit below)
I am trying to aggregate data from two separate collections within the same MongoDB database.
The "accounts" collection contains user information (cleansed):
{
_id: ObjectId("5c0d64a4224a2900108c005f"),
"username" : "mike22",
"email" : "mike22#<domain>.com",
"country" : GB,
"created" : ISODate("2018-11-26T23:37:49.051Z")
},
{
_id: ObjectId("5a0d64a4527h2880108c0445"),
"username" : "mike23",
"email" : "mike23#<domain>.com",
"country" : DE,
"created" : ISODate("2018-11-26T23:37:49.051Z")
},
{
_id: ObjectId("5a3334a45zzz2884448c0445"),
"username" : "mike24",
"email" : "mike24#<domain>.com",
"country" : DE,
"created" : ISODate("2018-11-26T23:37:49.051Z")
}
The "devices" collection contains device definitions for all users. A user is likely to have many devices defined in this collection and many users devices are in this collection.
A single device within this collection is defined as follows:
{
"_id" : ObjectId("5c10138c73bbe0001018e415"),
"capabilities" : [
"BrightnessController",
"PowerController"
],
"displayCategories" : [
"LIGHT"
],
"friendlyName" : "Test1",
"description" : "Test device 1",
"reportState" : true,
"username" : "mike22",
"endpointId" : 11,
"__v" : 0
},
{
"_id" : ObjectId("5c10138c73bbe0001018e415"),
"capabilities" : [
"PowerController"
],
"displayCategories" : [
"SWITCH"
],
"friendlyName" : "Test2",
"description" : "Test device 2",
"reportState" : true,
"username" : "mike23",
"endpointId" : 12,
"__v" : 0
},
{
"_id" : ObjectId("5c10138c73bbe0001018e415"),
"capabilities" : [
"PowerController"
],
"displayCategories" : [
"SMARTPLUG"
],
"friendlyName" : "Test3",
"description" : "Test device 3",
"reportState" : true,
"username" : "mike22",
"endpointId" : 13,
"__v" : 0
}
I'm able to use the aggregate below to show me a count of device per-user:
db.accounts.aggregate([
{
$lookup: {
from : "devices",
localField : "username",
foreignField : "username",
as : "userdevs"
},
},
{ $unwind:"$userdevs" },
{ $group : { _id : "$username", count : { $sum : 1 } } }
])
Example output from the data/ aggregate above:
{ "_id" : "mike22", "count" : 2 },
{ "_id" : "mike23", "count" : 1 }
(Note user with no devices is now missing/ should be there with a zero count?!)
However, I want to return all fields for each user plus a new field which shows me the count of devices they have in the "devices" collection. The output I am looking for is as below:
{
"_id" : ObjectId("5c0d64a4224a2900108c005f"),
"username" : "mike22",
"email" : "mike22#<domain>.com",
"country" : GB,
"created" : ISODate("2018-11-26T23:37:49.051Z"),
"countDevices": 2
},
{
"_id" : ObjectId("5a0d64a4527h2880108c0445"),
"username" : "mike23",
"email" : "mike23#<domain>.com",
"country" : DE,
"created" : ISODate("2018-11-26T23:37:49.051Z"),
"countDevices": 1
},
{
"_id" : ObjectId("5a0d64a4527h2880108c0445"),
"username" : "mike24",
"email" : "mike24#<domain>.com",
"country" : DE,
"created" : ISODate("2018-11-26T23:37:49.051Z"),
"countDevices": 0
}
Edit 16/12: So I am nearly there with the aggregate below. Zero-count users are missing though.
use users
db.accounts.aggregate([
{
$lookup: {
from : "devices",
localField : "username",
foreignField : "username",
as : "userdevs"
},
},
{ $unwind: "$userdevs"},
{ $group : { _id : {
_id: "$_id",
username: "$username",
email: "$email",
country: "$country",
region: "$region",
},
countDevices : { $sum : 1 } } }
])
2nd Edit 16/12:
I have found the aggregate needed below:
db.accounts.aggregate([
{ "$lookup": {
"from": "devices",
"let": { "username": "$username" },
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$$username", "$username" ] }
}},
{ "$count": "count" }
],
"as": "deviceCount"
}},
{ "$addFields": {
"countDevices": { "$sum": "$deviceCount.count" }
}}
])
First of All, you can flatten the answer you have got with a projection like below:
{ $project : {
_id : '$_id._id',
username : '$_id.username',
email : '$_id.email',
country : '$_id.country',
region : '$_id.region',
countDevices: 1
}
}
add this after the $group in your pipeline, you will get your result as you wanted in the question.
About zero-count users, there is a way to handle this in database using mongoDB, as explained in detail here but I do not recommend it, its better that you handle this kind of problem client side.
As-per second edit, the aggregate I used is as below:
db.accounts.aggregate([
{ "$lookup": {
"from": "devices",
"let": { "username": "$username" },
"pipeline": [
{ "$match": {
"$expr": { "$eq": [ "$$username", "$username" ] }
}},
{ "$count": "count" }
],
"as": "deviceCount"
}},
{ "$addFields": {
"countDevices": { "$sum": "$deviceCount.count" }
}}
])

Aggregation unwind multiple arrays $lookup as multiple fields for projection

This is my current query:
Follow.aggregate([
{
$match: {
"user": Types.ObjectId(user_id)
}
},
{
$unwind: "$following"
},
{
$lookup: {
"from": "users",
"localField": "following",
"foreignField": "createdById",
"as": "followingUsers"
}
}, {
$project: {
"user": 1,
"followingUsers": 1
}
}
])
This query is run on a document like this:
{
"_id" : ObjectId("5a271a93a19d690b25a3e181"),
"user" : ObjectId("5a271a4f50261a3c1695a391"),
"following" : [
ObjectId("5a257c87086eb00712fd02ec"),
ObjectId("5a257a79086eb00712fd02eb")
],
"followers" : [
ObjectId("5a257a79086eb00712fd02eb")
]
}
And gives a result like this:
{
"_id" : ObjectId("5a271a93a19d690b25a3e181"),
"user" : ObjectId("5a271a4f50261a3c1695a391"),
"followingUsers" : [
{
"_id" : ObjectId("5a257a79086eb00712fd02eb"),
"email" : "email#gmail.com",
"username" : "username",
"email_verified" : true,
"created" : ISODate("2010-12-04T16:40:25.670Z"),
"__v" : 0,
"last_login" : ISODate("2010-12-06T21:14:25.538Z"),
"is_active" : false
}
]
}
The above unwinds only one of the arrays and follows up the lookup.
I want to, in one scoop (assuming that is possible)
unwind both the $following and $followers array fields
$lookup both of the array fields independently
then have both come out as, say, followingUsers and followersUsers
So end up with something like this:
{
"_id" : ObjectId("5a271a93a19d690b25a3e181"),
"user" : ObjectId("5a271a4f50261a3c1695a391"),
"followingUsers" : [
{
"_id" : ObjectId("5a257a79086eb00712fd02eb"),
....
}
],
"followersUsers": [
{
"_id" : ObjectId("5a257a79086eb00712fd02eb"),
....
}
]
}
Am I even sane to imagine something like this in MongoDB?

Mongodb .aggregate not matching $or?

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.