Mongodb聚合$ sort by $ match

时间:2014-10-23 02:32:57

标签: php mongodb mongodb-query aggregation-framework

我有一个用户集合,其子文档是'音乐'有一个'喜欢'的子文件。我喜欢运行搜索,找到最喜欢特定艺术家的前10位用户,按他们喜欢的程度排序。这就是数据集的结构方式

[
{
    '_id' : ObjectId("507f1f77bcf86cd799439011"),
    'user_name' : "John",
    'music' : [
        'likes' [
            {'name': 'david bowie', 'strength': 50 },
            {'name': 'john lennon', 'strength': 100 },
            {'name': 'bob marley', 'strength': 20 },
        ]
    ]
},
{
    '_id' : ObjectId("54304264e77cc5a1670cb318"),
    'user_name' : "Paul",
    'music' : [
        'likes' [
            {'name': 'david bowie', 'strength': 60 },
            {'name': 'john lennon', 'strength': 70 },
            {'name': 'bob marley', 'strength': 100 },
        ]
    ]
}
]

我一直在尝试使用以下聚合命令:

$artist = "david bowie";
$db->collection->aggregate( 
        array( 
            array( '$project' => array( 'Likes' => '$music.likes' ) ),
            array( '$match' => array( 'Likes.name' => $artist ) ),
            array( '$sort' => array( 'Likes.strength' => 1 ) ),
            array( '$limit' => 10 )
            )
        );

匹配确实有效,但它只对Likes进行排序而不是整体结果。 另外 - 有没有办法不返回喜欢文档中的所有项目,而只返回与匹配相关的项目?

这是我得到的结果

[
{ 
    ["_id"]=> object(MongoId)#310 (1) { ["$id"]=> string(24) "507f1f77bcf86cd799439011",
    ["Likes"] => array(49) { 
        [0]=> array(2) { ["name"]=> string(11) "john lennon" ["strength"]=> float(100) },
        [1]=> array(2) { ["name"]=> string(11) "david bowie" ["strength"]=> float(50) },
        [2]=> array(2) { ["name"]=> string(11) "bob marley" ["strength"]=> float(20) },
        ...
    }
},
{ 
    ["_id"]=> object(MongoId)#310 (1) { ["$id"]=> string(24) "54304264e77cc5a1670cb318",
    ["Likes"] => array(49) { 
        [0]=> array(2) { ["name"]=> string(11) "bob marley" ["strength"]=> float(100) },
        [1]=> array(2) { ["name"]=> string(11) "john lennon" ["strength"]=> float(70) },
        [2]=> array(2) { ["name"]=> string(11) "david bowie" ["strength"]=> float(60) },
        ...
    }
}   
]

我应该在聚合中使用不同的命令组合吗?

2 个答案:

答案 0 :(得分:1)

因此,需要注意的是"喜欢" (来自投影)是嵌入在文档对象中的数组。这意味着虽然子场像"力量"将被考虑,实际考虑的是数组中的每个元素和每个子字段值。

所以这里的方法没有任何内在错误,但是当你在聚合框架中处理数组时,你通常首先要使用$unwind。当然,这取决于你的目的是什么"过滤"数组内容与否基本上有两种方法:

$artist = "david bowie";
$db->collection->aggregate( 
        array( 
            array( '$match' => array( 'music.likes.name' => $artist ) ),
            array( '$project' => array( 'Likes' => '$music.likes' ) ),
            array( '$unwind' => '$Likes' ),
            array( '$match' => array( 'Likes.name' => $artist ) ),
            array( '$group' => array( 
                '_id' => '$_id', 
                'Likes' => array( '$push' => '$Likes' )
            )),
            array( '$sort' => array( 'Likes.strength' => -1 ) ),
            array( '$limit' => 10 )
            )
        );

基本上"过滤器"每个文档中数组的内容只是与" artist"匹配的元素。条件,所以这里唯一剩下要排序的项目是那些匹配的项目。

$db->collection->aggregate( 
        array( 
            array( '$match' => array(music.likes.name' => $artist ) ),
            array( '$project' => array( 'Likes' => '$music.likes' ) ),
            array( '$unwind' => '$Likes' ),
            array( '$group' => array( 
                '_id' => '$_id', 
                'Likes' => array( '$push' => '$Likes' ),
                'strength' => array(
                    '$max' => array(
                        '$cond' => array(
                            array( '$eq' => array( '$Likes.name', $artist ) ),
                            '$Likes.strength',
                            0
                        )
                    )
                )
            )),
            array( '$sort' => array( 'strength' => -1 ) ),
            array( '$limit' => 10 )
            )
        );

在第二种情况下,你基本上是"建立"检查数组中元素的附加字段,确定是否使用"与..."艺术家匹配的价值#34;在$eq运算符中使用$cond测试作为三元条件。

因为这发生在$group阶段,所以在这里有意义的是只应用在数组的匹配元素中找到的$max值,当然值{{1}从测试中返回与条件不匹配的数组项。

唯一值得注意的是,首先使用$match阶段。您通常希望"过滤"首先要保证文件上的条件,避免不必要的工作这也是管道利用和" index"的唯一机会。在你的收藏中,你会想要那个。当然,$sort与最高"强度相反也是有意义的。价值最高。

这只取决于你是否想要"过滤"数组或只返回整个内容,但确定要排序的值。

答案 1 :(得分:0)

谢谢,我修改了Neil Lunn从上面回答来处理多个艺术家,这里是代码的样子

$artists = array('david bowie', 'bob marley');
$cursor = $user->collection->aggregate( 
    array( 
        array( '$match' => array( 
                '$and' => array(  
                    array('music.likes.name' => $artists[0]),  
                    array('music.likes.name' => $artists[1])  
                ) 
            )
        ),
        array( '$project' => array( 'Likes' => '$music.likes' ) ),
        array( '$unwind' => '$Likes' ),
        array( '$match' => array( 
                '$or' => array(
                    array('Likes.name' => $artists[0]),  
                    array('Likes.name' => $artists[1])  
                 ) 
            ) 
        ),
        array( '$group' => array( 
            '_id' => '$_id', 
            'Likes' => array( '$push' => '$Likes' )
        )),
        array( '$sort' => array( 'Likes.strength' => -1 ) ),
        array( '$limit' => 10 )
        )
    );