gpt4 book ai didi

node.js - 在 Mongoose 中填充后查询

转载 作者:太空宇宙 更新时间:2023-11-04 01:58:05 25 4
gpt4 key购买 nike

一般来说,我对Mongoose和MongoDB还是很陌生,所以我很难确定是否可能发生以下情况:

Item = new Schema({
id: Schema.ObjectId,
dateCreated: { type: Date, default: Date.now },
title: { type: String, default: 'No Title' },
description: { type: String, default: 'No Description' },
tags: [ { type: Schema.ObjectId, ref: 'ItemTag' }]
});

ItemTag = new Schema({
id: Schema.ObjectId,
tagId: { type: Schema.ObjectId, ref: 'Tag' },
tagName: { type: String }
});



var query = Models.Item.find({});

query
.desc('dateCreated')
.populate('tags')
.where('tags.tagName').in(['funny', 'politics'])
.run(function(err, docs){
// docs is always empty
});


有更好的方法吗?

编辑

如有任何混淆,我们深表歉意。我想做的是获取所有包含有趣标签或政治标签的商品。

编辑

没有where子句的文档:

[{ 
_id: 4fe90264e5caa33f04000012,
dislikes: 0,
likes: 0,
source: '/uploads/loldog.jpg',
comments: [],
tags: [{
itemId: 4fe90264e5caa33f04000012,
tagName: 'movies',
tagId: 4fe64219007e20e644000007,
_id: 4fe90270e5caa33f04000015,
dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
rating: 0,
dislikes: 0,
likes: 0
},
{
itemId: 4fe90264e5caa33f04000012,
tagName: 'funny',
tagId: 4fe64219007e20e644000002,
_id: 4fe90270e5caa33f04000017,
dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
rating: 0,
dislikes: 0,
likes: 0
}],
viewCount: 0,
rating: 0,
type: 'image',
description: null,
title: 'dogggg',
dateCreated: Tue, 26 Jun 2012 00:29:24 GMT
}, ... ]


使用where子句,我得到一个空数组。

最佳答案

对于大于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"
}
)



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


尽管我们还可以在数组上使用 $filter删除不需要的项目,但这实际上是最有效的形式,这是由于 Aggregation Pipeline Optimization的特殊条件,如 $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的方式而进行了相当优化,但它确实存在一个失败之处,即通常是 $lookuppopulate()动作固有的“ LEFT JOIN”被 $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" ] }
}}
]
}}
])


为了使声明的“ local”值与“ foreign”值匹配而使用的 $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()非常相似,但实际上是在服务器上进行联接。为了完整起见,此处的用法根据父子情况将返回的数据强制转换回猫鼬文档实例。

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


  N.B在这里使用 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();
}
)


或者,对于具有 async/await的Node 8.x及更高版本,没有任何其他依赖关系,则更为现代:

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()
}

})()

关于node.js - 在 Mongoose 中填充后查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47107667/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com