将$ lookup与条件连接一起使用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将$ lookup与条件连接一起使用相关的知识,希望对你有一定的参考价值。
如果我有以下文件
用户
{
uuid: string,
isActive: boolean,
lastLogin: datetime,
createdOn: datetime
}
项目
{
id: string,
users: [
{
uuid: string,
otherInfo: ...
},
{... more users}
]
}
我想选择所有自2周以来未登录且无效的用户,或者自5周以来没有项目的用户。
现在,2周工作正常,但我似乎无法弄清楚如何做“5周而且没有项目”部分
我提出了类似下面的内容,但最后一部分不起作用,因为$exists
显然不是顶级操作员。
有没有做过这样的事?谢谢!
return await this.collection
.aggregate([
{
$match: {
$and: [
{
$expr: {
$allElementsTrue: {
$map: {
input: [`$lastLogin`, `$createdOn`],
in: { $lt: [`$$this`, twoWeeksAgo] }
}
}
}
},
{
$or: [
{
isActive: false
},
{
$and: [
{
$expr: {
$allElementsTrue: {
$map: {
input: [`$lastLogin`, `$createdOn`],
in: { $lt: [`$$this`, fiveWeeksAgo] }
}
}
}
},
{
//No projects exists on this user
$exists: {
$lookup: {
from: _.get(Config, `env.collection.projects`),
let: {
currentUser: `$$ROOT`
},
pipeline: [
{
$project: {
_id: 0,
users: {
$filter: {
input: `$users`,
as: `user`,
cond: {
$eq: [`$$user.uuid`, `$currentUser.uuid`]
}
}
}
}
}
]
}
}
}
]
}
]
}
]
}
}
])
.toArray();
不确定为什么你认为在最初的$expr
需要$match
,但实际上:
const getResults = () => {
const now = Date.now();
const twoWeeksAgo = new Date(now - (1000 * 60 * 60 * 24 * 7 * 2 ));
const fiveWeeksAgo = new Date(now - (1000 * 60 * 60 * 24 * 7 * 5 ));
// as long a mongoDriverCollectionReference points to a "Collection" object
// for the "users" collection
return mongoDriverCollectionReference.aggregate([
// No $expr, since you can actually use an index. $expr cannot do that
{ "$match": {
"$or": [
// Active and "logged in"/created in the last 2 weeks
{
"isActive": true,
"$or": [
{ "lastLogin": { "$gte": twoWeeksAgo } },
{ "createdOn": { "$gte": twoWeeksAgo } }
]
},
// Also want those who...
// Not Active and "logged in"/created in the last 5 weeks
// we'll "tag" them later
{
"isActive": false,
"$or": [
{ "lastLogin": { "$gte": fiveWeeksAgo } },
{ "createdOn": { "$gte": fiveWeeksAgo } }
]
}
]
}},
// Now we do the "conditional" stuff, just to return a matching result or not
{ "$lookup": {
"from": _.get(Config, `env.collection.projects`), // there are a lot cleaner ways to register models than this
"let": {
"uuid": {
"$cond": {
"if": "$isActive", // this is boolean afterall
"then": null, // don't really want to match
"else": "$uuid" // Okay to match the 5 week results
}
}
},
"pipeline": [
// Nothing complex here as null will return nothing. Just do $in for the array
{ "$match": { "$in": [ "$$uuid", "$users.uuid" ] } },
// Don't really need the detail, so just reduce any matches to one result of [null]
{ "$group": { "_id": null } }
],
"as": "projects"
}},
// Now test if the $lookup returned something where it mattered
{ "$match": {
"$or": [
{ "active": true }, // remember we selected the active ones already
{
"projects.0": { "$exists": false } // So now we only need to know the "inactive" returned no array result.
}
]
}}
]).toArray(); // returns a Promise
};
它非常简单,因为通过$expr
计算的表达式实际上非常糟糕,而不是你想要的第一个管道阶段。另外“不是你需要的”,因为createdOn
和lastLogin
实际上不应该合并为$allElementsTrue
的数组,这只是一个AND条件,你描述的逻辑真的意味着OR。所以$or
在这里做得很好。
关于$or
的isActive
的条件分离,true/false
也是如此。它再次是“两周”或“五周”。这肯定不需要$expr
,因为标准的不等式范围匹配工作正常,并使用“索引”。
然后你真的只想在let
为$lookup
做“有条件”的事情而不是你的“它存在”的想法。所有你真正需要知道的(因为日期的范围选择实际上已经完成)是active
现在是true
还是false
。哪里是active
(意思是你的逻辑,你不关心项目)只是使$$uuid
管道阶段使用的$match
一个null
值,所以它将不匹配,并且$lookup
返回一个空数组。如果false
(也已经匹配了之前的日期条件)那么你使用实际值和“join”(当然有项目)。
然后它只是一个简单的问题,保持active
用户,然后只测试false
剩余的active
值,看看来自"projects"
的$lookup
数组实际上是否返回任何东西。如果没有,那么他们就没有项目。
可能应该注意这里是因为users
是projects
集合中的“数组”,你使用$in
作为$match
条件对阵列的单个值。
请注意,为简洁起见,我们可以在内部管道中使用$group
,只返回一个结果,而不是可能多次匹配到实际匹配的项目。你不关心内容或“计数”,但只是如果一个被退回或什么也没有。再次遵循提出的逻辑。
这可以获得您想要的结果,并且它以有效的方式实现,并且实际上使用可用的索引。
return await
肯定不会做你认为它做的事情,事实上它是一个ESLint警告信息(我建议你在你的项目中启用ESLint),因为这不是一个聪明的事情。它确实没有做任何事情,因为你需要await getResults()
(根据示例命名),因为await
关键字不是“魔术”,而只是编写then()
的更漂亮的方式。除了希望更容易理解之外,一旦你理解了async/await
在语法上的真正含义。
以上是关于将$ lookup与条件连接一起使用的主要内容,如果未能解决你的问题,请参考以下文章
在 mongodb 中使用 $lookup 指定多个连接条件