首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >右外连接在聚合管道中

右外连接在聚合管道中
EN

Stack Overflow用户
提问于 2017-08-25 10:24:23
回答 1查看 1.8K关注 0票数 2

我有两个集合,我们称它们为CatsParties,有以下模式:

代码语言:javascript
复制
{ name: String }

聚会

代码语言:javascript
复制
{ date: Date, attendants: [{ cat: { ref: 'Cat' }, role: String }] }

其中,role象征着其他一些属性,比如,参加会议的猫是否是VIP成员。

现在我想要一份所有猫的名单(即使是那些从来没有参加过任何聚会的可怜的猫咪),而对于每一只猫,我想要一张它曾经为至少一个派对扮演的角色的清单。此外,我希望这个完整的列表按照(每只猫)最后一次参加派对的date排序,而那些从未参加过任何派对的猫都是最后一个。

这给我带来了以下问题:

  • Parties上的阿格格把从未参加过派对的小猫排除在外。
  • Cats上进行聚合是错误的,因为我不能让猫参加$lookup聚会,因为该信息位于子文档数组中。

我目前拥有的管道给了我所有的猫,他们至少参加了一次聚会,列出了他们的角色,但没有按照上次参加的派对进行排序。事实上,我可以忍受把从未参加过派对的猫排除在外,但是分类对我来说是至关重要的:

代码语言:javascript
复制
Party.aggregate([
        { $unwind: '$attendants' },
        { $project: { role: '$attendants.role', cat: '$attendants.cat' } },
        {
            $group: {
                _id: '$cat',
                roles: { $addToSet: '$role' }
            }
        },
        {
            $lookup: {
                from: 'cats',
                localField: '_id',
                foreignField: '_id',
                as: 'cat'
            }
        },
        { $unwind: '$cat' },
        // (*)
        { $addFields: { 'cat.roles': '$roles' } },
        { $replaceRoot: { newRoot: '$cat' } }
])

我目前的想法基本上是在(*)的外部加入,添加猫参加过的聚会的列表,$project表示派对日期,然后$group使用$max获取最新的日期。然后,我可以$unwind,现在,一个元素数组和$sort在它的最后。

问题是在mongo,AFAIK中不存在正确的外部连接,而且我不知道如何在管道中获得每只猫的派对列表。

为了澄清,预期的输出应该类似于

代码语言:javascript
复制
[
    {
        "_id": "59982d3c7ca25936f8c327c8",
        "name": "Mr. Kitty",
        "roles": ["vip", "birthday cat"],
        "dateOfLastParty": "2017-06-02"
    },
    {
        "_id": "59982d3c7ca25936f8c327c9",
        "name": "Snuffles",
        "roles": ["best looking cat"],
        "dateOfLastParty": "2017-06-01"
    },
    ...
    {
        "_id": "59982d3c7ca25936f8c327c4",
        "name": "Sad Face McLazytown",
        "roles": [],
        "dateOfLastParty": null
    },
]
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-08-25 11:49:42

如前所述,您希望“cat”因此使用Cat模型,并执行实际上是$lookup固有的“左外部连接”,而不是要求来自相反集合的“右外部连接”,因为此时MongoDB不可能使用“右外部连接”。

作为一个“左连接”,它也要实用得多,因为您希望“cat”作为您的主要输出源。当链接到"Party“时,唯一要考虑的是,每个"Cat”都列在一个数组中,因此您将得到整个文档。因此,所需要做的就是在$lookup之后的“后置处理”中,您只需为当前cat的匹配条目“过滤”数组内容。

幸运的是,我们使用$arrayElemAt$indexOfArray获得了很好的特性,这使得我们能够进行精确的提取:

代码语言:javascript
复制
let kitties = await Cat.aggregate([
  { '$lookup': {
    'from': Party.collection.name,
    'localField': '_id',
    'foreignField': 'attendants.cat',
    'as': 'parties'
  }},
  { '$replaceRoot': {
    'newRoot': {
      '$let': {
        'vars': {
          'parties': {
            '$map': {
              'input': '$parties',
              'as': 'p',
              'in': {
                'date': '$$p.date',
                'role': {
                  '$arrayElemAt': [
                    '$$p.attendants.role',
                    { '$indexOfArray': [ '$$p.attendants.cat', '$_id' ] }
                  ]
                }
              }
            }
          }
        },
        'in': {
          '_id': '$_id',
          'name': '$name',
          'roles': '$$parties.role',
          'dateOfLastParty': { '$max': '$$parties.date' }
        }
      }
    }
  }}
]);

因此,我的“最优”处理的概念实际上在这里使用了$replaceRoot,因为您可以在$let语句下定义整个文档。我这样做的原因是,我们可以从以前的"parties"中获取$lookup数组的输出,并对每个条目进行整形,为当前的"kitty“提取匹配的"role"数据。实际上,我们可以自己做一个变量。

创建“数组变量”的原因是,我们可以使用$max将“最大/最后”日期属性提取为“单数”,并将“角色”值提取为“数组”。这使得很容易定义您想要的字段。

因为它是从Cat开始的“左加入”,那么那些错过了所有各方的可怜的小猫仍然存在,并且仍然有理想的输出。

两个聚合管道阶段。还有什么可以更简单的!

作为一份完整清单:

代码语言:javascript
复制
const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/catparty',
      options = { useMongoClient: true };

const catSchema = new Schema({
  name: String
});

const partySchema = new Schema({
  date: Date,
  attendants: [{
    cat: { type: Schema.Types.ObjectId, ref: 'Cat' },
    role: String
  }]
});

const Cat = mongoose.model('Cat', catSchema);
const Party = mongoose.model('Party', partySchema);

function log(data) {
  console.log(JSON.stringify(data,undefined,2))
}


(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

    // Clean collections
    await Promise.all(
      Object.keys(conn.models).map( m => conn.models[m].remove({}) )
    );


    var cats = await Cat.insertMany(
      ['Fluffy', 'Snuggles', 'Whiskers', 'Socks'].map( name => ({ name }) )
    );

    cats.shift();
    cats = cats.map( (cat,idx) =>
      ({ cat: cat._id, role: (idx === 0) ? 'Host' : 'Guest' })
    );
    log(cats);

    let party = await Party.create({
      date: new Date(),
      attendants: cats
    });

    log(party);

    let kitties = await Cat.aggregate([
      { '$lookup': {
        'from': Party.collection.name,
        'localField': '_id',
        'foreignField': 'attendants.cat',
        'as': 'parties'
      }},
      { '$replaceRoot': {
        'newRoot': {
          '$let': {
            'vars': {
              'parties': {
                '$map': {
                  'input': '$parties',
                  'as': 'p',
                  'in': {
                    'date': '$$p.date',
                    'role': {
                      '$arrayElemAt': [
                        '$$p.attendants.role',
                        { '$indexOfArray': [ '$$p.attendants.cat', '$_id' ] }
                      ]
                    }
                  }
                }
              }
            },
            'in': {
              '_id': '$_id',
              'name': '$name',
              'roles': '$$parties.role',
              'dateOfLastParty': { '$max': '$$parties.date' }
            }
          }
        }
      }}
    ]);

    log(kitties);


  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})();

以及示例输出:

代码语言:javascript
复制
[
  {
    "_id": "59a00d9528683e0f59e53460",
    "name": "Fluffy",
    "roles": [],
    "dateOfLastParty": null
  },
  {
    "_id": "59a00d9528683e0f59e53461",
    "name": "Snuggles",
    "roles": [
      "Host"
    ],
    "dateOfLastParty": "2017-08-25T11:44:21.903Z"
  },
  {
    "_id": "59a00d9528683e0f59e53462",
    "name": "Whiskers",
    "roles": [
      "Guest"
    ],
    "dateOfLastParty": "2017-08-25T11:44:21.903Z"
  },
  {
    "_id": "59a00d9528683e0f59e53463",
    "name": "Socks",
    "roles": [
      "Guest"
    ],
    "dateOfLastParty": "2017-08-25T11:44:21.903Z"
  }
]

您应该能够看到这些“角色”值实际上是如何成为一个包含更多数据的数组的。如果您需要它成为“唯一列表”,那么只需使用$setDifference包装,如下所示:

代码语言:javascript
复制
'roles': { '$setDifference': [ '$$parties.role', [] ] },

这也包括在内

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45879348

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档