如何在聚合操作的投影中使用$elemMatch?

34

这是我的物品:

{ "_id" : ObjectId("53fdcb6796cb9b9aa86f05b9"), "list" : [ "a", "b" ], "complist" : [ { "a" : "a", "b" : "b" }, { "a" : "c", "b" : "d" } ] }
这是我想实现的目标:检查“list”是否包含特定元素,并在阅读文档时仅获取“complist”中对象的字段“a”,而不考虑任何这些值。我正在构建一个论坛系统,这是将返回论坛详细信息的查询。我需要在了解用户是否在论坛的白名单中的情况下读取论坛信息。
使用find函数,我可以使用以下查询:
db.itens.find({},{list:{$elemMatch:{$in:["a"]}}})

为了仅获取匹配特定值的第一个元素,这样我就可以检查返回的数组是否不为空,并且知道“list”是否包含我要查找的值。我不能在查询上执行此操作,因为我希望无论“list”值中是否包含我要查找的值,都能获取文档。我需要文档并知道“list”是否具有特定值。

使用聚合,我可以使用查询

db.itens.aggregate({$project:{"complist.a":1}})

我希望只读取complist中包含的对象的字段“a”。这将获取论坛主题的基本信息,我不需要所有主题的信息,只需要其中的一些。

但是当我尝试使用查询时

db.itens.aggregate({$project:{"complist.b":1,list:{$elemMatch:{$in:["a"]}}}})

尝试同时执行这两个操作,它会抛出一个错误,指示运算符$elemMatch无效。

我在聚合中是否有什么地方使用$elemMatch时出错了?有更好的方法来完成这个任务吗?

8个回答

32

这是一个相当老的问题,但是提出的所有答案都不太好。


TLDR:

$project阶段中无法使用$elemMatch,但是您可以使用其他聚合运算符(如$filter)来实现相同的结果。

db.itens.aggregate([
    {
        $project: {
            compList: {
               $filter: {
                input: "$complist",
                as: "item",
                cond: {$eq: ["$$item.a", 1]}
               }
            }
        }
    }
])

如果您只想获取与条件匹配的数组中的第一个项目(类似于$elemMatch的操作),则可以使用$arrayElemAt


深入解释:

首先让我们了解$elemMatch:

$elemMatch是一个查询表达式,同时也存在projection版本,这是指查询投影而不是$project聚合阶段。

那么呢?这与任何事情有什么关系?嗯,$project阶段具有它可以拥有的某些输入结构,而我们想要使用的是:

<field>: <expression>

什么是有效的expression

表达式可以包括字段路径、文字、系统变量、表达式对象和表达式操作符。表达式可以嵌套。

所以我们想要使用一个expression operator,但是正如您从文档中看到的那样,$elemMatch不是其中的一部分。因此,在聚合$project阶段中使用它不是有效的表达式。


Invalid $project :: caused by :: FieldPath field names may not start with '$'. - Pixsa
我只是忘记添加字段名称了。我已经进行了编辑。 - Tom Slabbaert

17

15

这个问题的答案可能会有所帮助。 这个问题

db.collection_name.aggregate({
    "$match": {
        "complist": {
            "$elemMatch": {
                "a": "a"
            }
        }
    }
});

1
你是否正在使用mongoosejs来访问MongoDB? - Hevelson Rosario
这个可以工作了,第一次没有工作是因为如果有一个像“complist.sub.path”这样的路径,应该删除“complist”,以便下面只有“sub.path”。 - Shadoweb
我没有找到在这个方法中获取数组索引的方法,所以如果你想在聚合操作后进行额外的操作,最好选择完整的$unwind方式,或者像被接受的答案那样使用$project。 - Shadoweb

12

实际上,最简单的解决方案是将您的数组$unwind展开,然后使用$match筛选出适当的文档。您可以使用$group和$push重新组合适当的文档。


2

虽然这个问题很久了,但是以下是我2017年11月份的贡献。

我遇到了类似的问题,连续进行两个匹配操作对我有用。下面的代码是我整个代码的子集,并且我更改了元素名称,因此未经测试。无论如何,这应该指引你朝着正确的方向。

db.collection.aggregate([
    {
        "$match": {
            "_id": "ID1"
        }
    },
    {
        "$unwind": "$sub_collection"
    },
    {
        "$match": {
            "sub_collection.field_I_want_to_match": "value"
        }
    }
])

0

对于聚合操作,只需使用 $expr

db.items.aggregate([
    {
        "$match": {
           "$expr": {"$in": ["a", "$list"]}
        }
    },
])

0

你可以简单地展开文档,然后按ID重新分组。

{ $unwind: "$yourarray" },
{
  $match: {
    "field": { $gt: 1 }
  },
},
{
  $group: {
    _id: "$_id"
  }
},

-19

好的,你可以在查找的投影块中使用“array.field”。

 db.itens.find({},{"complist.b":1,list:{$elemMatch:{$in:["a"]}}})

做到了我需要的。


9
问题是关于聚合而不是查找。 - Vokail
1
问题是关于聚合而不是查找。 - hardik beladiya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接