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

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": []
}
},
},
])

Related

How to write following queries in MongoDB using aggregate?

Retrieve films with an actor living in "Spain",
Retrieve films with actor details.
Collections are:
db.actor.insert([{ "actorId" : "5", "firstName" : "Ritik", "lastName" : "Roshan", "address" : { "street" : "GM Road", "city" : "Guwahati", "state" : "Aasam", "country" : "India", "pincode" : "145145" }, "contactDetails" : { "email" : "Ritik.roshan#gmail.com", "phoneno" : "9874584" }, "age" : "52" }])
db.film.insert([{ "filmId" : "10","title" : "Doshti Ka Karishma", "releaseOfYear" : "2001", "category" : ["advanture","Romantic"],
"actor" : [{ "firstName" : "Ritik", "lastName" : "Roshan" },{ "firstName" : "Karishma", "lastName" : "Kapoor" }],
"director" : [{ "firstName" : "Satish", "lastName" : "Ambike" }],
"releaseDetails" : { "place" : "Rajasthan", "date" : ISODate("2001-05-18T15:14:08.023Z"), "rating" : "C"}}])
You can use $lookup to join two collection
db.film.aggregate([
{ $match: { "releaseDetails.place": "Rajasthan" } },
{
"$lookup": {
"from": "actor",
"let": {
actor: "$actor"
},
"pipeline": [
{
$match: {
$expr: {
$and: [
{ $in: [ "$firstName", "$$actor.firstName" ] },
{ $in: [ "$lastName", "$$actor.lastName" ] }
]
}
}
}
],
"as": "joinActors"
}
}
])
Working Mongo playground
Note : You are saving firstname and lastname as reference in film collection. But combination of firstname and lastname are not always unique, Better you save _id of actor into the film collection's actor section.
something like this Save ref id

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

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" }
}}
])

How to filter a nested/lower subdocument

Say if I have a result like this:
{
"_id" : ObjectId("5b722d06c23b5f2bd0329a41"),
"name" : "test",
"item" : {
"_id" : ObjectId("5b722d07c23b5f2bd0329a53"),
"display_name" : "",
"image" : "http://via.placeholder.com/700x500/ffffff/000000/?text=No%20Image&",
"is_private" : true,
"details" : [
{
"is_private" : false,
"type" : "text",
"_id" : ObjectId("5b722d06c23b5f2bd0329a44"),
"title" : "Name",
"content" : "",
"order" : 0
},
{
"is_private" : false,
"type" : "text",
"_id" : ObjectId("5b722d06c23b5f2bd0329a43"),
"title" : "Price",
"content" : "",
"order" : 1
},
{
"is_private" : false,
"type" : "text",
"_id" : ObjectId("5b722d06c23b5f2bd0329a42"),
"title" : "Company",
"content" : "",
"order" : 2
}
],
"tags" : [ ],
"__v" : 0
}
}
and I want to filter by item.details.is_private, how should I do this? I want to return all properties of item but filter out any item.details.is_private if it is true
I am currently projecting it as:
{
"$project": {
name: 1,
item: 1
}
}
but am not sure how to implement $filter in this setting
You can try using $addsFields with $filter aggregation
db.collection.aggregate([
{ "$addFields": {
"item.details": {
"$filter": {
"input": "$item.details",
"as": "detail",
"cond": {
"$eq": [ "$$detail.is_private", true ]
}
}
}
}}
])

$lookup and $match Mongodb golang

I want to get document with foreign key by using $lookup and $match on MongoDB.
There is a "Jobs" collection which stores Job document. In Job document there are two field using as foreing key "creatorParent" and "Children".
CreatorParent is a foreign key for "Users" collection and Children array contains id for user's children.
When I list the whole jobs, I want to retrieve detail from "Users" collection for both CreatorParent ID and ChildrenID. I want to marshall "Job" document with ParentDetail and ChildDetail. I don't want to write a custom method for that. Is it possible to handle it with MongoDB query?
By the way I'm beginner on MongoDB so should store needed details on Children and CreatorParent instead of storing ObjectId?
Users document:
{
"_id" : ObjectId("58daf84877733645eaa9b44f"),
"email" : "meto93#gmail.com",
"password" : "vpGl+Fjnef616cRgNbCkwaFDpSI=",
"passwordsalt" : "99397F4A9D3A499D96694547667E74595CE994D2E83345D6953EF866303E8B65",
"children" : [
{
"_id" : ObjectId("58daf84977733645eaa9b450"),
"name" : "Mert",
"age" : 5,
"additionalinformation" : "ilk cocuk",
"creationtime" : ISODate("2017-03-28T23:56:56.952Z"),
"userid" : ObjectId("58daf84877733645eaa9b44f"),
"gender" : null
},
{
"_id" : ObjectId("58daf84977733645eaa9b451"),
"name" : "Sencer",
"age" : 7,
"additionalinformation" : "ikinci cocuk",
"creationtime" : ISODate("2017-03-28T23:56:56.952Z"),
"userid" : ObjectId("58daf84877733645eaa9b44f"),
"gender" : null
}
]
}
Job
{
"_id" : ObjectId("58db0a2d77733645eaa9b453"),
"creationtime" : ISODate("2017-03-29T01:13:17.509Z"),
"startingtime" : ISODate("2017-04-03T13:00:00.000Z"),
"endingtime" : ISODate("2017-04-03T17:00:00.000Z"),
"children" : [
ObjectId("58daf84977733645eaa9b450"),
ObjectId("58daf84977733645eaa9b451")
],
"creatorparent" : ObjectId("58daf84877733645eaa9b44f"),
"applicants" : []
}
If I understood it correctly. A similar solution is achievable using MongoDB 3.4's $addFields and $lookup aggregation steps.
Mongo aggregation:
[
{
$addFields: {
"job":"$$ROOT"
}
},
{
$unwind: {
path : "$children"
}
},
{
$lookup: {
"from" : "users",
"localField" : "creatorParent",
"foreignField" : "_id",
"as" : "creatorParent"
}
},
{
$lookup: {
"from" : "users",
"localField" : "children",
"foreignField" : "_id",
"as" : "children"
}
},
{
$group: {
"_id": "$_id",
"job": { "$first": "$job" },
"creatorParent" : { "$first" : "$creatorParent" },
"children": { "$addToSet": { $arrayElemAt: [ "$children", 0 ] } }
}
}
]
The output will look like the following:
{ "_id" : ObjectId("58da9cb6340c630315348114"),
"job" : {
"_id" : ObjectId("58da9cb6340c630315348114"),
"name" : "Developer",
"creatorParent" : ObjectId("58da9c79340c630315348113"),
"children" : [
ObjectId("58da9c6d340c630315348112"),
ObjectId("58da9c5f340c630315348111")
],
"hourly_rate" : 12.0,
"additional_information" : "other infos"
},
"creatorParent" : [
{
"_id" : ObjectId("58da9c79340c630315348113"),
"name" : "The Boss",
"age" : 40.0
}
],
"children" : [
{
"_id" : ObjectId("58da9c5f340c630315348111"),
"name" : "James",
"age" : 28.0
},
{
"_id" : ObjectId("58da9c6d340c630315348112"),
"name" : "Andrew",
"age" : 26.0
}
]}
UPDATE:
If you substitute the last $group stage with this:
{
"_id": "$_id",
"name": { "$first": "$name" },
"jobstatus": { "$first": "$jobstatus" },
"hourlyrate": { "$first":"$hourlyrate" },
"creatorparent" : { "$first" : "$creatorparent" },
"children": { "$addToSet": { $arrayElemAt: [ "$children", 0 ] } }
}
Then you can achieve what you would like to, but in this $group stage you have to specify every field of job one-by-one with the $first expression.