Mongoose: find() ignore duplicate values

你。 提交于 2020-06-28 03:59:11

问题


I have a "chat" mongoose Schema which has the following properties:

const schema = mongoose.Schema({
    ...
    recipient: {
        type: mongoose.Types.ObjectId,
        required: true,
        ref: 'User',
    },
    sender: {
        type: mongoose.Types.ObjectId,
        required: true,
        ref: 'User',
    },
    content: {
        type: String,
    },
    ...
}, {
    timestamps: true,
});

Generally, I want to fetch the last message of each coversation that a user has. Meaning that I need to provide a user id (that can be either stored in the sender or recipient field) and get back the last message (indicated by createdAt) the user had with each of the other users.

Example: Let's say I have the following documents:

[
  {
    recipient: "One",
    sender: "Two",
    createdAt: ISODate("2014-01-01T08:00:00Z"),

  },
  {
    recipient: "One",
    sender: "Three",
    createdAt: ISODate("2014-02-15T08:00:00Z")
  },
  {
    recipient: "Two",
    sender: "One",
    createdAt: ISODate("2014-02-16T12:05:10Z")
  }
]

Instering "One" as input - the desired result from Model.find(...) is:

[
  {
    recipient: "One",
    sender: "Three",
    createdAt: ISODate("2014-02-15T08:00:00Z")
  },
  {
    recipient: "Two",
    sender: "One",
    createdAt: ISODate("2014-02-16T12:05:10Z")
  }
]

回答1:


Using the example data:

[
  {
    recipient: "One",
    sender: "Two",
    createdAt: ISODate("2014-01-01T08:00:00Z"),
    content: "Hi Mr. One! - Two"
  },
  {
    recipient: "One",
    sender: "Three",
    createdAt: ISODate("2014-02-15T08:00:00Z"),
    content: "Hello One! - Three"
  },
  {
    recipient: "Two",
    sender: "One",
    createdAt: ISODate("2014-02-16T12:05:10Z"),
    content: "Whats up, Two? - One"
  }
]

Have a look at the following aggregation: https://mongoplayground.net/p/DTSDWX3aLWe

It...

  • Uses $match to filter all messages by recipient or sender. Returns the ones matching the current user (One)
  • Adds a conversationWith field using $addFields that contains the recipient if it is a message to user One or the sender if it is a message sent by user One
  • Sorts the messages by date using $sort
  • Groups all the messages using $group by the new conversationWith field and returns the most recent message as firstMessage

The full aggregation pipeline:

db.collection.aggregate([
  {
    $match: {
      $and: [
        {
          $or: [
            {
              recipient: "One"
            },
            {
              sender: "One"
            }
          ],

        },

      ]
    }
  },
  {
    $addFields: {
      conversationWith: {
        $cond: {
          if: {
            $eq: [
              "$sender",
              "One"
            ]
          },
          then: "$recipient",
          else: "$sender"
        }
      }
    }
  },
  {
    $sort: {
      createdAt: -1
    }
  },
  {
    $group: {
      _id: "$conversationWith",
      firstMessage: {
        $first: "$$ROOT"
      }
    }
  }
])

Using mongoplayground you can remove the aggregation steps one-by-one to see what each step does.

Try:

  • Only the $match step
  • $match + $addFields
  • $match + $addFields + $sort
  • [..]

for best understanding.




回答2:


You can do this by aggregation as shown in below query

Working example - https://mongoplayground.net/p/wEi4Y6IZJ2v

db.collection.aggregate([
  {
    $sort: {
      recipient: 1,
      createdAt: 1
    }
  },
  {
    $group: {
      _id: "$recipient",
      createdAt: {
        $last: "$createdAt"
      }
    }
  },
  {
    $project: {
      _id: 0,
      recipient: "$_id",
      createdAt: "$createdAt"
    }
  }
])

If you have two fields to match, then you can use below query

Working Example - https://mongoplayground.net/p/Rk5MxuphLOT

db.collection.aggregate([
  {
    $match: {
      $or: [
        {
          sender: "One"
        },
        {
          recipient: "One"
        }
      ]
    }
  },
  {
    $addFields: {
      other: {
        $cond: {
          if: {
            $eq: [
              "$recipient",
              "One"
            ]
          },
          then: "$sender",
          else: "$recipient"
        }
      }
    }
  },
  {
    $sort: {
      createdAt: 1
    }
  },
  {
    $group: {
      _id: "$other",
      createdAt: {
        $last: "$createdAt"
      },
      recipient: {
        $last: "$recipient"
      },
      sender: {
        $last: "$sender"
      }
    }
  },
  {
    $project: {
      _id: 0,
      recipient: "$recipient",
      sender: "$sender",
      createdAt: "$createdAt"
    }
  }
])



回答3:


if you want to rely on mongodb to filter out duplicates, its better to never even allow to have duplicates by creating a unique index.

since it seems your recipients are nested in a parent scheme, I would do the filtering of the duplicates in nodejs since its hard to untangle this in a mongodb query. If you need to use mongodb, use the distinct function or the aggregate pipeline and be inspired by this article



来源:https://stackoverflow.com/questions/61969645/mongoose-find-ignore-duplicate-values

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!