47

I have user document collection like this:

User {
   id:"001"
   name:"John",
   age:30,
   friends:["userId1","userId2","userId3"....]
}

A user has many friends, I have the following query in SQL:

select * from user where in (select friends from user where id=?) order by age

I would like to have something similar in MongoDB.

Adarsh Ravi
  • 893
  • 1
  • 16
  • 39
L.J.W
  • 1,575
  • 5
  • 18
  • 24

10 Answers10

47

To have everything with just one query using the $lookup feature of the aggregation framework, try this :

db.User.aggregate(
    [
        // First step is to extract the "friends" field to work with the values
        {
            $unwind: "$friends"
        },
        // Lookup all the linked friends from the User collection
        {
            $lookup:
            {
                from: "User",
                localField: "friends",
                foreignField: "_id",
                as: "friendsData"
            }
        },
        // Sort the results by age
        {
            $sort: { 'friendsData.age': 1 }
        },
        // Get the results into a single array
        {
            $unwind: "$friendsData"
        },
        // Group the friends by user id
        {
            $group:
            {
                _id: "$_id",
                friends: { $push: "$friends" },
                friendsData: { $push: "$friendsData" }
            }
        }
    ]
)

Let's say the content of your User collection is the following:

{
    "_id" : ObjectId("573b09e6322304d5e7c6256e"),
    "name" : "John",
    "age" : 30,
    "friends" : [
        "userId1",
        "userId2",
        "userId3"
    ]
}
{ "_id" : "userId1", "name" : "Derek", "age" : 34 }
{ "_id" : "userId2", "name" : "Homer", "age" : 44 }
{ "_id" : "userId3", "name" : "Bobby", "age" : 12 }

The result of the query will be:

{
    "_id" : ObjectId("573b09e6322304d5e7c6256e"),
    "friends" : [
        "userId3",
        "userId1",
        "userId2"
    ],
    "friendsData" : [
        {
            "_id" : "userId3",
            "name" : "Bobby",
            "age" : 12
        },
        {
            "_id" : "userId1",
            "name" : "Derek",
            "age" : 34
        },
        {
            "_id" : "userId2",
            "name" : "Homer",
            "age" : 44
        }
    ]
}
Derek
  • 1,826
  • 18
  • 25
  • Nice, I'm confused how you can perform such an operation on one item in a collection (where is the findOne equivalent)? Edit: seems like $match might do this – Dominic Jan 18 '18 at 19:13
  • I wonder if this is more efficient than 2 queries since `$match` won't stop searching unlike `findOne`. I wonder if a `$limit` above it would help. – Dominic Jan 18 '18 at 19:26
36

Edit: this answer only applies to versions of MongoDb prior to v3.2.

You can't do what you want in just one query. You would have to first retrieve the list of friend user ids, then pass those ids to the second query to retrieve the documents and sort them by age.

var user = db.user.findOne({"id" : "001"}, {"friends": 1})
db.user.find( {"id" : {$in : user.friends }}).sort("age" : 1);
Derek
  • 1,826
  • 18
  • 25
dannie.f
  • 2,395
  • 2
  • 22
  • 20
  • 4
    Note that the SQL posted in the question is also two queries (although it would be possible with one with better organization ie a friends table) – P Varga Feb 12 '12 at 20:00
  • 1
    I think if you were to convert that to a "proper" database friends would automatically end up in a separate table by anyone that knows anything about database design and normalization. – wobbily_col Nov 10 '13 at 16:41
  • Actually, you would be better off with a relational entity on the sql side so that you could do select users.* from users join friends on friends.friend_id = users.id and friends.user_id = ? order by users.age ... rather than two effective users tables ... – EmeraldD. Jul 02 '15 at 21:57
11

https://docs.mongodb.org/manual/reference/operator/aggregation/lookup/

This is the doc for join query in mongodb , this is new feature from version 3.2.

So this will be helpful.

user3280472
  • 123
  • 1
  • 4
5

You can use in Moongoose JS .populate() and { populate : { path : 'field' } }. Example:

Models:

 mongoose.model('users', new Schema({
        name:String,
        status: true,
        friends: [{type: Schema.Types.ObjectId, ref:'users'}], 
        posts: [{type: Schema.Types.ObjectId, ref:'posts'}], 

    }));
 mongoose.model('posts', new Schema({
            description: String,
            comments: [{type: Schema.Types.ObjectId, ref:'comments'}], 

        }));
 mongoose.model('comments', new Schema({
            comment:String,
            status: true

        }));

If you want to see your friends' posts, you can use this.

Users.find().                    //Collection 1
        populate({path:'friends',   //Collection 2
        populate:{path:'posts'   //Collection 3
        }})
    .exec();

If you want to see your friends' posts and also bring all the comments, you can use this and too, you can indentify the collection if this not find and the query is wrong.

 Users.find().                                    //Collection 1
        populate({path:'friends',                 //Collection 2
        populate:{path:'posts',                   //Collection 3
        populate:{path:'commets, model:Collection'//Collection 4 and more
        }}})
    .exec();

And to finish, if you want get only some fields of some Collection, you can use the propiertie select Example:

Users.find().                                    
        populate({path:'friends', select:'name status friends'                  
        populate:{path:'comments'               
        }})
    .exec();
Andrey Araya
  • 101
  • 1
  • 3
3

MongoDB doesn't have joins, but in your case you can do:

db.coll.find({friends: userId}).sort({age: -1})
pingw33n
  • 12,292
  • 2
  • 37
  • 38
  • Hi,I not understand you,I want to get friends of the specified user ,and sort these friends by age. – L.J.W Dec 30 '10 at 15:09
  • Hi,I have read the mongodb.org/display/DOCS/Querying,but I can not find the solution..So I question here. – L.J.W Dec 31 '10 at 01:07
  • 4
    This is a correct solution if the friends list is symmetric: if A is a friend of B, then B will be a friend of A. What pingw33n is doing here is to say: give me all users who have `userId` as a friend. So this isn't finding a user, then finding their friends. If your friendship relation isn't symmetric (if it is a follow-type relation such as Twitter, then you'll have to go with dannie.t's approach, of doing it in two stages. Or else add a field for the reverse relation ('friendedBy', say) and do it this way. This is "de-normalization": storing the same data in more than one way for speed. – Ian Nov 04 '11 at 17:11
  • 1
    with MongoDB 3.2 you can use aggregate function ($lookup) which will have left join – user3280472 Jun 28 '16 at 17:23
2

one kind of join a query in mongoDB, is ask at one collection for id that match , put ids in a list (idlist) , and do find using on other (or same) collection with $in : idlist

u = db.friends.find({"friends": ? }).toArray()
idlist= []
u.forEach(function(myDoc) { idlist.push(myDoc.id ); } )
db.friends.find({"id": {$in : idlist} } )
Sérgio
  • 6,966
  • 1
  • 48
  • 53
1

Only populate array friends.

User.findOne({ _id: "userId"})
.populate('friends')
.exec((err, user) => {
    //do something
});

Result is same like this:

{
    "_id" : "userId",
    "name" : "John",
    "age" : 30,
    "friends" : [
        { "_id" : "userId1", "name" : "Derek", "age" : 34 }
        { "_id" : "userId2", "name" : "Homer", "age" : 44 }
        { "_id" : "userId3", "name" : "Bobby", "age" : 12 }
    ]
}

Same this: Mongoose - using Populate on an array of ObjectId

0

You can use playOrm to do what you want in one Query(with S-SQL Scalable SQL).

Dean Hiller
  • 19,235
  • 25
  • 129
  • 212
0
var p = db.sample1.find().limit(2) , 
    h = [];
for (var i = 0; i < p.length(); i++) 
{
  h.push(p[i]['name']);
}
db.sample2.find( { 'doc_name': { $in : h } } ); 

it works for me.

Dr. Piyush Dholariya
  • 1,266
  • 16
  • 31
0

You can do it in one go using mongo-join-query. Here is how it would look like:

const joinQuery = require("mongo-join-query");

joinQuery(
    mongoose.models.User,
    {
        find: {},
        populate: ["friends"],
        sort: { age: 1 },
    },
    (err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);

The result will have your users ordered by age and all of the friends objects embedded.

How does it work?

Behind the scenes mongo-join-query will use your Mongoose schema to determine which models to join and will create an aggregation pipeline that will perform the join and the query.

Marcelo Lazaroni
  • 9,819
  • 3
  • 35
  • 41