简体   繁体   中英

How to resolve the many-to-many relation keeping the order of ID array in mongoDB

I have two collections posts and tags on mongoDB. There is a many-to-many relationship between these collections. A post can belong to some tags, and a tag can contain some posts.

I am looking for an efficient query method to join posts to tags keeping the order of postIds .

If the data schema is inappropriate, I can change it.

The mongoDB version is 3.6.5

Sample data

db.posts.insertMany([
  { _id: 'post001', title: 'this is post001' },
  { _id: 'post002', title: 'this is post002' },
  { _id: 'post003', title: 'this is post003' }
])

db.tags.insertMany([
  { _id: 'tag001', postIds: ['post003', 'post001', 'post002'] }
])

Desired result

{
  "_id": "tag001",
  "postIds": [ "post003", "post001", "post002" ],
  "posts": [
    { "_id": "post003", "title": "this is post003" },
    { "_id": "post001", "title": "this is post001" },
    { "_id": "post002", "title": "this is post002" }
  ]
}

What I tried

I tried a query which use $lookup .

db.tags.aggregate([
  { $lookup: {
      from: 'posts',
      localField: 'postIds',
      foreignField: '_id',
      as: 'posts'
  }}
])

However I got a result which is different from I want.

{
  "_id": "tag001",
  "postIds": [ "post003", "post001", "post002" ],
  "posts": [
    { "_id": "post001", "title": "this is post001" },
    { "_id": "post002", "title": "this is post002" },
    { "_id": "post003", "title": "this is post003" }
  ]
}

In MongoDB you would attempt to model your data such that you avoid joins (as in $lookup s) alltogether, eg by storing the tags alongside the posts .

db.posts.insertMany([
  { _id: 'post001', title: 'this is post001', tags: [ "tag001", "tag002" ] },
  { _id: 'post002', title: 'this is post002', tags: [ "tag001" ] },
  { _id: 'post003', title: 'this is post003', tags: [ "tag002" ] }
])

With this structure in place you could get the desired result like this:

db.posts.aggregate([{
    $unwind: "$tags"
}, {
    $group: {
        _id: "$tags",
        postsIds: {
            $push: "$_id"
        },
        posts: {
            $push: "$$ROOT"
        }
    }
}])

In this case, I would doubt that you even need the postIds field in the result as it would be contained in the posts array anyway.

You can use a combination of $map and $filter to re-order elements in the posts array in a projection stage:

db.tags.aggregate([
    { $lookup: {
          from: 'posts',
          localField: 'postIds',
          foreignField: '_id',
          as: 'posts'
    } },
    { $project: {
        _id: 1,
        postIds: 1,
        posts: { $map: { 
            input: "$postIds", 
            as: "postId", 
            in: {
                $arrayElemAt: [ { $filter: { 
                    input: "$posts", 
                    as: "post", 
                    cond: { $eq: ["$$post._id", "$$postId"] } 
                } }, 0 ] 
            } 
        } }
    } }
])

The missing posts will be filled with null to keep index consistent with postIds .

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