Node.js 查询并筛选带有“点域”的ChangeStream文档。'
我有一份文件看起来像Node.js 查询并筛选带有“点域”的ChangeStream文档。',node.js,mongodb,mongodb-query,aggregation-framework,Node.js,Mongodb,Mongodb Query,Aggregation Framework,我有一份文件看起来像 { components: { weapon: { type: "Sword" }, health: { value: 10 } } type: "Monster" } { $match: { "updateDescription.updatedFields.components.weapon": { $exists: true } } 我正在使用一个正在返回的 { operationType: 'update', updateDescription:
{
components: { weapon: { type: "Sword" }, health: { value: 10 } }
type: "Monster"
}
{ $match: { "updateDescription.updatedFields.components.weapon": { $exists: true } }
我正在使用一个正在返回的
{
operationType: 'update',
updateDescription: {
updatedFields: { 'components.weapon': [Object] }
}
}
我想向聚合管道添加一个查询,以筛选出不适用于组件的任何更新,即,如果类型字段已更新,我不希望获得更新
我的问题看起来像
{
components: { weapon: { type: "Sword" }, health: { value: 10 } }
type: "Monster"
}
{ $match: { "updateDescription.updatedFields.components.weapon": { $exists: true } }
但是,这不起作用,因为updatedFields上的字段是“components.wearm”而不是components:{wearm:..}
如果我能使用括号符号,我会这样做
{ $match: { "updateDescription.updatedFields['components.weapon']": { $exists: true } }
但是,MongoDB语法中不允许这样做,或者至少它不起作用
有解决方案吗?这是一个棘手的问题,但这里有一些确凿的原因,正常的MongoDB文档案例不包括这样的虚线字段。所以确实需要一些特殊的处理 这里的基本情况是,您实际上需要将文档中包含虚线字段的键转换为字符串,然后只需查找该字符串中是否存在组件 简而言之,您基本上需要一个管道表达式,如下所示:
const pipeline = [
{ "$match": {
"$expr": {
"$ne": [
{ "$size": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ], }, -1]
}
}
}},
0
]
}
}}
];
[
{
"k": "coponents.weapon",
"v": "Sword"
}
]
这样做的目的是将updatedFields对象转换为k和v属性数组,而不是命名键。此时,结果值如下所示:
const pipeline = [
{ "$match": {
"$expr": {
"$ne": [
{ "$size": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ], }, -1]
}
}
}},
0
]
}
}}
];
[
{
"k": "coponents.weapon",
"v": "Sword"
}
]
这允许now数组与使用表达式的操作一起使用,从该表达式可以测试k属性值中是否存在字符串。如果它不是-1 for not found,那么该值中包含组件的任何元素都将是唯一保留的元素,并且匹配结果数组中的元素
注意:如果您有MongoDB 4.2,您可能希望查看操作符,而不是。简单地测试字符串中是否存在字符串不应该是必需的,但是如果您的用例需要,正则表达式当然可以做得更多
在这里,您可能会从字符串的开头一直搜索到包含的点,并替换以下内容的内部条件:
因为它是作为数组返回的,所以您可以测试,以便在删除组件值后,查看现在过滤的数组中是否还剩下任何内容。如果没有,并且大小确实为0,则通过丢弃结果,这也是允许在中使用聚合表达式的主运算符
当然,所要做的只是选择实际可返回的有效文档。如果您可能在变更流文档中指示的更新结果中有其他变更字段,那么您实际上需要对其采用相同类型的操作,以便从结果中实际删除组件字段:
{ "$addFields": {
"updateDescription": {
"updatedFields": {
"$arrayToObject": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ] }, -1 ]
}
}
}
}
}
}
}
请注意,这里的添加实质上颠倒了所使用的操作过程,然后实际返回updatedFields内容的原始形式,没有不需要的键
为了演示,下面是一个实际的完整列表,它复制了对具有这种结构的集合所做的更改,并包括观察者管道,以便使不需要的更改静音:
const { MongoClient } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const options = { useNewUrlParser: true, useUnifiedTopology: true };
const log = doc => console.log(JSON.stringify(doc, undefined, 2));
(async function() {
try {
let client = await MongoClient.connect(uri, options);
let db = client.db('test');
// Insert some starting data
await db.collection('things').deleteMany();
await db.collection('things').insertOne({
components: {
weapon: { type: "Sword" },
health: { value: 10 }
},
type: "Monster"
});
// Set up the changeStream
const pipeline = [
// Filters documents
{ "$match": {
"$expr": {
"$ne": [
{ "$size": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ], }, -1]
/* Alternate MongoDB 4.2 syntax
"$not": {
"$regexMatch": {
"input": "$$this.k",
"regex": /^components\./
}
}
*/
}
}
}},
0
]
}
}},
/* -- Uncomment just to see the k and v structure
{ "$project": {
"update": { "$objectToArray": "$updateDescription.updatedFields" }
}}
*/
// Actually removes the keys and returns only non filtered
{ "$addFields": {
"updateDescription": {
"updatedFields": {
"$arrayToObject": {
"$filter": {
"input": {
"$objectToArray": "$updateDescription.updatedFields"
},
"cond": {
"$eq": [{ "$indexOfCP": [ "$$this.k", "component" ] }, -1 ]
/* Alternate MongoDB 4.2 syntax
"$not": {
"$regexMatch": {
"input": "$$this.k",
"regex": /^components\./
}
}
*/
}
}
}
}
}
}
}
];
const changeStream = db.collection('things').watch(pipeline);
changeStream.on('change', next => log({ changeDocument: next }));
// Loop some changes
await new Promise(async (resolve, reject) => {
let tick = true;
setInterval(async () => {
try {
let { value }= await db.collection('things')
.findOneAndUpdate(
{},
{ $set: { 'components.weapon': (tick) ? 'Knife' : 'Sword' }},
{ returnOriginalDocument: false }
);
tick = !tick; // flip the boolean
log({ currentDoc: value });
} catch (e) {
reject(e);
}
},2000)
});
} catch (e) {
console.error(e)
}
})()
精彩而详细的回答,这说明了MongoDB操作符是如何为我工作的。谢谢你,尼尔。