如何使用 MongoDB 聚合 `$lookup` 作为 `findOne()`
Posted
技术标签:
【中文标题】如何使用 MongoDB 聚合 `$lookup` 作为 `findOne()`【英文标题】:How to use MongoDBs aggregate `$lookup` as `findOne()` 【发布时间】:2016-10-08 02:03:36 【问题描述】:众所周知,find()
返回一个结果数组,findOne()
只返回一个简单的对象。
对于 Angular,这会产生巨大的差异。我可以直接写myresult.name
,而不是myresult[0].name
。
我发现聚合管道中的$lookup
方法返回一个结果数组,而不仅仅是一个对象。
例如,我有两个集合:
users
收藏:
[
"firstName": "John",
"lastName": "Smith",
"country": 123
,
"firstName": "Luke",
"lastName": "Jones",
"country": 321
]
countries
收藏:
[
"name": "Australia",
"code": "AU",
"_id": 123
,
"name": "New Zealand",
"code": "NZ",
"_id": 321
]
我的聚合$lookup
:
db.users.aggregate([
$project:
"fullName":
$concat: ["$firstName", " ", "$lastName"]
,
"country": "$country"
,
$lookup:
from: "countries",
localField: "country",
foreignField: "_id",
as: "country"
])
查询结果:
[
"fullName": "John Smith",
"country": [
"name": "Australia",
"code": "AU",
"_id": 123
]
,
"fullName": "Luke Jones",
"country": [
"name": "New Zealand",
"code": "NZ",
"_id": 321
]
]
从上面的结果可以看出,每个country
都是一个数组,而不是像"country": ....
这样的单个对象。
我怎样才能让我的 $lookup
返回单个对象而不是数组,因为它只会匹配单个文档?
【问题讨论】:
我也觉得这应该从他们的角度来处理。如果只有一个匹配,它应该返回一个对象,而不是一个包含单个对象的数组。 【参考方案1】:db.users.aggregate([
$lookup:
from: 'countries',
localField: 'country',
foreignField: '_id',
as: 'country'
,
$unwind: '$country'
]).pretty()
您可以使用这个 mongo 查询来获取国家/地区对象
【讨论】:
【参考方案2】:当您不想重复项目中的所有字段时,只需使用 $addFields 覆盖有问题的字段即可:
db.users.aggregate([
"$project":
"fullName":
"$concat": [ "$firstName", " ", "$lastName"]
,
"country": "$country"
,
"$lookup":
"from": "countries",
"localField": "country",
"foreignField": "_id",
"as": "countryInfo"
,
"$addFields":
"countryInfo":
"$arrayElemAt": [ "$countryInfo", 0 ]
])
【讨论】:
这是我提供的解决方案。$project
在向集合中添加更多字段时使代码的可维护性降低。如果你在做$unwind
,请确保"preserveNullAndEmptyArrays": true
。但是,如果找到多个匹配项,则会返回意外结果。这个答案应该适用于findOne
关系的所有情况【参考方案3】:
你也可以使用"preserveNullAndEmptyArrays"
像这样:
db.users.aggregate(
[
"$project":
"fullName":
"$concat": [ "$firstName", " ", "$lastName"]
,
"country": "$country"
,
"$lookup":
"from": "countries",
"localField": "country",
"foreignField": "_id",
"as": "countryInfo"
,
"$unwind":
"path": "$countryInfo",
"preserveNullAndEmptyArrays": true
,
]
)
【讨论】:
$unwind: path: '$countryInfo' 就足够了。 为了方便使用,为什么不创建视图并将其作为普通集合引用(有一些限制)docs.mongodb.com/manual/core/views $unwind: '$countryInfo' 也够了 @benipsen 如果您没有 1-1 关系,将忽略preserveNullAndEmptyArrays
过滤掉没有 countryInfo
的身份或将 countryInfo
未定义?
@CervEd 感谢您提出这个可能的问题。我测试过,确实这似乎是一个问题——在这种情况下,$unwind 实际上是一个完全连接,如果数组为空,它将删除整个条目。所以,"preserveNullAndEmptyArrays": true
是必要的。【参考方案4】:
您快到了,您需要将另一个$project
阶段添加到您的管道并使用$arrayElemAt
返回数组中的单个元素。
db.users.aggregate(
[
"$project":
"fullName":
"$concat": [ "$firstName", " ", "$lastName"]
,
"country": "$country"
,
"$lookup":
"from": "countries",
"localField": "country",
"foreignField": "_id",
"as": "countryInfo"
,
"$project":
"fullName": 1,
"country": 1,
"countryInfo": "$arrayElemAt": [ "$countryInfo", 0 ]
]
)
【讨论】:
现在你也可以使用$unwind
docs.mongodb.com/manual/reference/operator/aggregation/unwind
作为游戏后期的 2 美分附加组件,我喜欢 $lookup
和 $unwind
的配对。我的一部分大脑喜欢认为这种组合可以在服务器端以类似于$limit
和$sort
组合的方式进行优化(其中sort
操作仅跟踪limit
记录)。
如果你想控制哪些字段包含在使用$arrayElemAt
选择的元素中——无需执行另一个$project
阶段——你可以使用$let
,如下所述:***.com/a/39650846/483520跨度>
用 $set 代替 $project 怎么样?以上是关于如何使用 MongoDB 聚合 `$lookup` 作为 `findOne()`的主要内容,如果未能解决你的问题,请参考以下文章