Home > Software design >  MongoDb query to exclude omission of rows based on criteria
MongoDb query to exclude omission of rows based on criteria

Time:08-13

In below example, looking for new partner suggestions for user abc. abc has already sent a request to 123 so that can be ignored. rrr has sent request to abc but rrr is in the fromUser field so rrr is still a valid row to be shown as suggestion to abc

I have two collections:

  1. User collection
[
    {
      _id: "abc",
      name: "abc",
      group: 1
    },
    {
      _id: "xyz",
      name: "xyyy",
      group: 1
    },
    {
      _id: "123",
      name: "yyy",
      group: 1
    },
    {
      _id: "rrr",
      name: "tttt",
      group: 1
    },
    {
      _id: "eee",
      name: "uuu",
      group: 1
    }   
]
  1. Partnership collection (if users have already partnered)
[
    {
      _id: "abc_123",
      fromUser: "abc",
      toUser: "123"
    },
    {
      _id: "rrr_abc",
      fromUser: "rrr",
      toUser: "abc"
    },
    {
      _id: "xyz_rrr",
      fromUser: "xyz",
      toUser: "rrr"
    }   
] 

My query below excludes the user rrr but it should not because its not listed in toUser field in the partnership collection corresponding to the user abc.

How to modify this query to include user rrr in this case?

db.users.aggregate([
  {
    $match: {
      group: 1,
      _id: {
        $ne: "abc"
      }
    }
  },
  {
    $lookup: {
      from: "partnership",
      let: {
        userId: "$_id"
      },
      as: "prob",
      pipeline: [
        {
          $set: {
            users: [
              "$fromUser",
              "$toUser"
            ],
            u: "$$userId"
          }
        },
        {
          $match: {
            $expr: {
              $and: [
                {
                  $in: [
                    "$$userId",
                    "$users"
                  ]
                },
                {
                  $in: [
                    "abc",
                    "$users"
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  },
  {
    $match: {
      "prob.0": {
        $exists: false
      }
    }
  },
  {
    $sample: {
      size: 1
    }
  },
  {
    $unset: "prob"
  }
])

https://mongoplayground.net/p/utGMeHFRGmt

CodePudding user response:

Your current query does not allow creating an existing connection regardless of the connection direction. If the order of the connection is important use:

db.users.aggregate([
  {$match: {
      group: 1,
      _id: {$ne: "abc"}
    }
  },
  {$lookup: {
      from: "partnership",
      let: { userId: {$concat: ["abc", "_", "$_id"]}},
      as: "prob",
      pipeline: [{$match: {$expr: {$eq: ["$_id", "$$userId"]}}}]
    }
  },
  {$match: {"prob.0": {$exists: false}}},
  {$sample: {size: 1}},
  {$unset: "prob"}
])

See how it works on the playground example

CodePudding user response:

For MongoDB 5 and later, I'd propose the following aggregation pipeline:

db.users.aggregate([
  {
    $match: {
      group: 1,
      _id: {
        $ne: "abc"
      }
    }
  },
  {
    $lookup: {
      from: "partnership",
      as: "prob",
      localField: "_id",
      foreignField: "toUser",
      pipeline: [
        {
          $match: {
            fromUser: "abc",
          }
        }
      ]
    }
  },
  {
    $match: {
      "prob.0": {
        $exists: false
      }
    }
  },
  {
    $unset: "prob"
  }
])

The following documents are returned (full result without the $sample stage):

[
  {
    "_id": "eee",
    "group": 1,
    "name": "uuu"
  },
  {
    "_id": "rrr",
    "group": 1,
    "name": "tttt"
  },
  {
    "_id": "xyz",
    "group": 1,
    "name": "xyyy"
  }
]

The main difference is that the lookup connects the collections by the toUser field (see localField, foreignField) and uses a minimal pipeline to restrict the results further to only retrieve the requests from the current user document to "abc".

See this playground to test.


When using MongoDB < 5, you cannot use localField and foreignField to run the pipeline only on a subset of the documents in the * from* collection. To overcome this, you can use this aggregation pipeline:

db.users.aggregate([
  {
    $match: {
      group: 1,
      _id: {
        $ne: "abc"
      }
    }
  },
  {
    $lookup: {
      from: "partnership",
      as: "prob",
      let: {
        userId: "$_id"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                {
                  $eq: [
                    "$fromUser",
                    "abc"
                  ]
                },
                {
                  $eq: [
                    "$toUser",
                    "$$userId"
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  },
  {
    $match: {
      "prob.0": {
        $exists: false
      }
    }
  },
  {
    $unset: "prob"
  }
])

The results are the same as for the upper pipeline.

See this playground to test.

CodePudding user response:

For another, another way, this query starts from the partnership collection, finds which users to exclude, and then does a "$lookup" for everybody else. The remainder is just output formatting, although it looks like you may want to add a "$sample" stage at the end.

db.partnership.aggregate([
  {
    "$match": {
      "fromUser": "abc"
    }
  },
  {
    "$group": {
      "_id": null,
      "exclude": {"$push": "$toUser" }
    }
  },
  {
    "$lookup": {
      "from": "users",
      "let": {
        "exclude": {"$concatArrays": [["abc"], "$exclude"]
        }
      },
      "pipeline": [
        {
          "$match": {
            "$expr": {
              "$not": {"$in": ["$_id", "$$exclude"]}
            }
          }
        }
      ],
      "as": "output"
    }
  },
  {
    "$project": {
      "_id": 0,
      "output": 1
    }
  },
  {"$unwind": "$output"},
  {"$replaceWith": "$output"}
])

Try it on mongoplayground.net.

  • Related