How to retrieve in the same query user's followers and users I already follow in mongodb - database

I'm using mongodb with this collections: User and Follow. User collection has users and follow has the followers mapping.
Try to run this query but only get the user's followers and who I'm following too:
users = User.collection.aggregate([{
"$lookup": {
from: "follows",
localField: "_id",
foreignField: "followable_id",
as: "follow"
}
},
{
"$match": {
"follow.followable_type": "User",
"follow.user_id": BSON::ObjectId.from_string(userid)
}
},
{
"$group": {
_id: "$follow.followable_id",
count: {
"$sum": 1
},
data: {
"$addToSet": "$$ROOT"
},
}
},
{
"$project": {
_id: 1,
data: 1,
count: 1,
follow: {
'$cond': {
'if': {
'$eq': ["$count", 2]
},
then: true,
else: false
}
}
}
},
{
"$skip": 0 * 10
},
{
"$limit": 10
}
])
users.each do |user|
puts "#{user['data'].first['name']}: #{user['follow']}: #{user['count']}"
end
How to return in the same query the users I'm following?
the output:
Diego_Lukmiller: false: 1
Marianno: false: 1
kah: false: 1
Fausto Torres: false: 1

I solved/fixed it by doing in three steps (written in ruby/mongoid):
//get users the user X follows
users = Follow.collection.aggregate([
{ "$match": { "followable_id": BSON::ObjectId.from_string(userid) } },
{ "$lookup": { from: "users", localField: "user_id", foreignField: "_id", as: "user" } },
{ "$unwind": "$user" },
{ "$sort": {"created_at":-1} },
{ "$skip": page * 10 },
{ "$limit": 10 },
{ "$group":{ _id: "$followable_id", data: {"$addToSet": "$user._id"} } }
])
userids=users.blank? ? [] : users.first['data'].to_a
//get users I follow
f=Follow.collection.aggregate([
{"$match":{user_id: BSON::ObjectId.from_string(meid), followable_id: {"$in":userids}}},
{"$group":{ _id: nil, data: {"$addToSet": "$followable_id"}}}
])
//make intersection
users = User.collection.aggregate([
{ "$lookup": { from: "follows", localField: "_id", foreignField: "user_id", as: "follow" } },
{ "$unwind": "$follow" },
{ "$match": { "follow.followable_type": "User","follow.followable_id": BSON::ObjectId.from_string(userid) } },
{ "$group":{ _id: "$_id", data:{"$addToSet": "$$ROOT"}} },
{ "$unwind": "$data" },
{ "$project": {
_id: 1,data: 1,count: 1,
follow: {
'$cond': {
"if": { '$in': [ "$_id", f.first['data'] ] }, "then": true,
"else": false
}
}
}
},
{ "$skip": page * 10 },
{ "$limit": 10 }
])
The output:
name: Fausto Torres; Do I follow too? false; Counter that's make the magic: 1
name: kah; Do I follow too? false; Counter that's make the magic: 1
name: Marianno; Do I follow too? true; Counter that's make the magic: 2
name: Diego_Lukmiller; Do I follow too? true; Counter that's make the magic: 2
The query performs pretty fast and looks simple. Is there way / How do I solve it in a single query?!?

Related

How to query two collections with related data?

I have 2 collections, collection A has some documents like {'id':1,'field':'name'},{'id':1,'field':'age'},and collection B has some documents like
{'_id':1,'name':'alice','age':18,'phone':123},{'_id':2,'name':'bob','age':30,'phone':321}
and I want to find all the document whose '_id' is in collectionA, and just project the corresponding field.
for example:
collection A
{'id':1,'field':'name'},
{'id':1,'field':'age'}
collection B
{'_id':1,'name':'alice','age':18,'phone':123},
{'_id':2,'name':'bob','age':30,'phone':321}
the result is:
{'name':'alice','age':18},
I don't know if there is an easy way to do that?
You can use $lookup to join two collection
db.col1.aggregate([
{
$match: {
id: 1
}
},
{
"$lookup": {
"from": "col2",
"localField": "id",
"foreignField": "_id",
"as": "listNames"
}
},
{
$project: {
listNames: {
$first: "$listNames"
}
}
},
{
$project: {
_id: 0,
name: "$listNames.name",
age: "$listNames.age"
}
}
])
Mongo Playground: https://mongoplayground.net/p/E-0WvK_SUS_
So the idea is:
Convert the documents in to key, value pair for both the collections using $objectToArray.
Then perform a join operation based on key k and (id <-> _id) using $lookup.
Replace the result as root element using $replaceRoot.
Convert array to object using $arrayToObject and again $replaceRoot.
Query:
db.colB.aggregate([
{
$project: {
temp: { $objectToArray: "$$ROOT" }
}
},
{
$lookup: {
from: "colA",
let: { temp: "$temp", colB_id: "$_id" },
pipeline: [
{
$addFields: {
temp: { k: "$field", v: "$id" }
}
},
{
$match: {
$expr: {
$and: [
{ $in: ["$temp.k", "$$temp.k"] },
{ $eq: ["$temp.v", "$$colB_id"] }
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$$temp",
as: "item",
cond: { $eq: ["$field", "$$item.k"] }
}
}
}
}
}
],
as: "array"
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$array" }
}
}
]);
Output:
{
"name" : "alice",
"age" : 18
}

aggregation in nodejs resulting in nested json, can I get it without nesting, taking only one data _id from all collections

aggregation in nodejs resulting in nested json, can I get it without nesting, taking only one data _id from all collections. Is there any possibility to get the data without a nested json
I was trying aggregation in nodejs with the below code. I got the output as given in output session below. But I would like to get the output as expected output, since I cant use looping on looping
Student.aggregate([
{
$match: { name: 'abcd'}
},
{
$lookup:{
from:'teachers',
pipeline: [
{
$match: { name: 'pqrs' }
},
{
$project:{
"_id":1
}
}
],
as: "teacherLookup"
}
},
{
$lookup:
{
from:'subjects',
pipeline: [
{
$match: { name: 'computer' }
},
{
$project:{
"_id":1
}
}
],
as: "subjectLookup"
}
}
])
output
[
{
_id: '52301c7878965455d2a4',
teacherLookup: [ '5ea737412589688930' ],
subjectLookup: [ '5ea745821369999917' ]
}
]
I am expecting the output as (without nested json)
[
{
studentId: '5ea1c7878965455d2a4',
teacherId: '5ea737412589688930' ,
subjectId: '5ea745821369999917'
}
]
You can use $arrayElemAt to get the first element from the array.
Student.aggregate([
{
$match: { name: "abcd" },
},
{
$lookup: {
from: "teachers",
pipeline: [
{
$match: { name: "pqrs" },
},
{
$project: {
_id: 1,
},
},
],
as: "teacherId",
},
},
{
$lookup: {
from: "subjects",
pipeline: [
{
$match: { name: "computer" },
},
{
$project: {
_id: 1,
},
},
],
as: "subjectId",
},
},
{
$project: {
teacherId: { $arrayElemAt: ["$teacherId", 0] },
subjectId: { $arrayElemAt: ["subjectId", 0] },
},
}
]);

Lookup VS Lookup with pipeline MongoDB (Performace & How it internally works)

I'm making a blog and have an query about which would give me better performace, simple lookup or lookup with pipeline because sometime simple lookup gave me fast result and sometime pipleline lookup. So, I am bit confused now which one to use or where to use. Suppose I have 2 collection, user and comment collection.
// Users Collection
{
_id: "MONGO_OBJECT_ID",
userName: "Web Alchemist"
}
// Comments Collection
{
_id: "MONGO_OBJECT_ID",
userId: "USER_MONGO_OBJECT_ID",
isActive: "YES", // YES or NO
comment: "xyz"
}
Now I want to Lookup from users collection to comments, which one would be better for this. I made two query which giving me same result.
[
{
$match: { _id: ObjectId("5d68c019c7d56410cc33b01a") }
},
{
$lookup: {
from: "comments",
as: "comments",
localField: "_id",
foreignField: "userId"
}
},
{
$unwind: "$comments"
},
{
$match: {
"comments.isActive": "YES"
}
},
{ $limit: 5},
{
_id: 1, userName: 1, comments: { _id: "$comments._id", comment: "$comments.comment"}
},
{
$group: {
_id: "$_id",
userName: { '$first': '$userName' },
comments: { $addToSet: "comments"}
}
}
]
OR
[
{
$match: { _id: ObjectId("5d68c019c7d56410cc33b01a") }
},
{
$lookup: {
from: "comments",
as: "comments",
let: { userId: "$_id" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$userId', '$$userId'] },
{ $eq: ['$isActive', 'YES'] }
]
}
}
},
{ limit: 5 },
{
$project: { _id: 1, comment: 1 }
}
]
}
}
]

Update value in nested array of mongo 3.5

I want to make an upsert call to update as well as insert my data in nested array of mongo db.
This is my mongo document.
{
"_id" : "575",
"_class" : "com.spyne.sharing.SpyneShareUserProject",
"spyneSharePhotoList" : [
{
"_id" : "fxLO68XyMR",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#eventila.com",
"selectedByClient" : false
},
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : false
}
]
},
{
"_id" : "nVpD0KoQAI",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#eventila.com",
"selectedByClient" : true
}
]
},
{
"_id" : "Pm0B3Q9Igv",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : true
}
]
}
]
}
Here my requirement is,
lets say i have an ID i.e. 575 (_id)
Then i will have the nested array ID i.e. fxLO68XyMR (spyneSharePhotoList._id)
Then i will have nested email id as ID i.e. chittaranjan#eventila.com (spyneSharePhotoList.spyneShareUsers._id) and selectedByClient (boolean)
Now i want is to check if this ID (spyneSharePhotoList.spyneShareUsers._id) is already present in the desired location i want to update the boolean value i.e. selectedByClient (true / false) according to that email id.
If the id is not present in the array, the it will make a new entry. as
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : false
}
in spyneShareUsers list.
Please help me to do this task. Thank you
Most likely this is not the smartest solution, but it should work:
shareUserProject = {
id: "575",
PhotoListId: "fxLO68XyMR",
ShareUserId: "chittaranjan_new#eventila.com"
}
db.collection.aggregate([
{ $match: { _id: shareUserProject.id } },
{
$facet: {
root: [{ $match: {} }],
update: [
{ $unwind: "$spyneSharePhotoList" },
{ $match: { "spyneSharePhotoList._id": shareUserProject.PhotoListId } },
{
$set: {
"spyneSharePhotoList.spyneShareUsers": {
$concatArrays: [
{
$filter: {
input: "$spyneSharePhotoList.spyneShareUsers",
cond: { $ne: ["$$this._id", shareUserProject.ShareUserId] }
}
},
[{
_id: shareUserProject.ShareUserId,
selectedByClient: { $in: [shareUserProject.ShareUserId, "$spyneSharePhotoList.spyneShareUsers._id"] }
}]
]
}
}
}
]
}
},
{ $unwind: "$root" },
{ $unwind: "$update" },
{
$set: {
"root.spyneSharePhotoList": {
$concatArrays: [
["$update.spyneSharePhotoList"],
{
$filter: {
input: "$root.spyneSharePhotoList",
cond: { $ne: ["$$this._id", shareUserProject.PhotoListId] }
}
}
]
}
}
},
{ $replaceRoot: { newRoot: "$root" } }
]).forEach(function (doc) {
db.collection.replaceOne({ _id: doc._id }, doc);
})
I did not check whether all operators are available in MongoDB 3.5
My goal was to process everything in aggregation pipeline and run just a single replaceOne() at the end.
Here another solution based on $map operator:
db.collection.aggregate([
{ $match: { _id: shareUserProject.id } },
{
$set: {
spyneSharePhotoList: {
$map: {
input: "$spyneSharePhotoList",
as: "photoList",
in: {
$cond: {
if: { $eq: [ "$$photoList._id", shareUserProject.PhotoListId ] },
then: {
"_id": "$$photoList._id",
spyneShareUsers: {
$cond: {
if: { $in: [ shareUserProject.ShareUserId, "$$photoList.spyneShareUsers._id" ] },
then: {
$map: {
input: "$$photoList.spyneShareUsers",
as: "shareUsers",
in: {
$cond: {
if: { $eq: [ "$$shareUsers._id", shareUserProject.ShareUserId ] },
then: { _id: shareUserProject.ShareUserId, selectedByClient: true },
else: "$$shareUsers"
}
}
}
},
else: {
$concatArrays: [
"$$photoList.spyneShareUsers",
[ { _id: shareUserProject.ShareUserId, selectedByClient: false } ]
]
}
}
}
},
else: "$$photoList"
}
}
}
}
}
}
]).forEach(function (doc) {
db.collection.replaceOne({ _id: doc._id }, doc);
})
You can achieve the same result also with two updates:
shareUserProject = {
id: "575",
PhotoListId: "fxLO68XyMR_x",
ShareUserId: "chittaranjan_new#gmail.com"
}
ret = db.collection.updateOne(
{ _id: shareUserProject.id },
{ $pull: { "spyneSharePhotoList.$[photoList].spyneShareUsers": { _id: shareUserProject.ShareUserId } } },
{ arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)
db.collection.updateOne(
{ _id: shareUserProject.id },
{ $push: { "spyneSharePhotoList.$[photoList].spyneShareUsers": { _id: shareUserProject.ShareUserId, selectedByClient: ret.modifiedCount == 1 } } },
{ arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)

MongoDB Aggregate ObjectId inside Array of embedded Documents

I have seen a ton of question about the $lookup aggregator for arrays of ObjectIds but I can't seem to find anything about when the ObjectId is inside an array of embedded documents.
I have the follow document inside a mongodb database:
_id: ObjectId('...')
Alarms: [
{
Gateway: ObjectId('...')
Port: 1
},
{
Gateway: ObjectId('...')
Port: 2
}
]
I would like to have the following:
_id: ObjectId('...')
Alarms [
{
Gateway: ...(Gateway Object),
Port: 1
},
{
Gateway: ...(Gateway Object),
Port: 2
}
]
I have tried the following with no success:
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.Gateway'
}
But this gives me the following result:
_id: ObjectId('...')
Alarms [
{
Gateway: {
...(Gateway Object)
}
Port: 1
}
]
Please try the below queries :
If you don't want the object which doesn't have match in Gateway collection exist in Alarms array in final result :
db.Alarms.aggregate([{ $unwind: '$Alarms' }, {
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.Gateway'
}
}, { $match: { 'Alarms.Gateway': { $ne: [] } } },
{ $addFields: { 'Alarms.Gateway': { $arrayElemAt: ['$Alarms.Gateway', 0] } } },
{ $group: { _id: '$_id', Alarms: { $push: '$Alarms' } } }
])
Test : MongoDB-Playground
Otherwise, if you want all objects in Alarms array to be returned irrespective of whether there is a match in Gateway or not :
db.Alarms.aggregate([{ $unwind: '$Alarms' }, {
$lookup: {
from: 'Gateway',
localField: 'Alarms.Gateway',
foreignField: '_id',
as: 'Alarms.GatewayObj'
}
}, { $addFields: { 'Alarms.Gateway': { $cond: [{ $ne: ['$Alarms.GatewayObj', []] }, { $arrayElemAt: ['$Alarms.GatewayObj', 0] }, '$Alarms.Gateway'] } } },
{ $project: { 'Alarms.GatewayObj': 0 } },
{ $group: { _id: '$_id', Alarms: { $push: '$Alarms' } } }
])
Test : MongoDB-Playground
Difference between two queries would be one will return below object in Alarms array (Vs) one don't.
{
"Gateway": ObjectId("5e2b5425d02e05b6940de2fb"),
"Port": 2
}

Resources