MongoDB:如何确定数组字段是否包含元素?


76

我有两个收藏。第一个集合包含学生:

{ "_id" : ObjectId("51780f796ec4051a536015cf"), "name" : "John" }
{ "_id" : ObjectId("51780f796ec4051a536015d0"), "name" : "Sam" }
{ "_id" : ObjectId("51780f796ec4051a536015d1"), "name" : "Chris" }
{ "_id" : ObjectId("51780f796ec4051a536015d2"), "name" : "Joe" }

第二个集合包含课程:

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc4"),
        "name" : "CS 101",
        "students" : [
                ObjectId("51780f796ec4051a536015cf"),
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d2")
        ]
}
{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc5"),
        "name" : "Literature",
        "students" : [
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d0"),
                ObjectId("51780f796ec4051a536015d2")
        ]
}
{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc6"),
        "name" : "Physics",
        "students" : [
                ObjectId("51780f796ec4051a536015cf"),
                ObjectId("51780f796ec4051a536015d0")
        ]
}

每个课程文档都包含一个students数组,其中包含该课程注册的学生列表。当学生在网页上查看课程时,他需要查看他是否已经注册该课程。为此,当courses代表学生查询集合时,我们需要确定students数组是否已经包含学生的ObjectId。有没有一种方法可以在查找查询的投影中指定仅在数组存在的情况下才ObjectIdstudents数组中检索学生?

我试图看看是否可以使用$ elemMatch运算符,但它适用于一系列子文档。我知道我可以使用聚合框架,但在这种情况下,这样做似乎有些过头了。聚合框架可能不会像单个查找查询那样快。有没有一种方法可以查询课程集,以便返回的文档可以采用与此类似的形式?

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc4"),
        "name" : "CS 101",
        "students" : [
                ObjectId("51780f796ec4051a536015d0"),
        ]
}

Answers:


72

[现在可以在最新版本中基于此进行编辑]

[更新的答案]您可以通过以下方式查询班级名称和学生ID,前提是它们已经被注册。

db.student.find({},
 {_id:0, name:1, students:{$elemMatch:{$eq:ObjectId("51780f796ec4051a536015cf")}}})

然后您将获得预期的结果:

{ "name" : "CS 101", "students" : [ ObjectId("51780f796ec4051a536015cf") ] }
{ "name" : "Literature" }
{ "name" : "Physics", "students" : [ ObjectId("51780f796ec4051a536015cf") ] }

[原创答案]目前无法做您想做的事情。不幸的是,如果将学生作为对象存储在数组中,您将能够执行此操作。实际上,让您感到惊讶的是,您仅使用ObjectId(),因为如果您要显示特定课程的学生名单,则总是需要查找学生(先查找Id的列表,然后再查找学生集合中的名字-两个查询而不是一个!)

如果要在课程数组中存储一个ID和名称(例如),如下所示:

{
        "_id" : ObjectId("51780fb5c9c41825e3e21fc6"),
        "name" : "Physics",
        "students" : [
                {id: ObjectId("51780f796ec4051a536015cf"), name: "John"},
                {id: ObjectId("51780f796ec4051a536015d0"), name: "Sam"}
        ]
}

您的查询将只是:

db.course.find( { }, 
                { students : 
                    { $elemMatch : 
                       { id : ObjectId("51780f796ec4051a536015d0"), 
                         name : "Sam" 
                       } 
                    } 
                } 
);

如果该学生仅参加CS 101,您将获得:

{ "name" : "Literature" }
{ "name" : "Physics" }
{
    "name" : "CS 101",
    "students" : [
        {
            "id" : ObjectId("51780f796ec4051a536015cf"),
            "name" : "John"
        }
    ]
}

感谢您确认我关于无法查询简单数组以及子文档数组的想法。我想我将不得不重新评估我的架构。您知道10Gen是否计划解决此问题吗?
shargors

我无法在jira.mongodb.org中找到为此目的的Jira票证,并且没有安排任何票证发行的计划,因此我想肯定不会很快。
Asya Kamsky


$ all是查询的运算符-查询不是问题,问题又在返​​回,因此我们正在讨论的是投影运算符。
Asya Kamsky 2013年

course集合中同时存储名称和对象ID的问题是,您然后将学生姓名存储在多个位置,这可能会导致不同步。在这种情况下,您的查询将返回不完整的数据。如果服务器负载和硬件负担不小,则最好使用额外的查询来获取名称。您仍然可以将学生ObjectIds作为对象存储,以允许OP进行查询:{"id":...}
drevicko 2014年

21

看来$in运营商会很好地满足您的目的。

您可以执行以下操作(伪查询):

if (db.courses.find({"students" : {"$in" : [studentId]}, "course" : courseId }).count() > 0) {
  // student is enrolled in class
}

或者,您可以删除该"course" : courseId子句,然后获取该学生已注册的所有所有课程的集合。


这种方法的问题在于,只有学生注册了该课程,它才会从课程集合中返回文档。如果学生尚未注册,则需要第二次查询。这不是有效的。
shargors

第二个查询将是检索学生的对象ID?但这还不是已知的(毕竟,您正在使用它进行第一个查询)。
xbonez 2013年

最终目标是检索课程文档,并带有标记以指示学生是否已注册。您提供的查询仅在学生已注册时才检索课程文档。如果学生尚未注册课程,则需要另一个查询来检索课程文档。
shargors

1
我认为数据模型没有任何问题。我怀疑没有两个查询就无法真正实现想要的目标,因为最终,您要向数据库询问两个问题:给我课程文件,并且是该课程的学生。即使在传统的关系数据库中,您也将面临相同的问题。
xbonez

1
反应迟到(对不起!),但这并不是Mongo的目标。Mongo将存储非关系,无模式的数据对象。AFAIK,它不会尝试减少所需的查询数量。
xbonez 2014年

0

我试图通过提出问题陈述和解决方案来进行解释。希望对您有所帮助

问题陈述:

查找所有已发布产品,其名称应为ABC产品或PQR产品,且价格应低于15 /-

解:

以下是需要注意的条件

  1. 产品价格应小于15
  2. 产品名称应为ABC产品或PQR产品
  3. 产品应处于发布状态。

以下是适用以上条件的语句,用于创建查询和获取数据。

$elements = $collection->find(
             Array(
                [price] => Array( [$lt] => 15 ),
                [$or] => Array(
                            [0]=>Array(
                                    [product_name]=>Array(
                                       [$in]=>Array(
                                            [0] => ABC Product,
                                            [1]=> PQR Product
                                            )
                                        )
                                    )
                                ),
                [state]=>Published
                )
            );
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.