【问题标题】:Mongoose populate virtual with sort and limitMongoose 使用排序和限制填充虚拟
【发布时间】:2017-12-06 05:29:03
【问题描述】:

我正在尝试编写一个 mongoose 查询来检索一组资产以及这些资产的最新交易。交易与资产位于单独的集合中。

为此,我首先在资产模型中创建了一个虚拟数组,以将资产链接到交易。

schema.virtual('transactions', {
    ref: 'transaction',
    localField: '_id',
    foreignField: '_asset',
    justOne: false
})

然后我在 node.js express 控制器中使用 .populate 进行查询(注意硬编码的“limit: 1”会在某个时候变为 N):

exports.getList = function (req, res) {
    Model
        .find({}, { __v: 0 })
        .populate({
            path: 'transactions',
            options: { sort: { 'created_at': -1}, limit: 1},
        })
        .lean()
        .exec(function (err, model) {
            if (err)
                res.send(err);
            res.json(model);
        });
  }

在我的测试系统中有 3 个资产,每个资产都有一堆交易,它会返回前两个资产的旧交易,而第三个则什么也不返回。

当我删除“limit:1”并返回所有交易时,它会正确排序并返回所有三种资产的交易。

我相信我遇到了这个错误:

https://github.com/Automattic/mongoose/issues/4321

对优雅的解决方法有什么想法吗?
退回所有交易将不是一个可行的长期解决方案。

【问题讨论】:

  • 如果您真的希望始终返回“最新”,那么您可以首先使用$sort$position 以这种方式“更新”。然后你可以简单地在投影中使用$slice“查询”。
  • 这样的好处是,即使在.populate() 上使用“选项”,初始“查询”仍然实际上还是会返回整个数组。如果您实际上以这种方式“更新”,然后是$slice,则实际上在查询中返回“最新”。所以.populate() 在这种情况下并不像你认为的那样“限制”。
  • 实际上再看一遍,我注意到那里的.virtual(),这增加了问题。您似乎期望的那种结果只有在通过.aggregate() 查询“相关”数据时才有可能,因为它是“每个父资产的最后一个”,这是一个“聚合” ”。 .populate() 方法不会这样做,当前版本的 MongoDB 也不是可行的方法。您的用例可能更适合将“最近的交易”实际保存在父级的实际数组中,而不是“只是”一个虚拟属性。

标签: node.js mongodb mongoose mongodb-query


【解决方案1】:

对于那些在 2021 年阅读本文的人,截至 mongoose 5.12.3 可以在模型中完成以下操作:

schema.virtual('transactions', {
    ref: 'transaction',
    localField: '_id',
    foreignField: '_asset',
    justOne: false,
    options: { sort: { 'createdAt': -1}, limit: 1},
})

然后在控制器中:

exports.getList = function (req, res) {
    Model
        .find({}, { __v: 0 })
        .populate({
            path: 'transactions',
        })
        .lean()
        .exec(function (err, model) {
            if (err)
                res.send(err);
            res.json(model);
        });
  }

【讨论】:

    【解决方案2】:

    这确实是一个卷曲的问题。这里的基础是 .populate() 并且当然不是“填充虚拟”,它的设计目的不是像您在这里期望的那样工作。

    问题说明

    本质上,.populate() 本质上是向 MongoDB 发出的另一个查询,用于检索相关数据。为此,它基本上发出带有$in 的查询,其中包含要匹配的目标的所有“相关字段”值。

    "issue 4321" 的核心在于,使用"sort""limit" 等选项,需要提供$in 参数的实际查询实际上是.aggregate() 语句将能够“获取为每个键分组的最后一个 n 结果”。这实际上并不是 mongoose 目前向 MongoDB 发布的问题,目前按可用操作对 n 项进行分组也不实际。

    您可以手动使用.aggregate() 解决此问题,如所提供列表末尾所示,但当然实际上仅在少数情况下。

      // Get latest transactions for each master
      Transaction.aggregate([
        { '$match': {
          '_asset': {
            '$in': masters.map(m => m._id)
          }
        }},
        { '$sort': { '_asset': 1, 'createdAt': -1 } },
        { '$group': {
          '_id': '$_asset',
          'amount': { '$first': '$amount' },
          'createdAt': { '$first': '$createdAt' },
          'updatedAt': { '$first': '$updatedAt' },
          'did': { '$first': '$_id' }
        }},
        { '$project': {
          '_id': '$did',
          '_asset': '$_id',
          'amount': 1,
          'createdAt': 1,
          'updatedAt': 1
        }}
      ])
    

    它不是很好,也不是一个真正高效的解决方案,但比其他类似的替代方案要好。我相信还有更好的方法。

    更好的解决方案

    对于您的情况,我怀疑有许多类似的情况,您不希望在父文档中包含“完整”交易列表,即使作为参考,因为产生的数组的潜在大小。这种“反模式”通常是“虚拟填充”,实际上$lookup 旨在避免。

    在您的 “获取最新交易” 的特定用例中,这些都不是可行的解决方案。因为两者本质上都需要查看“所有”事务,然后只从它们检索n 结果。

    因此,这里的“最新”或实际上是“最新”的案例实际上会退回到“有限数量”的“嵌入”(至少是引用),以便提供可行的解决方案。所以建议就是这样做,并在父级本身中保留一个"recent" 交易列表。这会在包含的场景中为您提供一个文档,如下所示:

    {
            "_id" : ObjectId("5959e34adf833e1451a32661"),
            "__v" : 0,
            "name" : "One",
            "recent" : [
                    ObjectId("5959e34bdf833e1451a32676"),
                    ObjectId("5959e34bdf833e1451a32674"),
                    ObjectId("5959e34bdf833e1451a32672"),
                    ObjectId("5959e34bdf833e1451a32670"),
                    ObjectId("5959e34bdf833e1451a3266e")
            ]
    }
    

    请注意,这些不是“所有”相关交易,而只是“最新”交易。关键是只保留一个适合目的的“小”列表。

    通过这种方式,您可以直接查询“父项”,而只需从包含"recent" 项的数组中查询$slice。在清单中,我这样做:

    Master.find().select({ 'recent': { '$slice': 1 } })
    

    这将返回数组中的“最新”条目,而无需对服务器进行任何其他查询。在这种情况下,它是“最新的”,因为我们在写入包含所有内容的 "transactions" 集合的同时将项目“添加”到该数组中:

    Transaction.create({ _asset: master._id, amount: data.amount })
      .then(transaction =>
        Master.update(
          { _id: transaction._asset },
          { "$push": {
            "recent": {
              "$each": [transaction._id],
              "$position": 0,
              "$slice": 5
            }
          }}
        )
    

    关键元素是 $push 到父数组中的数组,它在数组的开头用 $position 修改为“preprend”,因此“第一个”项目始终是“最新”事务与父级相关。

    然后$slice 修饰符用于将"recent" 数组保持在n 项的限制。因此,随着新项目的添加,“最旧”的项目将被“推出”列表。

    这里还有另外一个实际用途,就是在分页场景中列出“master”和“transactions”时,第一个请求可以直接使用"recent"数组。然后对新“页面”的附加请求,可以简单地通过$nin 过滤掉"recent" 数组中包含的项目,并使用常规的.skip().limit() 或替代“范围”分页实践来检索每个“页面”结果。

    要完整演示所有概念,请参阅下面的清单和所有生成的结果。

    演示列表:

    const async = require('async'),
          mongoose = require('mongoose'),
          Schema = mongoose.Schema;
    
    mongoose.set('debug',true);
    mongoose.Promise = global.Promise;
    
    mongoose.connect('mongodb://localhost/prepend');
    
    const transactionSchema = new Schema({
      _asset: { type: Schema.Types.ObjectId, ref: 'Master' },
      amount: Number
    },{
      timestamps: {
        createdAt: 'createdAt'
      }
    });
    
    
    const Transaction = mongoose.model('Transaction', transactionSchema);
    
    const masterSchema = new Schema({
      name: String,
      recent: [{ type: Schema.Types.ObjectId, ref: 'Transaction' }]
    });
    
    masterSchema.virtual('transactions', {
      ref: 'Transaction',
      localField: '_id',
      foreignField: '_asset',
      justOne: false
    });
    
    
    const Master = mongoose.model('Master', masterSchema);
    
    function log(data) {
      console.log(JSON.stringify(data, undefined, 2))
    }
    
    async.series(
      [
        // Clean data
        (callback) =>
          async.each(mongoose.models,(model,callback) =>
            model.remove({},callback),callback),
    
        // Create Masters
        (callback) =>
          Master.insertMany(['One','Two'].map( name => ({ name })),callback),
    
        // Add 10 transactions to each master
        (callback) =>
          async.each(['One','Two'],(name,callback) =>
            async.eachSeries(
              Array.apply(null,Array(10)).map((e,i) => ({ name, amount: i+1 })),
              (data,callback) => {
                Master.findOne({ name: data.name })
                  .then(master =>
                    Transaction.create({ _asset: master._id, amount: data.amount })
                  )
                  .then(transaction =>
                    Master.update(
                      { _id: transaction._asset },
                      { "$push": {
                        "recent": {
                          "$each": [transaction._id],
                          "$position": 0,
                          "$slice": 5
                        }
                      }}
                    )
                  )
                  .then(res => callback())
                  .catch(callback)
              },
              callback
            ),
          callback),
    
        // Show populated recent 1 entry only
        (callback) =>
          Master.find().select({ 'recent': { '$slice': 1 } })
            .populate('recent').exec((err,results) => {
            if (err) callback(err);
            log(results);
            callback();
          }),
    
        // Populate recent - page 1 then fetch next page
        (callback) =>
          async.waterfall(
            [
              (callback) =>
                Master.findOne({ name: 'One' }).populate('recent')
                  .lean()
                  .exec((err,master) => {
                    if (err) callback(err);
                    log(master);
                    callback(null,{
                      _asset: master._id,
                      exclude: master.recent.map( r => r._id )
                    });
                  }),
    
              (options,callback) =>
                Transaction.find({
                  _asset: options._asset,
                  _id: { '$nin': options.exclude }
                }).sort({ 'createdAt': -1 }).limit(5)
                .exec((err,transactions) => {
                  if (err) callback(err);
                  log(transactions)
                  callback();
                })
    
            ],
            callback
          ),
    
        // Issue 4321 - Fix - Manual populate with aggregate
        (callback) =>
          Master.find().select('-recent').exec()
            .then(masters => {
              // Get latest transactions for each master
              Transaction.aggregate([
                { '$match': {
                  '_asset': {
                    '$in': masters.map(m => m._id)
                  }
                }},
                { '$sort': { '_asset': 1, 'createdAt': -1 } },
                { '$group': {
                  '_id': '$_asset',
                  'amount': { '$first': '$amount' },
                  'createdAt': { '$first': '$createdAt' },
                  'updatedAt': { '$first': '$updatedAt' },
                  'did': { '$first': '$_id' }
                }},
                { '$project': {
                  '_id': '$did',
                  '_asset': '$_id',
                  'amount': 1,
                  'createdAt': 1,
                  'updatedAt': 1
                }}
              ]).exec((err,transactions) => {
                // Map latest transactions to master
                masters = masters.map(
                  m => Object.assign(
                    m.toObject(),
                    {
                      transactions: transactions.filter(
                        t => t._asset.toHexString() === m._id.toHexString()
                      )
                    }
                  )
                );
    
                log(masters);
                callback();
              })
            }).catch(callback)
    
      ],
      (err) => {
        if (err) throw err;
        mongoose.disconnect();
      }
    );
    

    演示输出

    Mongoose: transactions.remove({}, {})
    Mongoose: masters.remove({}, {})
    Mongoose: masters.insertMany([ { __v: 0, name: 'One', _id: 5959e34adf833e1451a32661, recent: [] }, { __v: 0, name: 'Two', _id: 5959e34adf833e1451a32662, recent: [] } ], null)
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:14 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:14 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 1, _id: ObjectId("5959e34adf833e1451a32663"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 1, _id: ObjectId("5959e34adf833e1451a32664"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34adf833e1451a32664") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34adf833e1451a32663") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 2, _id: ObjectId("5959e34bdf833e1451a32665"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 2, _id: ObjectId("5959e34bdf833e1451a32666"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32666") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32665") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 3, _id: ObjectId("5959e34bdf833e1451a32667"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 3, _id: ObjectId("5959e34bdf833e1451a32668"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32668") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32667") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 4, _id: ObjectId("5959e34bdf833e1451a32669"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 4, _id: ObjectId("5959e34bdf833e1451a3266a"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a3266a") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32669") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 5, _id: ObjectId("5959e34bdf833e1451a3266b"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 5, _id: ObjectId("5959e34bdf833e1451a3266c"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a3266c") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a3266b") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 6, _id: ObjectId("5959e34bdf833e1451a3266d"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 6, _id: ObjectId("5959e34bdf833e1451a3266e"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a3266e") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a3266d") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 7, _id: ObjectId("5959e34bdf833e1451a3266f"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 7, _id: ObjectId("5959e34bdf833e1451a32670"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32670") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a3266f") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 8, _id: ObjectId("5959e34bdf833e1451a32671"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 8, _id: ObjectId("5959e34bdf833e1451a32672"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32672") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32671") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 9, _id: ObjectId("5959e34bdf833e1451a32673"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 9, _id: ObjectId("5959e34bdf833e1451a32674"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32674") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32673") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: masters.findOne({ name: 'Two' }, { fields: {} })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32662"), amount: 10, _id: ObjectId("5959e34bdf833e1451a32675"), __v: 0 })
    Mongoose: transactions.insert({ updatedAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), createdAt: new Date("Mon, 03 Jul 2017 06:25:15 GMT"), _asset: ObjectId("5959e34adf833e1451a32661"), amount: 10, _id: ObjectId("5959e34bdf833e1451a32676"), __v: 0 })
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32661") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32676") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.update({ _id: ObjectId("5959e34adf833e1451a32662") }, { '$push': { recent: { '$each': [ ObjectId("5959e34bdf833e1451a32675") ], '$slice': 5, '$position': 0 } } }, {})
    Mongoose: masters.find({}, { fields: { recent: { '$slice': 1 } } })
    Mongoose: transactions.find({ _id: { '$in': [ ObjectId("5959e34bdf833e1451a32676"), ObjectId("5959e34bdf833e1451a32675") ] } }, { fields: {} })
    [
      {
        "_id": "5959e34adf833e1451a32661",
        "__v": 0,
        "name": "One",
        "recent": [
          {
            "_id": "5959e34bdf833e1451a32676",
            "updatedAt": "2017-07-03T06:25:15.282Z",
            "createdAt": "2017-07-03T06:25:15.282Z",
            "_asset": "5959e34adf833e1451a32661",
            "amount": 10,
            "__v": 0
          }
        ]
      },
      {
        "_id": "5959e34adf833e1451a32662",
        "__v": 0,
        "name": "Two",
        "recent": [
          {
            "_id": "5959e34bdf833e1451a32675",
            "updatedAt": "2017-07-03T06:25:15.280Z",
            "createdAt": "2017-07-03T06:25:15.280Z",
            "_asset": "5959e34adf833e1451a32662",
            "amount": 10,
            "__v": 0
          }
        ]
      }
    ]
    Mongoose: masters.findOne({ name: 'One' }, { fields: {} })
    Mongoose: transactions.find({ _id: { '$in': [ ObjectId("5959e34bdf833e1451a32676"), ObjectId("5959e34bdf833e1451a32674"), ObjectId("5959e34bdf833e1451a32672"), ObjectId("5959e34bdf833e1451a32670"), ObjectId("5959e34bdf833e1451a3266e") ] } }, { fields: {} })
    {
      "_id": "5959e34adf833e1451a32661",
      "__v": 0,
      "name": "One",
      "recent": [
        {
          "_id": "5959e34bdf833e1451a32676",
          "updatedAt": "2017-07-03T06:25:15.282Z",
          "createdAt": "2017-07-03T06:25:15.282Z",
          "_asset": "5959e34adf833e1451a32661",
          "amount": 10,
          "__v": 0
        },
        {
          "_id": "5959e34bdf833e1451a32674",
          "updatedAt": "2017-07-03T06:25:15.264Z",
          "createdAt": "2017-07-03T06:25:15.264Z",
          "_asset": "5959e34adf833e1451a32661",
          "amount": 9,
          "__v": 0
        },
        {
          "_id": "5959e34bdf833e1451a32672",
          "updatedAt": "2017-07-03T06:25:15.216Z",
          "createdAt": "2017-07-03T06:25:15.216Z",
          "_asset": "5959e34adf833e1451a32661",
          "amount": 8,
          "__v": 0
        },
        {
          "_id": "5959e34bdf833e1451a32670",
          "updatedAt": "2017-07-03T06:25:15.195Z",
          "createdAt": "2017-07-03T06:25:15.195Z",
          "_asset": "5959e34adf833e1451a32661",
          "amount": 7,
          "__v": 0
        },
        {
          "_id": "5959e34bdf833e1451a3266e",
          "updatedAt": "2017-07-03T06:25:15.180Z",
          "createdAt": "2017-07-03T06:25:15.180Z",
          "_asset": "5959e34adf833e1451a32661",
          "amount": 6,
          "__v": 0
        }
      ]
    }
    Mongoose: transactions.find({ _id: { '$nin': [ ObjectId("5959e34bdf833e1451a32676"), ObjectId("5959e34bdf833e1451a32674"), ObjectId("5959e34bdf833e1451a32672"), ObjectId("5959e34bdf833e1451a32670"), ObjectId("5959e34bdf833e1451a3266e") ] }, _asset: ObjectId("5959e34adf833e1451a32661") }, { sort: { createdAt: -1 }, limit: 5, fields: {} })
    [
      {
        "_id": "5959e34bdf833e1451a3266c",
        "updatedAt": "2017-07-03T06:25:15.164Z",
        "createdAt": "2017-07-03T06:25:15.164Z",
        "_asset": "5959e34adf833e1451a32661",
        "amount": 5,
        "__v": 0
      },
      {
        "_id": "5959e34bdf833e1451a3266a",
        "updatedAt": "2017-07-03T06:25:15.135Z",
        "createdAt": "2017-07-03T06:25:15.135Z",
        "_asset": "5959e34adf833e1451a32661",
        "amount": 4,
        "__v": 0
      },
      {
        "_id": "5959e34bdf833e1451a32668",
        "updatedAt": "2017-07-03T06:25:15.080Z",
        "createdAt": "2017-07-03T06:25:15.080Z",
        "_asset": "5959e34adf833e1451a32661",
        "amount": 3,
        "__v": 0
      },
      {
        "_id": "5959e34bdf833e1451a32666",
        "updatedAt": "2017-07-03T06:25:15.039Z",
        "createdAt": "2017-07-03T06:25:15.039Z",
        "_asset": "5959e34adf833e1451a32661",
        "amount": 2,
        "__v": 0
      },
      {
        "_id": "5959e34adf833e1451a32664",
        "updatedAt": "2017-07-03T06:25:15.009Z",
        "createdAt": "2017-07-03T06:25:15.009Z",
        "_asset": "5959e34adf833e1451a32661",
        "amount": 1,
        "__v": 0
      }
    ]
    Mongoose: masters.find({}, { fields: { recent: 0 } })
    Mongoose: transactions.aggregate([ { '$match': { _asset: { '$in': [ 5959e34adf833e1451a32661, 5959e34adf833e1451a32662 ] } } }, { '$sort': { _asset: 1, createdAt: -1 } }, { '$group': { _id: '$_asset', amount: { '$first': '$amount' }, createdAt: { '$first': '$createdAt' }, updatedAt: { '$first': '$updatedAt' }, did: { '$first': '$_id' } } }, { '$project': { _id: '$did', _asset: '$_id', amount: 1, createdAt: 1, updatedAt: 1 } } ], {})
    [
      {
        "_id": "5959e34adf833e1451a32661",
        "__v": 0,
        "name": "One",
        "transactions": [
          {
            "amount": 10,
            "createdAt": "2017-07-03T06:25:15.282Z",
            "updatedAt": "2017-07-03T06:25:15.282Z",
            "_id": "5959e34bdf833e1451a32676",
            "_asset": "5959e34adf833e1451a32661"
          }
        ]
      },
      {
        "_id": "5959e34adf833e1451a32662",
        "__v": 0,
        "name": "Two",
        "transactions": [
          {
            "amount": 10,
            "createdAt": "2017-07-03T06:25:15.280Z",
            "updatedAt": "2017-07-03T06:25:15.280Z",
            "_id": "5959e34bdf833e1451a32675",
            "_asset": "5959e34adf833e1451a32662"
          }
        ]
      }
    ]
    

    【讨论】:

    • 尼尔,哇!感谢您的惊人回复!你显然花了一些时间。与此相比,我考虑过的其他解决方案将是野蛮的。
    • @hoekma 这实际上是"Hybrid Schema" 的“缩减”示例,在链接的核心文档主题中简要介绍了该示例。恕我直言,对于最常见的请求是“最近的数据”的大量关系,这是一个明智的模式。因此,我通过示例来展示我在该问题的初始 cmets 中所涉及的内容。由于存在“最近”数组,因此您可以使用该数据,并且仅在实际请求时才转到其他“完整源”。这是最常见的情况。
    猜你喜欢
    • 2018-05-20
    • 2018-10-22
    • 2020-12-08
    • 2019-06-17
    • 1970-01-01
    • 1970-01-01
    • 2017-09-19
    • 2018-03-10
    • 2018-10-20
    相关资源
    最近更新 更多