MongoDB在PHP中返回匹配的数组元素

时间:2016-03-16 11:14:22

标签: php mongodb mongodb-query aggregation-framework mongodb-php

在这个例子中,

可以返回数组的特定项吗?我在PHP中使用mongo版本3(3.0.4)和mongoDB驱动程序版本1.6.11。

我在mongo集合中有这个对象:

{
    "_id": ObjectId('56e9247287f5204c0a000001'),
    "name": "test name",
    "folders": [
        {
            "name": "folder name",
            "items": [
                {
                    "name": "item 1",
                    "internalID": 1
                },
                {
                    "name": "item 2",
                    "internalID": 2
                },
                {
                    "name": "item 3",
                    "internalID": 3
                },
                {
                    "name": "item 4",
                    "internalID": 4
                }
            ]
        }
    ]
}

我需要查找并返回对象,其中一个项目的内部ID = 2 ..结果应该是这样的:

{
    "_id": ObjectId('56e9247287f5204c0a000001'),
    "items": [
        {
            "name": "item 2",
            "internalID": 2
        },
    ]
}

以下代码不起作用,返回包含所有项目的完整对象。

$collection->find(
    [
        'folders.items.internalID' => 2,
    ],
        [
            'folders' => [
                '$elemMatch' => [
                    'items.internalID' => 2
                ]
            ]
        ]
);
你能帮帮我吗?可能只返回mongodb中数组中的一个匹配项吗?

谢谢!

2 个答案:

答案 0 :(得分:0)

我想,我找到了解决方案:

$collection->aggregate(
    array(
        # First match the "document" to reduce the pipeline
        array(
            '$match' => array(
                'folders.items.internalID' => 3
            )
        ),

        # Then unwind the array
        array('$unwind' => '$folders'),
        array('$unwind' => '$folders.items'),

        # Match again on the "unwound" elements to filter
        array(
            '$match' => array(
                'folders.items.internalID' => 3
            )
        ),

        # Group back to original structure per document
        array(
            '$group' => array(
                '_id' => '$_id',
                'items' => array(
                    '$push' => '$folders.items'
                )
            )
        ),
        array(
            '$group' => array(
                '_id' => '$_id',
                'items' => array(
                    '$push' => '$items'
                )
            )
        )
    )
);

返回:

Array
(
    [0] => Array
        (
            [_id] => MongoId Object
                (
                    [$id] => 56e9494087f5204c0a000002
                )

            [items] => Array
                (
                    [0] => Array
                        (
                            [0] => Array
                                (
                                    [name] => item 3
                                    [internalID] => 3
                                )

                        )

                )

        )

)

答案 1 :(得分:0)

为此,您需要使用聚合框架。

首先,您需要使用$match运算符过滤掉所有与您的查询条件不匹配的文档。这减少了下一阶段要处理的文档数量。

从那里,您需要$project您的文档并使用MongoDB 3.2中的$filter运算符new来过滤掉那些与您的条件不符的子文档。当然$map运算符返回一个值数组;这里只返回2d数组。

$match = array('$match' => array('folders.items.internalID' => 2));
$project = array(
    '$project' => array('item' => array('$map' => array(
        'input' => '$folders', 
        'as' => 'f', 
        'in' => array('$filter' => array(
            'input' => '$$f.items', 
            'as' => 'af', 
            'cond' => array('$eq' => array('$$af.internalID', 2))
         )), 
    )))
); 

产生这个:

{
    "_id" : ObjectId("56e96b784839cea8e9a1d098"),
    "item" : [
        [
            {
                "name" : "item 2",
                "internalID" : 2
            }
        ]
    ]
}
{
    "_id" : ObjectId("56e96bcb4839cea8e9a1d099"),
    "item" : [
        [
            {
                "name" : "item 2",
                "internalID" : 2
            }
        ],
        [
            {
                "name" : "item 2",
                "internalID" : 2
            }
        ]
    ]
}

$pipeline = array($match, $project);
$out = $c->aggregate($pipeline);

正如您所看到的,这不是预期的输出格式,但足够接近。你只需停在这里并处理结果客户端。原因是为了获得确切的输出格式,你需要 denormalizate " items"使用$unwind运算符进行两次数组运算,但如果处理大型集合,则此操作可能非常昂贵,从而导致性能下降。

$unwind = array('$unwind' => '$item');
$group = array('$group' => array(
    '_id' => '$_id', 
    'item' => array('$push' => '$item')
));

$pipeline = array($match, $project, $unwind, $unwind, $group);
$out = $c->aggregate($pipeline);

由于旧版本不支持$filter运营商,因此您需要使用$setDifference运营商。

$project = array(
    '$project' => array('item' => array('$map' => array(
        'input' => '$folders', 
        'as' => 'f', 
        'in' => array('$setDifference' => array(
            '$map' => array(
                'input' => '$$f.items', 
                'as' => 'af', 
                'in' => array('$cond' => array('$eq' => array('$$af.internalID', 2)), "$$af", false)
            ), 
            array(false)
        ))
    )))
); 

版本3.2或更新的shell解决方案。

var match =  { "$match": { "folders.items.intenalID": 2 } };
var project = { "$project": { 
    "item": { 
        "$map": { 
            "input": "$folders", 
                "as": "f", 
                "in": { 
                    "$filter": { 
                        "input": "$$f.items", 
                        "as": "af", 
                        "cond": { "$eq": [ "$$af.internalID", 2 ] }
                    }
                }
            }
     }
}}; 

// optional
var unwind = { "$unwind": "$item" }; 
var group = { 
    "$group": { 
        "_id": "$_id", 
            "item": { "$push": "$item" }
     }
};

db.collection.aggregate(match, project, unwind, unwind, group);

版本< = 3.0解决方案。

var project = { "$project": { 
    "item": { 
        "$map": { 
            "input": "$folders", 
            "as": "f", 
            "in": { 
                "$setDifference": [
                    { "$map": { 
                        "input": "$$f.items",  
                        "as": "af", 
                        "in": { 
                            "$cond": [ 
                                { "$eq": [ "$$af.internalID", 2 ] },
                                "$$af", 
                                false
                             ]
                         }
                     }}, 
                     [false]
                 ]
             }
         }
     }
}};