MongoDB 操作符 $unwind 展开数组(agregation)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MongoDB 操作符 $unwind 展开数组(agregation)相关的知识,希望对你有一定的参考价值。

参考技术A

$unwind :将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。

您可以传递字段路径操作数或文档操作数来展开数组字段。

字段路径

您可以将数组字段路径传递给 $unwind 。使用此语法时,如果字段值为 null 、缺失或空数组,则 $unwind 不会输出文档。

指定字段路径时,在字段名称前加上美元符号 $ 并用引号引起来。

带选项的文档

您可以将文档传递给 $unwind 以指定各种行为选项。

插入数据

使用 $unwind 展开

该操作返回以下结果:

使用 includeArrayIndex 和 preserveNullAndEmptyArrays

示例数据

以下 $unwind 操作是等效的,并为 sizes 字段中的每个元素返回一个文档。如果 sizes 字段未解析为数组但不丢失、为空或空数组, $unwind 则将非数组操作数视为单元素数组。

该操作返回以下文档:

includeArrayIndex

以下 $unwind 操作使用 includeArrayIndex 选项在输出中包含数组索引。

该操作展开 sizes 数组并在新 arrayIndex 字段中包含数组索引的数组索引。如果该 sizes 字段未解析为数组但不缺失、为 null 或空数组,则该 arrayIndex 字段为 null 。
操作返回结果:

preserveNullAndEmptyArrays

以下 $unwind 操作使用 preserveNullAndEmptyArrays 选项来包含 sizes 字段为空、缺失或空数组的文档。

输出包括 sizes 字段为空、缺失或空数组的文档:

示例数据:

以下管道展开 sizes 数组并按展开大小值对结果文档进行分组:

第一阶段:
该 $unwind 阶段为 sizes 数组中的每个元素输出一个新文档。该阶段使用 preserveNullAndEmptyArrays 选项在输出中包含 sizes 字段缺失、为空或空数组的文档。此阶段将以下文档传递到下一阶段:

第二阶段:
该 $group 阶段将文档分组 sizes 并计算每个尺寸的平均价格。此阶段将以下文档传递到下一阶段:

第三阶段:
该 $sort 阶段按 averagePrice 降序对文档进行排序。该操作返回以下结果:

在 mongosh 中,创建一个 sales 使用以下文档命名的示例集合 :

以下操作按标签对出售的商品进行分组,并计算每个标签的总销售额。

第一阶段:
第一阶段 $unwind 为 items 数组中的每个元素输出一个新文档:

第二阶段:
第二阶段 $unwind 为 items.tags 数组中的每个元素输出一个新文档:

第三阶段:
该阶段 $group 按标签对文档进行分组,并计算带有每个标签的商品的总销售额:

https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

MongoDB聚合查询优化:$match、$lookup和double $unwind

【中文标题】MongoDB聚合查询优化:$match、$lookup和double $unwind【英文标题】:MongoDB aggregation query optimization: $match, $lookup and double $unwind 【发布时间】:2021-05-04 22:47:26 【问题描述】:

假设我们有两个集合:

devices:此集合中的对象具有(除其他外)字段name(字符串)和cards(数组);该数组中的每个部分都有字段modelslot。这些卡片不是另一个集合,它只是一些嵌套数据。 interfaces:此集合中的对象具有(以及其他)字段nameowner

额外信息:

对于cards,我只对slot 是数字的那些感兴趣 对于与先前条件匹配的devicepart,在另一个集合中有一个interface 对象,其中owner 字段的值是devicename,原因并且名字是s[slot]p1(字符's'+那个部分的槽+'p1')

我的工作是创建一个查询以生成所有这些设备中所有现有卡片的摘要,每个条目都包含来自interfaces 集合的信息。我还需要能够对查询进行参数化(如果我只对具有特定名称的特定设备感兴趣,只对特定型号的卡片等感兴趣)

到目前为止,我有这个:

mongo_client.devices.aggregate([
    # Retrieve all the devices having the cards field
    
        "$match": 
            # "name": "<device-name>",
            "cards": 
                "$exists": "true"
            
        
    ,
    
    # Group current content with every cards object
    
         "$unwind": "$cards"
    ,
    
    # Only take the ones having "slot" a number
    
        "$match": 
            "cards.slot": 
                "$regex": "^\d+$"
            
        
    ,
    
    # Retrieve the device's interfaces
    
        "$lookup": 
            "from": "interfaces",
            "let": 
                "owner": "$name",
            ,
            "as": "interfaces",
            "pipeline": [
                "$match": 
                    "$expr": 
                        "$eq": ["$owner", "$$owner"]
                    ,
                ,
            ]
        
    ,
    
    
        "$unwind": "$interfaces"
    ,
    
    
        "$match": 
            "$expr": 
                "$eq": ["$interfaces.name", 
                    "$concat": ["s", "$cards.slot", "p1"]
                ]
            
        
    ,
    
    # Build the final object
    
        "$project": 
            # Card related fields
            "slot": "$cards.slot",
            "model": "$cards.model",

            
            # Device related fields
            "device_name": "$name",
           
            # Fields from interfaces
           "interface_field_x": "$interfaces.interface_field_x",
           "interface_field_y": "$interfaces.interface_field_y",
        
    ,
])

查询有效,而且速度很快,但我有一个问题:

    有什么办法可以避免第二个$unwind?如果对于每个 device 有 50-150 个 interface 对象,其中 owner 是该设备的名称,我觉得我正在放慢速度。每个设备都有一个名为s[slot]p1 的唯一接口。如何以更好的方式获得该特定对象?我尝试在$lookup 甚至$regex$regexMatch 内部的$match 中使用两个$eq 表达式,但我无法使用外部slot 字段,即使我将其放入@987654351 @。

    如果我想在需要时对查询进行参数化以过滤数据,您是添加匹配表达式作为中间步骤还是仅在最后过滤?

欢迎对查询进行任何其他改进。我也对如何使其防错感兴趣(如果错误地丢失了cards 或者找不到s1p1 接口。

谢谢!

【问题讨论】:

【参考方案1】:

您的问题缺少查询的示例数据,但是:

将第三阶段合并到第一阶段,去掉$exists 使用 localField+foreignField 代替管道,管道要慢得多

查询中展开的次数应与您想要的结果集中的对象相对应:

0 为设备展开 1 张牌张开 接口展开 2 次​​li>

为了匹配所需的条件,不需要展开。

【讨论】:

嘿!谢谢你的建议。我完全错过了我可以在展开之前过滤slots 的事实。 localField + foreignFields 是我以前的,但我认为pipeline 可能会更好。所以我想我可以获取设备的所有接口,然后使用$match 来获取s[slot]p1 一个。在这种情况下,$unwind 仍然是必要的还是有更好的方法来做到这一点? (查找后的匹配应该只返回一个元素,如果出现问题,可能返回 0)。 我不明白你在说什么。如果您想修改您的问题,请随时修改您的问题以说出您想要表达的内容。 我在问是否可以避免第二个$unwind(用于接口),因为总是只有一个名为s[slot]p1 的对象或根本没有对象。展开具有单个元素的数组似乎太多了。数组索引更好/更安全还是我应该坚持放松? 您应该索引用于在第一阶段检索文档的字段。 Unwind 不检索文档,因此不受索引的影响。

以上是关于MongoDB 操作符 $unwind 展开数组(agregation)的主要内容,如果未能解决你的问题,请参考以下文章

在 MongoDB 中展开 3 个数组

MongoDB 学习笔记之 Aggregation Pipeline

MongoDB Aggregation - $unwind order 文档是不是与嵌套数组 order 相同

MongoDB Aggregation - $unwind order 文档是不是与嵌套数组 order 相同

mongoDB实战聚合管道--$unwind

mongodb aggregate $unwind