5

I'm new with mongo and still trying to drift my head away from the mySQL methodology and dive it into mongo, so a few things are still blur on how i'm suppose to do and i was hopping you guys could shed me some light.

Let me explain the problem. I'm using nodeJS and the mongodb native.

Say i have a collection of users (i've used simpler _id just to illustrate, i know its an objectID, but consider just for the sake of the example)

{
     _id: 1,
     name: 'John',
     likes: ['2','3','4'] 
},
{
     _id: 2,
     name: 'Michelle'
     likes: ['1','4']
},
{
     name: 'Sabrina'
     _id: 3,
     likes: []
},
{
     name: 'Simone'
     _id: 4,
     likes: ['1','2', '3']
}

note that John liked Michell, Sabrina, and Simone, that Michelle liked John and Simone, Sabrina didn't like anyone, and Simone liked John, Michelle and Sabrina.

How do i run a query for John to find out who also liked him back based on his array of likes? I need something that would return Michelle if id = 1, or returns John and Michelle if id = 4. Only the ones that the id i'm fetching is in the other users like.

let me try to simplify.

I need to fetch John and go through all his likes and check id 2 likes me back? Yes id 3 likes me back? No

return [John];

or fetch Simone and go through all her likes and check id 1 likes me back? Yes id 2 likes me back? Yes id 3 likes me back? No

return [John, Michelle]

Any help would be appreciated :) thanks

2
  • You say you want to "return Michelle if id = 1" but shouldn't you include Simone in the results for id=1 as well? Commented Apr 13, 2013 at 2:39
  • If you're happy with lytnus's answer, you should mark it as accepted. It's helpful to you, him, and the community to know when something is resolved. Commented Apr 13, 2013 at 17:12

4 Answers 4

5

Here we go:

Something like this should do the trick using the mongodb-native driver in nodejs:

var people = db.collection("users");
people.findOne({name: "John"}, function(err, result)
  if(err) throw err; //..or however you want to handle an error.
  people.find({_id: {$in: result.likes}, likes:result._id}).toArray(function(err, others)
    {
    if(err) throw err; //...again, your call how to handle a potential error.
    //finds all people who are in likes array AND have Johns ID in their likes array.
    });
  });

Basically, first you retrieve the record for John. Next, you use his likes array to retrieve all users with matching id's :)

Note that this can be optimised slightly by opting to only retrieve the liked field for John, as in:

people.findOne({name: "John"}, {"likes": 1}, function(err, result)
  {
  //.....
  });

Also note that if the likes array is particularly massive, you may want to retrieve values one at a time rather than all at once using the .toArray() method, by instead using the cursor returned from find() and working with items one at a time, as in:

//....
var cursor = people.find({_id: {$in: result.likes}, likes:result._id});
cursor.each(function(err, item)
    {
    if(err) throw err; //...
    if(item != null)
    //do stuff with each person returned given likes array
    });
//....

I'm afraid I havnt tested the above, so apologies if I made any mistakes!

I find the manual for the mongo-native node.js driver to be invaluable; it covers most things pretty well (although some of the code seems a little "older" than other bits):

http://mongodb.github.io/node-mongodb-native/api-articles/nodekoarticle1.html

Sign up to request clarification or add additional context in comments.

3 Comments

Hy lytnus, i thought i could do it in one query but your solution is great. the only thing i had to actually change was the bit with people.find({_id: {$in: result.likes}, likes:{ $in: [result._id]}}) that i had to use an array to check if my id is withIN their likes. Thanks!!!
Do you not have an index on likes? If you have an index (which you should), I'm pretty sure you don't need the $in syntax. I could be wrong, but that's what I remember off the top of my head.
Actually, good point; I mentioned some smaller (potential) optimisations but ignored the obvious one that you should be indexing on likes (and name). From what I gathered, you only needed to use $in if you are passing in an array for your search, in which case mongo searches for any fields whose entry (or entries if the field being searched on is an array) are IN the array supplied.
4

It has to be done in two queries (since Mongo is non-relational). The fist query would be to get the person (I'm sure you've figured out how to do that). The second would answer who likes him/her back.

var userId = 1;
people.findOne({ _id: userId }, { likes: 1 }, function (err, person) {
    if (err)
        throw err; // <-- obviously handle the error better than this

    people.find({ _id: { $in: person.likes }, likes: userId }).toArray(
        function (err, likers) {
            //now you have your list of people who liked the user back
        }
    );
});

2 Comments

lytnus's answer is now essentially identical to mine. Previously he was using an incorrect query, so I posted this correction, but now it makes no difference.
Yeah, sorry about that. I edited my answer a bunch of times as I was rather tired when I wrote it and made several mistakes along the way :D
1

With your document structure it will take two trips.

However generally when using a nosql db like mongo, one should denormalize. Instead of avoiding repetitive data, repeating in mongo is good and standard practice.

try:

{
     _id: 1,
     name: 'John',
     likes: ['2','3','4'],
     liked_by: ['2''4'] 
},
{
     _id: 2,
     name: 'Michelle',
     likes: ['1','4'],
     liked_by: ['1''4'] 
}

2 Comments

Denormalizing is often a good practice in NoSql, true, but be careful to weigh the pros and cons. Does this case actually save you anything? If your next question is "What are the names of the people who like me?" then you're going to have to do that second pass anyway, so you might as well keep your data more normalized and save some implementation headaches. As I pointed out in another question, if you want to do this sort of thing the right way, you should use a graph database like neo4j.
I do need to keep my data normalized because i need the name and all the other details from the users. Another thing is, i can't really use 'liked_by' coz i'm actually using like friends on facebook, and in the time i like some of my friends they might not be in the database just yet, so i couldnt add the liked_by to those ppl that are not registered yet. i thought about using map reduce but i dont think this would really solve my problem. I was trying to avoid the 2 steps, but i don't see how!
1

You can now use $lookup:

Usage:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.