嵌套的 MongoDB 查询在 Golang 的 mgo.v2 中如何实现?

huangapple go评论87阅读模式
英文:

Nested MongoDB query with Golang mgo.v2

问题

我正在查询一个具有嵌套数组项的JSON结构。我想返回整个结构,但只包括与查询条件匹配的嵌套项。

所以,示例结构如下:

{
parentId:1,
items: [
{
field1: 1
field2: 2
},
{
field1: 3
field2: 4
}
]
}

我想用类似这样的方式进行查询:

db.getCollection('mycollection').find({"items.field1":1, "items.field2":2})

这个查询可以工作,但它也会返回第二个子项,我实际上想要返回的是:

{
parentId:1,
items: [
{
field1: 1
field2: 2
}
]
}

我已经能够在MongoDB本身中创建一个实现我想要的查询:

db.getCollection('mycollection').aggregate(
{ $unwind : "$items" },
{ $match : {
"items.field1": 1,
"items.field2": 2,
}}
)

但是,当尝试使用mgo.v2设置时,它变得有点困难。collection.Find方法似乎不喜欢$unwind命令,并且似乎无法找到正确的语法来使其与Pipe方法一起工作。

有人有任何建议如何创建这个查询吗?我可以只创建字符串并传递执行吗?

英文:

I'm querying a json structure with a nested array of items. I'd like to return the entire structure but only include the items of the nest which match the query criteria.

So - sample structure is

{
     parentId:1,
     items: [
            {
               field1: 1
               field2: 2
            },
            {
               field1: 3
               field2: 4
            }
            ]
}

I would like to query with something like this ->

db.getCollection('mycollection').find({"items.field1":1, "items.field2":2}) 

this works but it also brings back the second child item, what I want it to actually return is this ->

{
     parentId:1,
     items: [
            {
               field1: 1
               field2: 2
            }
            ]
}

I have been able to create a query in MongoDB itself which achieves what I want ->

db.getCollection('mycollection').aggregate(
  { $unwind : "$items" },
  { $match : {
     "items.field1": 1,
     "items.field2": 2,
  }}
)

but when trying to set this up using mgo.v2 it's proving a bit of a mare. The collection.Find method doesn't seem to like the $unwind command and can't seem to get the right syntax to get it working with the Pipe method.

Does anyone have any suggestions how this can be created? Can I just create the string and pass it for execution?

答案1

得分: 2

你需要使用**$filter**操作符来构建你需要的管道。该操作符根据指定的条件选择数组的子集并返回。它返回一个只包含满足条件的元素的数组。

在你的情况下,你需要运行以下聚合操作:

db.mycollection.aggregate([
   {
        "$project": {
            "parentId": 1,            
            "items": {
                "$filter": {
                    "input": "$items",
                    "as": "item",
                    "cond": { 
                        "$and": [
                            { "$eq": ["$$item.field1", 1] },
                            { "$eq": ["$$item.field2", 2] }
                        ]
                    }
                }
            }
        }
    }
])

对于不支持**$filter**操作符的MongoDB版本,你可以使用一组集合操作符,如下所示:

db.mycollection.aggregate([
   {
        "$project": {
            "parentId": 1,            
            "items": {
                "$setDifference": [
                    { "$map": {
                        "input": "$items",
                        "as": "item",
                        "in": {
                            "$cond": [
                               { 
                                    "$and": [
                                        { "$eq": ["$$item.field1", 1] },
                                        { "$eq": ["$$item.field2", 2] }
                                    ]
                                },
                                "$$item",
                                false
                            ]
                        }
                    }},
                    [false]
                ]
            }
        }
    }
])

作为最后的选择,你可以使用**$unwind**操作符,该操作符会为每个数组条目生成一个文档副本,这会占用更多的内存(聚合管道的内存上限为总内存的10%),因此生成和处理的时间也会更长。你可以按照以下方式运行:

db.mycollection.aggregate([
    { "$unwind" : "$items" },
    { "$match" : {
        "items.field1": 1,
        "items.field2": 2,
    }},
    {
        "$group": {
            "_id": "$_id",
            "parentId": { "$first": "$parentId" },
            "items": { "$push": "$items" }
        }
    }
])

在mgo中,你可以将其作为管道运行:

pipeline := []bson.M{   
    bson.M{ "$unwind": "$items" },
    bson.M{
        "$match": bson.M{ 
            "items.field1": 1,
            "items.field2": 2
        }
    },
    bson.M{
        "$group": bson.M{
            "_id": "$_id",
            "parentId": bson.M{ "$first": "$parentId" },
            "items": bson.M{ "$push": "$items" }
        }
    }   
}
pipe := mycollection.Pipe(pipeline)
iter := pipe.Iter()

在Robomongo中进行测试:

嵌套的 MongoDB 查询在 Golang 的 mgo.v2 中如何实现?

英文:

The actual pipeline you need involves the use of the $filter operator, which selects a subset of the array to return based on the specified condition. It returns an array with only those elements that match the condition.

In your case, you need to run the following aggregation operation

db.mycollection.aggregate([
   {
		"$project": {
			"parentId": 1,			
			"items": {
				"$filter": {
					"input": "$items",
					"as": "item",
					"cond": { 
						"$and": [
							{ "$eq": ["$$item.field1", 1] },
							{ "$eq": ["$$item.field2", 2] }
						]
					}
				}
			}
		}
	}
])

Testing
嵌套的 MongoDB 查询在 Golang 的 mgo.v2 中如何实现?


For MongoDB versions which do not support the $filter operator, you can use a combination of set operators as:

db.mycollection.aggregate([
   {
		"$project": {
			"parentId": 1,			
			"items": {
				"$setDifference": [
					{ "$map": {
						"input": "$items",
						"as": "item",
						"in": {
							"$cond": [
							   { 
									"$and": [
										{ "$eq": ["$$item.field1", 1] },
										{ "$eq": ["$$item.field2", 2] }
									]
								},
								"$$item",
								false
							]
						}
					}},
					[false]
				]
			}
		}
	}
])

As a last resort, you can use the $unwind operator as the operator produces a copy of each document per array entry which uses more memory (possible memory cap on aggregation pipelines of 10% total memory) and thus also takes time to produce as well as "time" to process. You can run as:

db.mycollection.aggregate([
	{ "$unwind" : "$items" },
	{ "$match" : {
		"items.field1": 1,
		"items.field2": 2,
	}},
	{
		"$group": {
			"_id": "$_id",
			"parentId": { "$first": "$parentId" },
			"items": { "$push": "$items" }
		}
	}
])

which you can run in mgo as a pipeline:

pipeline := []bson.M{   
	bson.M{ "$unwind": "$items" },
    bson.M{
        "$match": bson.M{ 
			"items.field1": 1,
			"items.field2": 2
		}
    },
	bson.M{
		"$group": bson.M{
			"_id": "$_id",
			"parentId": bson.M{ "$first": "$parentId" },
			"items": bson.M{ "$push": "$items" }
		}
	}	
}
pipe := mycollection.Pipe(pipeline)
iter := pipe.Iter()

Testing in Robomongo

嵌套的 MongoDB 查询在 Golang 的 mgo.v2 中如何实现?

答案2

得分: 2

参考chridam的答案,我认为使用$filter的方法可以简化一些:

c := session.DB("test").C("mycollection")

pipe := c.Pipe([]bson.M{{
    "$project": bson.M{
        "parentId": 1,
        "items": bson.M{
            "$filter": bson.M{
                "input": "$items",
                "as":    "item",
                "cond": bson.M{
                    "$and": []bson.M{
                        {"$eq": []interface{}{"$$item.field1", 1}},
                        {"$eq": []interface{}{"$$item.field2", 2}},
                    },
                },
            },
        },
    },
}})

resp := []bson.M{}
err = pipe.All(&resp)

if err != nil {
    panic(err)
}

fmt.Println(resp)

另一种方法使用$unwind

c := session.DB("test").C("mycollection")

pipe := c.Pipe([]bson.M{
    {"$unwind": "$items"},
    {"$match": bson.M{
        "items.field1": 1,
        "items.field2": 2,
    }},
    {"$group": bson.M{
        "_id":      "$_id",
        "parentId": bson.M{"$first": "$parentId"},
        "items":    bson.M{"$push": "$items"},
    }},
})

resp := []bson.M{}
err = pipe.All(&resp)

if err != nil {
    panic(err)
}

fmt.Println(resp)

两种方法都可以正常编译并返回结果:

[map[
    _id:ObjectIdHex(".....") 
    parentId:1 
    items:[map[
        field2:2 
        field1:1
    ]]
]]

go 1.6, mongo 3.2
英文:

Referring to chridam's answer, I believe the one with $filter can be simplified a bit:

c := session.DB("test").C("mycollection")

pipe := c.Pipe([]bson.M{{
	"$project": bson.M{
		"parentId": 1,
		"items": bson.M{
			"$filter": bson.M{
				"input": "$items",
				"as":    "item",
				"cond": bson.M{
					"$and": []bson.M{
						{"$eq": []interface{}{"$$item.field1", 1}},
						{"$eq": []interface{}{"$$item.field2", 2}},
					},
				},
			},
		},
	},
}})

resp := []bson.M{}
err = pipe.All(&resp)

if err != nil {
	panic(err)
}

fmt.Println(resp)

The other with $unwind:

c := session.DB("test").C("mycollection")

pipe := c.Pipe([]bson.M{
	{"$unwind": "$items"},
	{"$match": bson.M{
		"items.field1": 1,
		"items.field2": 2,
	}},
	{"$group": bson.M{
		"_id":      "$_id",
		"parentId": bson.M{"$first": "$parentId"},
		"items":    bson.M{"$push": "$items"},
	}},
})

resp := []bson.M{}
err = pipe.All(&resp)

if err != nil {
	panic(err)
}

fmt.Println(resp) 

Both compile without error and return

[map[
    _id:ObjectIdHex(".....") 
    parentId:1 
    items:[map[
        field2:2 
        field1:1
    ]]
]]

go 1.6, mongo 3.2

huangapple
  • 本文由 发表于 2016年9月15日 21:31:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/39512394.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定