在MongoDB集合中按频率对数组进行排序

3
我正在尝试按照MongoDB集合中每个文档内的频率对一些数组进行排序。
目前我的文档大致如下:
{
    "_id": whatever,
    "color": "blue",
    "fruit": ["apple", "banana", "apple", "orange", "apple", "orange", ...],
    "vegetable": ["onion", "lettuce", "spinach", "lettuce", ...],
    "meat": ["pulled pork", "steak", "chicken wings", "pulled pork", "pulled pork", ...]
}

注意:这不是真实的数据,但文档的属性是完全相同的。
最终目标是找到每种颜色中最常见的水果、蔬菜和肉类,所以我猜想如果我能按颜色分组,并按频率排序获取每个数组的第一个元素,那就能得到我所需的结果。
我尝试过展开(unwinding),但我的数据库太大了,无法为每个数组展开(每个数组大约有50,000个元素,所以50,000^3似乎不是理想的选择)。我还寻找了一个“mode”组函数,因为MongoDB有一个“median”函数,但似乎没有(v5.0.22)。我还研究了映射和归约函数用于流水线处理(db.collection.aggregate({$map //或 $reduce...})),但由于这对我来说还比较新,所以进展不大,尽管我觉得这可能是正确的方法。
有人做过类似的事情吗?或许在这里能起作用?谢谢!

1
你怎么样才能“...找到每种颜色中最常见的水果、蔬菜和肉类”?颜色水果的对等字段。另外:这里是超级聚合,不是优化索引获取或事务插入。在SPARK中运行并将数据转储到AWS S3是可能的吗? - undefined
@BuzzMoschetti 也许我举的例子不是最好的,因为正如帖子中所说,数据具有属性但并不相同。对于这个例子,想象一下颜色是与所有其他属性相对应的一个字段。关于大型聚合操作;是的,这正是MongoDB应该做的,如果我进行ACID操作,我可能会使用其他东西,但我需要在Mongo上进行操作。 - undefined
你可能对MongoDB的功能有一些过时的印象。实际上,MongoDB从8年前开始就支持在多个集合中的多个文档上进行starttrans/commit/rollback操作。此外,color字段已经从数组类型变为单值标量类型,因此可以采用新的方法处理。fruit等字段是否仍然是长度为50000的数组?总共有多少个文档? - undefined
@BuzzMoschetti确实,但我与Mongo的唯一业务是它的文档导向。关于颜色字段,我在复制时弄错了:它是一个在文档之间变化的单一值(否则就没有意义了,我的错)。其他字段保持不变。颜色不会重复,因为它们来自一个分组子句。我的想法是为每种颜色的数组获取模式,但除了插入一些JS之外,我还没有找到原生MQL的解决方案。 - undefined
啊……所以已经有一个$group阶段了?可能是用$push来收集fruitmeat等的标量值? - undefined
1个回答

1
OP表示我们在问题中看到的文档是一个$group(显然是基于color)的结果,可能会将fruitvegetablemeat的值$push到不断增长的数组中(最多达到50,000个)。如果目标是获取每种颜色的众数,则可以使用$facet作为“多组”。假设每个单独的文档具有以下结构(注意:这里只使用fruitmeat来简化;该方法可以扩展到vegetable和文档中的任何其他字段):
    {"color": "blue", "fruit": "A", "meat": "X"}

以下的$facet管道将会产生我们所寻找的模式:
db.foo.aggregate([
    {$facet: {
        "most_fruit": [
            // Sum by color and fruit name:                                      
            {$group: {_id: {c:'$color', v:'$fruit'}, N: {$sum: 1}}}

            // Reorg by color only....                                           
            ,{$group: {_id: '$_id.c', X:{$push: {v:'$_id.v',N:'$N'}}}}

            // ...and now sort highest-to-lowest and take the highest one.       
            // Nice thing is if you really want, you are already set up to       
            // capture, for example, the highest *nd* the lowest.                
            ,{$project: {
                X: {$first: {$sortArray: {input: '$X', sortBy: {'N':-1} }} }
            }}
        ],

        // Same thing ... but for meat                                           
        "most_meat": [
            {$group: {_id: {c:'$color', v:'$meat'}, N: {$sum: 1}}}

            ,{$group: {_id: '$_id.c', X:{$push: {v:'$_id.v',N:'$N'}}}}
            ,{$project: {
                X: {$first: {$sortArray: {input: '$X', sortBy: {'N':-1} }} }
            }}
        ]
    }}
]);

产生的结果具有这种形状:
{                                                                                
  most_fruit: [                                                                  
    {_id: 'blue', X: {v: 'A', N: 2} },                                           
    {_id: 'green',X: {v: 'F', N: 3} }                                            
  ],                                                                             
  most_meat: [                                                                   
    {_id: 'green',X: {v: 'Z', N: 4} },                                           
    {_id: 'blue', X: {x: 'X', N: 3} }                                            
  ]                                                                              
}    

返回一个包含所有信息的单个文档。虽然它没有按颜色进行组织,但在数据库端(使用MQL)没有进一步的处理可以使数据的分组或过滤更高效;现在需要客户端来设置信息。
以下是一个候选的客户端重组:
var oneDoc = c.next();

function processItem(obj, fname) {
    for(var n = 0; n < obj[fname].length; n++) {
        var cn = oneDoc[fname][n]['_id'];
        if(undefined == color_major[cn]) {
            // Set up ALL the possible modes.  -1 is our way of                  
            // signalling it has not yet been set.                               
            color_major[cn] = {'most_fruit':-1,'most_meat':-1};
        }
        color_major[cn][fname] = oneDoc[fname][n]['X'];
    }
}

var color_major = {};
processItem(oneDoc, 'most_fruit');
processItem(oneDoc, 'most_meat');

print(color_major);

产量:
{
  green: { most_fruit: { v: 'F', N: 3 }, most_meat: { v: 'Z', N: 4 } },
  blue: { most_fruit: { v: 'A', N: 2 }, most_meat: { v: 'X', N: 3 } }
}

如果你真的想把DB搞翻

(...但实际上不需要,因为上面的$facet阶段输出的数据非常少...)

这是额外的管道,用于重新格式化数据:

    // Turn $facet field names (lval) into values (rval):                                         
    ,{$project: {X: {$objectToArray: '$$ROOT'} }}

    // Double unwind is OK because there is only #color X 2 (fruit and meat) entries.             
    // Even if 1000 colors and fruit meat and veg and whatevs, still quite doable:                
    ,{$unwind: '$X'}
    ,{$unwind: '$X.v'}

    // Reorg on color:                                                                            
    ,{$group: {_id: '$X.v._id', W: {$push: {k: '$X.k', v: '$X.v.X'}} }}

    // ...and put it all back together:                                                           
    ,{$replaceRoot: { newRoot: {$mergeObjects: [ {color:'$_id'}, {$arrayToObject: '$W'} ]}
     }}

产量:
{
  color: 'green',
  most_fruit: {
    v: 'F',
    N: 3
  },
  most_meat: {
    v: 'Z',
    N: 4
  }
}
{
  color: 'blue',
  most_fruit: {
    v: 'A',
    N: 2
  },
  most_meat: {
    v: 'X',
    N: 3
  }
}

这是一个真正深入而详细的解释。我理解每一步都像是显而易见的(尽管对我来说并非如此哈哈)。正是我所需要的,谢谢Buzz! - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接