问题 在mongo聚合中选择* group by


我想做一些我觉得很简单的事情。假设我在mongo中有一系列记录,它们具有公共密钥和可变数量的属性。我想在记录中选择所有属性和按名称分组。例如

{ Name: George, x: 5, y: 3 }
{ Name: George, z: 9 }
{ Name: Rob, x: 12, y: 2 }

我想生成一个如下所示的CSV:

Name     X   Y   Z
George   5   3   9
Rob      12  2

试着

DB.data.aggregate({ $group : { _id : "$Name" } })

不幸的是,我将所有名称作为记录返回,但不是所有可能属性的联合。


10523
2017-08-31 04:08


起源



答案:


如果要组合属性,则需要将这些属性添加到 group。例如,使用 $addToSet 找到按每个名称分组的x,y,z属性的唯一值:

db.data.aggregate(
    { $group : {
            _id : "$Name",
            x: { $addToSet: "$x" },
            y: { $addToSet: "$y" },
            z: { $addToSet: "$z" },
    }}
)

返回:

{
    "result" : [
        {
            "_id" : "Rob",
            "x" : [
                12
            ],
            "y" : [
                2
            ],
            "z" : [ ]
        },
        {
            "_id" : "George",
            "x" : [
                5
            ],
            "y" : [
                3
            ],
            "z" : [
                9
            ]
        }
    ],
    "ok" : 1
}

11
2017-08-31 05:01



谢谢,我使用$ push做了类似的事情,它似乎工作。我的后续问题是,从这里是否最好的方法是将数据导出到平面CSV,展开结果集中的内部数组? - Roger Sanchez
我正在使用pymongo和python来创建csv。剩下的一个问题是,当我使用$ addToSet时,我正在为每个键创建结果数组,即使每个键值对只有一个不同的值。这使得扁平化到csv的过程非常麻烦。有没有办法避免创建键值数组? - Roger Sanchez
@RogerSanchez: $addToSet 要么 $push 将返回数组值,因此您必须在CSV导出中进行一些按摩或考虑不同的聚合函数。例如,如果所有值都是数字,并且每个字段只有一个唯一值,则可以使用 $max 代替。如果结果值是 有时 数组,你将不得不在代码中争吵。这是一个可能有用的示例Python要点: 将agg数组展平为CSV中的引用字符串。 - Stennie
很好的参考。我最终通过创建包含我想要的密钥名称的列表,然后使用WriteDict到csv文件,以更加手动的方式解决扁平化问题。效果很好。再次感谢原始的mongo分组答案,这是一个棘手的部分。 - Roger Sanchez
我做了一个mongotry检查结果: mongotry.herokuapp.com/... - blacelle


答案:


如果要组合属性,则需要将这些属性添加到 group。例如,使用 $addToSet 找到按每个名称分组的x,y,z属性的唯一值:

db.data.aggregate(
    { $group : {
            _id : "$Name",
            x: { $addToSet: "$x" },
            y: { $addToSet: "$y" },
            z: { $addToSet: "$z" },
    }}
)

返回:

{
    "result" : [
        {
            "_id" : "Rob",
            "x" : [
                12
            ],
            "y" : [
                2
            ],
            "z" : [ ]
        },
        {
            "_id" : "George",
            "x" : [
                5
            ],
            "y" : [
                3
            ],
            "z" : [
                9
            ]
        }
    ],
    "ok" : 1
}

11
2017-08-31 05:01



谢谢,我使用$ push做了类似的事情,它似乎工作。我的后续问题是,从这里是否最好的方法是将数据导出到平面CSV,展开结果集中的内部数组? - Roger Sanchez
我正在使用pymongo和python来创建csv。剩下的一个问题是,当我使用$ addToSet时,我正在为每个键创建结果数组,即使每个键值对只有一个不同的值。这使得扁平化到csv的过程非常麻烦。有没有办法避免创建键值数组? - Roger Sanchez
@RogerSanchez: $addToSet 要么 $push 将返回数组值,因此您必须在CSV导出中进行一些按摩或考虑不同的聚合函数。例如,如果所有值都是数字,并且每个字段只有一个唯一值,则可以使用 $max 代替。如果结果值是 有时 数组,你将不得不在代码中争吵。这是一个可能有用的示例Python要点: 将agg数组展平为CSV中的引用字符串。 - Stennie
很好的参考。我最终通过创建包含我想要的密钥名称的列表,然后使用WriteDict到csv文件,以更加手动的方式解决扁平化问题。效果很好。再次感谢原始的mongo分组答案,这是一个棘手的部分。 - Roger Sanchez
我做了一个mongotry检查结果: mongotry.herokuapp.com/... - blacelle


这是另一种方法:

$connection = 'mongodb://localhost:27017';
$con        = new Mongo($connection); // mongo connection

$db         = $con->test; /// database
$collection = $db->prb; // table

$keys       = array("Name" => 1,"x"=>1,"y"=>1,"z"=>1);

// set intial values
$initial    = array("count" => 0);

// JavaScript function to perform
$reduce     = "function (obj, prev) { prev.count++; }";

$g          = $collection->group($keys, $initial, $reduce);

echo "<pre>";
print_r($g);

你会得到这样的答案(不是确切的输出):

Array
(
    [retval] => Array
        (
            [0] => Array
                (
                    [Name] => George
                    [x] => 
                    [y] =>
                    [z] =>
                    [count] => 2
                )

            [1] => Array
                (
                    [Name] => Rob
                    [x] => 
                    [y] =>
                    [z] =>
                    [count] => 1
                )

        )

    [count] => 5
    [keys] => 3
    [ok] => 1
)

0
2018-02-11 05:32



而 group 是一个可行的选项,只要你的集合没有分片,你不应该在非PHP问题中使用PHP示例。 - JohnnyHK
@JohnnyHK:我正在寻找它很长一段时间,我在堆栈中得到了这个链接,但它没有给我正确的答案,所以当我找到答案时我在这里发布,有些人可能觉得它很有用,如果你真的要我删除我可以做到这一点。 - Prasanth Bendra
由你决定,但是 aggregate 在这种情况下,这是一个更好的解决方案,如果可能,示例应该是JavaScript,因为它是“本机”mongo语言。不用担心,只是让你知道。 - JohnnyHK
@JohnnyHK:谢谢:) - Prasanth Bendra
@PrasanthBendra:最初的问题是关于聚合框架而不是 group() ;-)。您的示例也不完整,无法按名称和属性计数(x,y,z)对结果进行分组。您目前只按名称分组,这相当于原始问题说明。 - Stennie


Stennie的解决方案要求您确切地知道要从要查询的集合中的每个匹配项返回哪些属性。情况并非总是如此。

我们必须在我们正在编写的Groovy on Grails应用程序中解决这个问题。

我们编写了一个这样的方法来处理“通过X查找”请求:

private List<DBObject> findDistinctPages(Map by) {
    def command =
        new GroupCommand(
                (DBCollection) db.pages,
                new BasicDBObject(['url': 1]),
                new BasicDBObject(by),
                new BasicDBObject([:]),
                'function ( current, result ) { for(i in current) { result[i] = current[i] } }',
                ''
        )
    db.pages.group(command).sort { it.title }
}

然后在我们的代码中调用它,如下所示:

def pages = findDistinctPages([$or: [[type: 'channel'], [type: 'main']]])

这通过将初始查询的结果传递给GroupCommand末尾的javascript函数来工作。 Mongo只返回您在初始查询中指定的属性而没有其他内容,因此您必须第二次迭代结果,使用mongo中的其余数据填充它们。


0
2017-10-18 15:06





使用 $addToSet 对小组来说,它会奏效

db.data.aggregate(
    { $group : {
            _id : "$Name",
            x: { $addToSet: "$x" },
            y: { $addToSet: "$y" },
            z: { $addToSet: "$z" },
    }}
)

-1
2018-06-19 05:53