【问题标题】:Documents in array matching a sequence与序列匹配的数组中的文档
【发布时间】:2016-11-19 06:53:38
【问题描述】:

考虑以下 3 个文档(简化):

{
    "name" : "Alex Ham",
    "opponents" : [ 
        {
            "location" : "west",
            "position" : 5
        },
        {
            "location" : "north",
            "position" : 7
        },
        {
            "location" : "east",
            "position" : 2
        }
    ]
},
{
    "name" : "John Flex",
    "opponents" : [ 
        {
            "location" : "north",
            "position" : 9
        },
        {
            "location" : "north",
            "position" : 4
        },
        {
            "location" : "south",
            "position" : 2
        }
    ]
},
{
    "name" : "Adam Will",
    "opponents" : [ 
        {
            "location" : "east",
            "position" : 6
        },
        {
            "location" : "south",
            "position" : 8
        }
    ]
}

我需要按数组opponents 中的项目序列匹配文档。我将有整数,例如 572、942、68 等。整数的每个数字代表opponents 数组中的position

例如,对于 572,我需要匹配第一个文档 (Alex Ham),因为如果您查看 opponents 数组,项目的序列及其 position 字段分别为 5、7 和 2。

对于 942,我必须匹配第二个文档 (John Flex),对于 68,我必须匹配第三个文档 (Adam Will),依此类推。

考虑到拥有大量数据(几百万个文档),并考虑到性能,我如何构建一个聚合管道来匹配上述案例的文档。

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    我认为在这种情况下您不需要使用聚合。要通过聚合完成此任务,您需要遍历整个集合并将所有文档投影到一个新表单,其中包含一个可以与您的输入匹配的值。虽然聚合速度很快,但还是不够好。

    我建议将您的输入格式化为可以与文档匹配的表单:

    const val = 572;
    
    const arr = val.toString().split('');
    const size = arr.length;
    
    const selector = arr.map((v, i) => {
      return {
        [`opponents.${i}.position`]: parseInt(v, 10),
      };
    }).reduce((acc, cur) => {
      return Object.assign(acc, cur);
    }, {});
    
    selector.opponents = {
      $size: size,
    };
    console.log(selector);

    现在使用这个新的选择器,您可以使用普通的.find 操作来获取文档:

    collection.find(arr);
    

    【讨论】:

    • 是什么让你觉得聚合的速度不够快,而这种方式呢?
    • 假设您有 100 万个文档,然后您使用聚合将所有这些文档投影到可以与输入匹配的形式,我相信聚合会非常复杂,需要很多阶段。所以即使我们知道聚合很快,但在这种情况下,它仍然需要大量的 CPU 能力和时间。
    • 这里的聚合管道大多会以流的形式进行处理。因此,以上仅在最坏的情况下是正确的。此外,上述代码依赖于外部应用程序逻辑。另一个缺失的部分是您不检查数组的大小。所以你最终会匹配 5723 和 572。
    • 哦,关于可能的错误匹配,你是对的,我会解决这个问题。你能告诉我更多关于“最坏情况”的信息吗?这是什么意思?因为在这种情况下,我们只能在转换阶段之后使用$match,例如在最后阶段或几乎最后阶段,所以我们仍然需要浏览所有文件,对吧?
    • 这看起来是一个可行的解决方案。这是我的选择之一,我仍在寻找其他解决方案。顺便说一句,我收藏了 1800 万份文档。谢谢你的回答。
    【解决方案2】:

    请通过下面解决问题的管道。基本逻辑是$unwind在保留索引的同时,使用索引计算整数的位值,使用公式position * (pow(10, size - index - 1))

    [
    {$project: {name: 1, opponents: 1, sz: {$size: '$opponents'}}},
    {$unwind: {path: '$opponents', includeArrayIndex: 'i'}},
    {$project: {name: 1, opponents: 1, 
        num: {$multiply: [{$pow: [10, {$subtract: [{$subtract: ['$sz', 1]}, '$i']}]}, '$opponents.position']}
        }},
    {$group: {
        _id: '$_id', 
        num: {$sum: '$num'}, 
        name: {$first: '$name'}, 
        opponents: {$push: '$opponents'}
    }},
    {$match: {num: 572}},
    ]
    

    【讨论】:

    • 哇,看起来很酷,你能详细解释一下你在$project阶段做了什么
    • 但是我认为所有这些计算都会使您的查询变慢,不能有任何简单的方法
    • 不,这些都是基本的数学运算,运行速度非常快,因为它们是在 mongo 内部 C++ 实现中运行的。
    • 是否可以使用 $concat (aggregation) 来实现这一点?
    • 顺便说一句,上述聚合不起作用。考虑一个例子,opponents 中有 2 个项目,position 分别是 5 和 3,在第二个 $project(数学所在的位置)之后,num 字段对于第一个文档是 50,而 3对于第二个文件。这与{"$match" : {"num" : 53}} 的下一个聚合步骤中的任何内容都不匹配
    猜你喜欢
    • 2014-05-16
    • 2016-10-02
    • 2019-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-16
    • 1970-01-01
    相关资源
    最近更新 更多