简体   繁体   中英

Mongodb $and operator doesn't work as expected

I have a basic social media app that allows users to follow each other. When need to find some specific persons "followers", i look for users who have the id of this specific person in their "following"s;

{
          $and: [
            {
              $and: [
                {
                  "following.userId": mongoose.Types.ObjectId(targetId)
                },
                {
                  "following.following": true
                }
              ]
            },
            {
              $or: [{ firstName: firstNameRegex }, { lastName: lastNameRegex }]
            },
            { blockedUsers: { $nin: mongoose.Types.ObjectId(req.userId) } }
          ]
        };

If a user stops following someone, "following.following" property becomes false. When run this query, I get every person who has followed that specific person in some time without looking "following.following": true property at all. "following.following" doesn't evaluate the times when "following.userId" matches, rather it looks for whole array and matches if some of them has "following.following" true.

Here is the file structure

I've managed to solve this problem using $elemMatch operator like this:

{
          $and: [
            {
              following: {
                $elemMatch: {
                  userId: mongoose.Types.ObjectId(targetId),
                  following: true
                }
              }
            },
            {
              $or: [{ firstName: firstNameRegex }, { lastName: lastNameRegex }]
            },
            { blockedUsers: { $nin: mongoose.Types.ObjectId(req.userId) } }
          ]
        };

You are querying embedded array documents, simple $and query will not be helpful here the way you are using it.

Basically we want to match multiple fields from single embedded documents in an array.

so let's consider this example:

For simplicity, I have added a few fields from my understanding, and considering you are facing an issue with $and query, I will and accordingly assuming rest query does not change and works.

db.followers.find().pretty()
{
    "_id" : ObjectId("5d984403d933b7b079038ca9"),
    "userId" : "1",
    "followers" : [
        {
            "fId" : "4",
            "following" : true
        },
        {
            "fId" : "2",
            "following" : true
        },
        {
            "fId" : "3",
            "following" : false
        }
    ]
}
{
    "_id" : ObjectId("5d984422d933b7b079038caa"),
    "userId" : "2",
    "followers" : [
        {
            "fId" : "1",
            "following" : true
        },
        {
            "fId" : "3",
            "following" : false
        },
        {
            "fId" : "4",
            "following" : false
        }
    ]
}
{
    "_id" : ObjectId("5d984432d933b7b079038cab"),
    "userId" : "3",
    "followers" : [
        {
            "fId" : "1",
            "following" : true
        },
        {
            "fId" : "2",
            "following" : true
        },
        {
            "fId" : "4",
            "following" : true
        }
    ]
}
{
    "_id" : ObjectId("5d984446d933b7b079038cac"),
    "userId" : "4",
    "followers" : [
        {
            "fId" : "1",
            "following" : false
        },
        {
            "fId" : "2",
            "following" : true
        },
        {
            "fId" : "3",
            "following" : true
        }
    ]
}

ANS 1:

db.followers.find({followers:{ "fId": "1", "following": true  }}).pretty()
{
    "_id" : ObjectId("5d984422d933b7b079038caa"),
    "userId" : "2",
    "followers" : [
        {
            "fId" : "1",
            "following" : true
        },
        {
            "fId" : "3",
            "following" : false
        },
        {
            "fId" : "4",
            "following" : false
        }
    ]
}
{
    "_id" : ObjectId("5d984432d933b7b079038cab"),
    "userId" : "3",
    "followers" : [
        {
            "fId" : "1",
            "following" : true
        },
        {
            "fId" : "2",
            "following" : true
        },
        {
            "fId" : "4",
            "following" : true
        }
    ]
}

Notice how the followers array is used in the query. ref enter link description here

In your case, we can modify your query like this:

   {
      $and: [                // by default it's $and only, you don't have to mention explicitly
        {
          $and: [            // you can even remove this $and
            "following":
            {
              "userId": mongoose.Types.ObjectId(targetId),
              "following": true
            }
          ]
        },
        {
          $or: [{ firstName: firstNameRegex }, { lastName: lastNameRegex }]
        },
        { blockedUsers: { $nin: mongoose.Types.ObjectId(req.userId) } }
      ]
 }

ANS 2:

You can use $elemMatch

$elemMatch is used to query multiple fields from a single document in an array.

db.followers.find({followers: {$elemMatch: { "fId": "1", "following": true  }}}).pretty()
{
    "_id" : ObjectId("5d984422d933b7b079038caa"),
    "userId" : "2",
    "followers" : [
        {
            "fId" : "1",
            "following" : true
        },
        {
            "fId" : "3",
            "following" : false
        },
        {
            "fId" : "4",
            "following" : false
        }
    ]
}
{
    "_id" : ObjectId("5d984432d933b7b079038cab"),
    "userId" : "3",
    "followers" : [
        {
            "fId" : "1",
            "following" : true
        },
        {
            "fId" : "2",
            "following" : true
        },
        {
            "fId" : "4",
            "following" : true
        }
    ]
}

Query for you will be:

{
      $and: [
        {
            "following":
                {$elemMatch: {
                    "userId": mongoose.Types.ObjectId(targetId),
                    "following": true
                }
             }
        },
        {
          $or: [{ firstName: firstNameRegex }, { lastName: lastNameRegex }]
        },
        { blockedUsers: { $nin: mongoose.Types.ObjectId(req.userId) } }
      ]
}

BUT THIS WILL BE WRONG (See Query Below):

db.followers.find({"followers.fId": "1", "followers.following": true  }).pretty()
{
    "_id" : ObjectId("5d984422d933b7b079038caa"),
    "userId" : "2",
    "followers" : [
        {
            "fId" : "1",
            "following" : true
        },
        {
            "fId" : "3",
            "following" : false
        },
        {
            "fId" : "4",
            "following" : false
        }
    ]
}
{
    "_id" : ObjectId("5d984432d933b7b079038cab"),
    "userId" : "3",
    "followers" : [
        {
            "fId" : "1",
            "following" : true
        },
        {
            "fId" : "2",
            "following" : true
        },
        {
            "fId" : "4",
            "following" : true
        }
    ]
}
{
    "_id" : ObjectId("5d984446d933b7b079038cac"),
    "userId" : "4",
    "followers" : [
        {
            "fId" : "1",
            "following" : false
        },
        {
            "fId" : "2",
            "following" : true
        },
        {
            "fId" : "3",
            "following" : true
        }
    ]
}

Note

To see only match documents, you can use

db.followers.find({followers: {$elemMatch: { "fId": "1", "following": true  }}},{"followers.$": 1}).pretty()


db.followers.find({followers: {$elemMatch: { "fId": "1", "following": true  }}},{"followers.$": 1}).pretty()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM