从mongoengine上的所有类中过滤掉嵌入的元素

时间:2014-07-31 18:02:46

标签: python mongodb mongoengine aggregation-framework

我在Mongoengine上上了两节课:

class UserPoints(EmbeddedDocument):
   user = ReferenceField(User, verbose_name='user')
   points = IntField(verbose_name='points', required=True)
   def __unicode__(self):
       return self.points

 class Local(Document):
    token = StringField(max_length=250,verbose_name='token_identifier',unique=True)
    points = ListField(EmbeddedDocumentField(UserPoints),required=False)
    def __unicode__(self):
        return self.name

如果我做了类似的事情:“LP = Local.objects.filter(points__user = user)”我从我的用户那里得到了所有带有用户点的本地人。但我想要用户的所有UserPoints。我怎么样?

我也尝试:“lUs = UserPoints.objects.filter(user = user)”但我有一个空数组。

PD:我做这样的事情来解决问题,但效率不高。

 LDPoints = []
 LP = Local.objects.filter(points__user=user)
    print 'List P: '+str(len(LP))
    for local in LP:
            for points in local.points:
                    if points.user == user:
                            dPoints = parsePoints(points)
                            lDPoints.append(dPoints)

1 个答案:

答案 0 :(得分:1)

添加到原始且得到值得尊敬的答案是聚合框架现在已经有$filter一段时间了,这比原始版本中使用的$map$setDifference方法要清晰得多答案。

Local._get_collection().aggregate([
  { "$match": { "points.user": user } },
  { "$project": {
    "token": 1,
    "points": {
      "$filter": {
        "input": "$points",
        "as": "el",
        "cond": { "$eq": [ "$$el.user", user ] }
      }
    }
  }}
])

同样的原则适用于获得"倍数"来自集合中数组的匹配项,您使用基础驱动程序的aggregate()方法,从_get_collection()调用。


原始

避免"过滤"您选择的"用户"的嵌入式文档只是使用aggregation framework。这允许您操纵"数组内容"在数据库服务器上而不是在客户端代码中过滤结果。

使用原始pymongo驱动程序方法完成聚合,但由于Mongoengine构建在此驱动程序之上,因此您可以使用._get_collection()方法从类中访问原始集合对象:

Local._get_collection().aggregate([
    # Match the documents that have the required user
    { "$match": {
         "points.user": user
    }},

    # unwind the embedded array to de-normalize
    { "$unwind": "$points" },

    # Matching now filters the elements
    { "$match": {
         "points.user": user
    }},

    # Group back as an array
    { "$group": {
        "_id": "$_id",
        "token": { "$first": "$token" },
        "points": { "$push": "$points" }
    }}
 ])

如果您的服务器上有MongoDB 2.6或更高版本,那么" user / points"组合始终是唯一的,您可以使用此处提供的$map$setDifference运算符在没有$unwind|$match|$group周期的情况下进行交替过滤:

Local._get_collection().aggregate([
    # Match the documents that have the required user
    { "$match": {
         "points.user": user
    }},

    # Filter the array in place
    { "$project": {
        "token": 1,
        "points": {
            "$setDifference": [
                { 
                    "$map": {
                       "input": "$points",
                       "as": "el",
                       "in": {
                           "$cond": [
                               { "$eq": [ "$$el.user", user ] },
                               "$$el",
                               false
                           ]
                       }
                    }
                },
                [false]
            ]
        }
     }}
])

在第二种情况下,$cond是一个三元运算符,它将逻辑表达式作为它的第一个参数,并在该表达式为true或{{1时返回值作为其他论点。在false内,测试每个元素以查看条件是否为真,在这种情况下"是用户字段等于所选用户"。

返回该数组位置的内容或以其他方式$mapfalse采用生成的数组和"过滤器"错误的值,因此只返回匹配的元素。

在遗留方法中,$unwind管道运算符用于将每个数组元素有效地转换为具有所有其他父属性的自己的文档。这允许您应用相同的$match条件,这与初始查询不同,实际上删除了现在作为单个元素不再符合条件的文档。您总是想要第一个阶段,因为在可能不包含匹配条件的所有文档上没有处理此$setDifference组合的点。

$group阶段使每个文档的所有内容都重新排列。使用$first选项返回$unwind|$match$push运算符基本上重复的所有其他字段,以使用匹配元素重建数组。

所以虽然没有"内置" MongoEngine执行此类查询的方法,您可以通过访问原始驱动程序以MongoDB方式执行此操作。

另请注意,如果您只希望一个元素匹配给定的"用户"或者其他查询,您也可以使用原始驱动程序可用的字段投影形式。但是聚合方法对于数组的任何多个匹配元素都是必需的。