问题
A server is running multiple processes, process reads task from DB. Tasks contain status and _userid, which is external key for users collection. Look like like:
{
_userid: ObjectId(),
status: 'waiting',
date: Date()
}
Status can be: 'waiting', 'in progress', 'finished'. For every user I need tasks to run ordered by date and only one at a time. How can I build a query that selects one task:
- If nothing 'in progress', select first with status 'waiting' and update status to 'in progress';
- If something is 'in progress', select task that has status 'waiting' and does not belong to _userid of any task that has status 'in progress' (reminder: only one at a time for every user). And also update status to 'in progress'.
Is the query like this possible in MongoDB?
EDIT: For example, I've got a tasks in DB:
{
_userid: ObjectId(1),
status: 'waiting',
date: Date(1)
},
{
_userid: ObjectId(1),
status: 'waiting',
date: Date(2)
},
{
_userid: ObjectId(2),
status: 'waiting',
date: Date(3)
},
{
_userid: ObjectId(2),
status: 'waiting',
date: Date(4)
}
In this case, first task should be chosen from tasks, because everything is 'waiting'. After first task has been chosen it becomes 'in progress':
{
_userid: ObjectId(1),
status: 'in progress',
date: Date(1)
},
{
_userid: ObjectId(1),
status: 'waiting',
date: Date(2)
},
{
_userid: ObjectId(2),
status: 'waiting',
date: Date(3)
},
{
_userid: ObjectId(2),
status: 'waiting',
date: Date(4)
}
Now, next task should be chosen with status 'waiting' and not with _userid: ObjectId(1). It will be ObjectId(2) with Date(3). After that we have 2 _userid 'in progress': ObjectId(1) and ObjectId(2), so next request should receive nothing.
{
_userid: ObjectId(1),
status: 'in progress',
date: Date(1)
},
{
_userid: ObjectId(1),
status: 'waiting',
date: Date(2)
},
{
_userid: ObjectId(2),
status: 'in progress',
date: Date(3)
},
{
_userid: ObjectId(2),
status: 'waiting',
date: Date(4)
}
When first task is finished it's status will be 'finished' and now query must receive second task for ObjectId(1), because it's status 'waiting' and nothing else runs for this ObjectId(1).
{
_userid: ObjectId(1),
status: 'finished',
date: Date(1)
},
{
_userid: ObjectId(1),
status: 'in progress',
date: Date(2)
},
{
_userid: ObjectId(2),
status: 'in progress',
date: Date(3)
},
{
_userid: ObjectId(2),
status: 'waiting',
date: Date(4)
}
EDIT 2: OK, irrelevant any more
回答1:
Check this one:
db.task.aggregate([
{
$sort: {
_userid: 1,
date: 1
}
},
{
$group: {
_id: "$_userid",
task: {
$push: "$$ROOT"
}
}
}
]).forEach(function(doc){
for(var i=0; i<doc.task.length; i++){
//If we want to check if there is any "in progress" for this user
//doc.task.filter(x => x.status == "in progress").length > 0
if(doc.task[i].status == "in progress") {
//Nothing to do
break;
} else if(doc.task[i].status == "waiting") {
//We set i element to in progress
doc.task[i].status = "in progress";
db.task.save(doc.task[i]);
break;
} else if(doc.task[i].status == "finished") {
//We skip this task
continue;
}
}
});
If you guarantee that the "finished" tasks are sorted by date, you can filter them.
db.task.aggregate([
{
$match: {
status: {
$ne: "finished"
}
}
},
{
$sort: {
_userid: 1,
date: 1
}
},
...
来源:https://stackoverflow.com/questions/60523655/mongodb-select-and-update-with-complicated-conditions