【问题标题】:MongoDB returning a matching array element in PHP [duplicate]MongoDB在PHP中返回匹配的数组元素[重复]
【发布时间】:2016-03-16 11:14:22
【问题描述】:

在这个例子中可以返回特定的数组项吗?我在 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
                }
            ]
        }
    ]
}

我需要找到并返回对象,其中一项是 internalID = 2.. 结果应该是这样的:

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

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

$collection->find(
    [
        'folders.items.internalID' => 2,
    ],
        [
            'folders' => [
                '$elemMatch' => [
                    'items.internalID' => 2
                ]
            ]
        ]
);

你能帮帮我吗? mongodb中的数组是否可以只返回一个匹配项?

谢谢!

【问题讨论】:

  • 请提及您的 MongoDB 和驱动程序版本
  • 我在 PHP 中使用的是 mongo 版本 3 (3.0.4) 和 mongoDB 驱动程序版本 1.6.11。
  • $itemMatch 检查数组内部的值是否匹配。它不会限制您的数组选择。
  • 为什么不直接使用投影$elemMatch?你不需要聚合框架。docs.mongodb.org/manual/reference/operator/projection/elemMatch

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


【解决方案1】:

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

$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
                                )

                        )

                )

        )

)

【讨论】:

  • 放松不是很好,但它是一种解决方法。
  • 好的,谢谢,还有一个更好的解决方案(我不喜欢这个带有聚合的代码,它太大了)?我尝试了 nated $elemMatch,但它不起作用...
【解决方案2】:

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

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

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

$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);

如您所见,这不是预期的输出格式,但足够接近。您只需停在这里并在客户端处理您的结果。原因是因为要获得准确的输出格式,您需要使用$unwind 运算符对“items”数组进行两次反规范化,但如果您正在处理大型集合,则此操作可能会非常昂贵,因此结果性能下降。

$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);

版本
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]
                 ]
             }
         }
     }
}}; 

【讨论】:

    猜你喜欢
    • 2012-08-31
    • 1970-01-01
    • 1970-01-01
    • 2015-05-27
    • 2016-01-26
    • 2012-10-25
    • 1970-01-01
    • 1970-01-01
    • 2015-06-21
    相关资源
    最近更新 更多