在猫鼬中填充后查询

在猫鼬中填充后查询,第1张

在猫鼬中填充后查询

对于大于3.2的现代MongoDB,您可以在大多数情况下

$lookup
用作替代
.populate()
。这也有实际上做加盟,而不是什么“在服务器上”的优势
.populate()
,实际上是做
“多次查询”,以“模仿” 的联接。

所以,

.populate()
不是
真的在关系数据库中是如何做的意义上的“加盟”。在
$lookup
另一方面,运营商,实际执行服务器上的工作,是一个或多或少类似
“LEFT JOIN”

Item.aggregate(  [    { "$lookup": {      "from": ItemTags.collection.name,      "localField": "tags",      "foreignField": "_id",      "as": "tags"    }},    { "$unwind": "$tags" },    { "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },    { "$group": {      "_id": "$_id",      "dateCreated": { "$first": "$dateCreated" },      "title": { "$first": "$title" },      "description": { "$first": "$description" },      "tags": { "$push": "$tags" }    }}  ],  function(err, result) {    // "tags" is now filtered by condition and "joined"  })

注意

.collection.name
这里实际上计算的是“字符串”,它是分配给模型的MongoDB集合的实际名称。由于猫鼬默认情况下会“复数化”集合名称,并且
$lookup
需要实际的MongoDB集合名称作为参数(因为这是服务器 *** 作),因此这是在猫鼬代码中使用的便捷技巧,而不是直接对集合名称进行“硬编码”

虽然我们也可以

$filter
数组上使用以删除不需要的项目,但实际上这是最有效的形式,这是由于针对条件as
和条件an 的特殊条件进行了聚合管道优化。
$lookup
$unwind
$match

实际上,这导致三个管道阶段被分解为一个阶段:

   { "$lookup" : {     "from" : "itemtags",     "as" : "tags",     "localField" : "tags",     "foreignField" : "_id",     "unwinding" : {       "preserveNullAndEmptyArrays" : false     },     "matching" : {       "tagName" : {         "$in" : ["funny","politics"         ]       }     }   }}

这是最佳选择,因为实际 *** 作“先过滤要加入的集合”,然后返回结果并“展开”数组。两种方法都被采用,因此结果不会超过16MB的BSON限制,这是客户端没有的限制。

唯一的问题是,在某些方面它似乎是“反直觉的”,尤其是当您希望将结果存储在数组中时,但这

$group
就是这里的意义,因为它可以重构为原始文档格式。

不幸的是,我们此时根本无法

$lookup
使用服务器使用的相同最终语法进行编写。恕我直言,这是一个需要纠正的疏忽。但是就目前而言,简单地使用序列即可,并且是具有最佳性能和可伸缩性的最可行选择。

附录-MongoDB 3.6及更高版本

尽管此处显示的模式由于其他阶段如何进入而已进行了
相当优化

$lookup
,但它确实存在一个失败之处,即这通常是两者固有的“
LEFT
JOIN”,
$lookup
populate()
通过的
“最佳”
使用则否定了
$unwind
这里不保留空数组。您可以添加该
preserveNullAndEmptyArrays
选项,但这会否定上述的
“优化” 序列,并且基本上使通常在优化中组合的所有三个阶段保持不变。

MongoDB 3.6以 “更具表现力”的
形式扩展,

$lookup
允许“子管道”表达。这不仅可以满足保留“
LEFT JOIN”的目标,而且还可以通过简化的语法来优化查询以减少返回的结果:

Item.aggregate([  { "$lookup": {    "from": ItemTags.collection.name,    "let": { "tags": "$tags" },    "pipeline": [      { "$match": {        "tags": { "$in": [ "politics", "funny" ] },        "$expr": { "$in": [ "$_id", "$$tags" ] }      }}    ]  }}])

$expr
以匹配使用的已声明与“洋”价值“本地”值实际上是MongoDB中做什么“内部”现在与原来的
$lookup
语法。通过以这种形式表达,我们可以
$match
自己在“子管道”中定制初始表达。

实际上,作为真正的“聚合管道”,您几乎可以使用此“子管道”表达式中的聚合管道执行任何 *** 作,包括“嵌套”

$lookup
其他相关集合的级别。

进一步的使用超出了这里所问问题的范围,但是对于甚至“嵌套的人口”而言,新的使用模式

$lookup
允许这几乎相同,而
“很多” 功能在其全部使用方面更为强大。


工作范例

以下是在模型上使用静态方法的示例。一旦实现了该静态方法,则调用将变得简单:

  Item.lookup(    {      path: 'tags',      query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }    },    callback  )

或增强一些甚至更现代:

  let results = await Item.lookup({    path: 'tags',    query: { 'tagName' : { '$in': [ 'funny', 'politics' ] } }  })

使它与

.populate()
结构非常相似,但实际上是在服务器上进行联接。为了完整起见,此处的用法根据父级和子级案例将返回的数据强制转换回Mongoose文档实例。

在大多数情况下,它是相当琐碎且易于适应或使用的。

注意
:此处使用async只是为了简化运行随附示例的过程。实际的实现没有这种依赖性。

const async = require('async'),      mongoose = require('mongoose'),      Schema = mongoose.Schema;mongoose.Promise = global.Promise;mongoose.set('debug', true);mongoose.connect('mongodb://localhost/looktest');const itemTagSchema = new Schema({  tagName: String});const itemSchema = new Schema({  dateCreated: { type: Date, default: Date.now },  title: String,  description: String,  tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]});itemSchema.statics.lookup = function(opt,callback) {  let rel =    mongoose.model(this.schema.path(opt.path).caster.options.ref);  let group = { "$group": { } };  this.schema.eachPath(p =>    group.$group[p] = (p === "_id") ? "$_id" :      (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });  let pipeline = [    { "$lookup": {      "from": rel.collection.name,      "as": opt.path,      "localField": opt.path,      "foreignField": "_id"    }},    { "$unwind": `$${opt.path}` },    { "$match": opt.query },    group  ];  this.aggregate(pipeline,(err,result) => {    if (err) callback(err);    result = result.map(m => {      m[opt.path] = m[opt.path].map(r => rel(r));      return this(m);    });    callback(err,result);  });}const Item = mongoose.model('Item', itemSchema);const ItemTag = mongoose.model('ItemTag', itemTagSchema);function log(body) {  console.log(JSON.stringify(body, undefined, 2))}async.series(  [    // Clean data    (callback) => async.each(mongoose.models,(model,callback) =>      model.remove({},callback),callback),    // Create tags and items    (callback) =>      async.waterfall(        [          (callback) => ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],   callback),          (tags, callback) => Item.create({ "title": "Something","description": "An item",   "tags": tags },callback)        ],        callback      ),    // Query with our static    (callback) =>      Item.lookup(        {          path: 'tags',          query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }        },        callback      )  ],  (err,results) => {    if (err) throw err;    let result = results.pop();    log(result);    mongoose.disconnect();  })

或者,对于Node 8.x及更高版本,

async/await
没有任何其他依赖关系则更加现代:

const { Schema } = mongoose = require('mongoose');const uri = 'mongodb://localhost/looktest';mongoose.Promise = global.Promise;mongoose.set('debug', true);const itemTagSchema = new Schema({  tagName: String});const itemSchema = new Schema({  dateCreated: { type: Date, default: Date.now },  title: String,  description: String,  tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]});itemSchema.statics.lookup = function(opt) {  let rel =    mongoose.model(this.schema.path(opt.path).caster.options.ref);  let group = { "$group": { } };  this.schema.eachPath(p =>    group.$group[p] = (p === "_id") ? "$_id" :      (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });  let pipeline = [    { "$lookup": {      "from": rel.collection.name,      "as": opt.path,      "localField": opt.path,      "foreignField": "_id"    }},    { "$unwind": `$${opt.path}` },    { "$match": opt.query },    group  ];  return this.aggregate(pipeline).exec().then(r => r.map(m =>     this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })  ));}const Item = mongoose.model('Item', itemSchema);const ItemTag = mongoose.model('ItemTag', itemTagSchema);const log = body => console.log(JSON.stringify(body, undefined, 2));(async function() {  try {    const conn = await mongoose.connect(uri);    // Clean data    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));    // Create tags and items    const tags = await ItemTag.create(      ["movies", "funny"].map(tagName =>({ tagName }))    );    const item = await Item.create({       "title": "Something",      "description": "An item",      tags     });    // Query with our static    const result = (await Item.lookup({      path: 'tags',      query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }    })).pop();    log(result);    mongoose.disconnect();  } catch (e) {    console.error(e);  } finally {    process.exit()  }})()

从MongoDB
3.6及更高版本开始,即使没有

$unwind
$group
构建,也可以:

const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');const uri = 'mongodb://localhost/looktest';mongoose.Promise = global.Promise;mongoose.set('debug', true);const itemTagSchema = new Schema({  tagName: String});const itemSchema = new Schema({  title: String,  description: String,  tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]},{ timestamps: true });itemSchema.statics.lookup = function({ path, query }) {  let rel =    mongoose.model(this.schema.path(path).caster.options.ref);  // MongoDB 3.6 and up $lookup with sub-pipeline  let pipeline = [    { "$lookup": {      "from": rel.collection.name,      "as": path,      "let": { [path]: `$${path}` },      "pipeline": [        { "$match": {          ...query,          "$expr": { "$in": [ "$_id", `$$${path}` ] }        }}      ]    }}  ];  return this.aggregate(pipeline).exec().then(r => r.map(m =>    this({ ...m, [path]: m[path].map(r => rel(r)) })  ));};const Item = mongoose.model('Item', itemSchema);const ItemTag = mongoose.model('ItemTag', itemTagSchema);const log = body => console.log(JSON.stringify(body, undefined, 2));(async function() {  try {    const conn = await mongoose.connect(uri);    // Clean data    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));    // Create tags and items    const tags = await ItemTag.insertMany(      ["movies", "funny"].map(tagName => ({ tagName }))    );    const item = await Item.create({      "title": "Something",      "description": "An item",      tags    });    // Query with our static    let result = (await Item.lookup({      path: 'tags',      query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }    })).pop();    log(result);    await mongoose.disconnect();  } catch(e) {    console.error(e)  } finally {    process.exit()  }})()


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5010306.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-15
下一篇 2022-11-14

发表评论

登录后才能评论

评论列表(0条)

保存