在 MongoDB 中执行排序查询
Performing sorting query in MongoDB
我想在 MongoDB 中进行这个复杂的排序查询,但我未能实现。
集合中的模型如下所示:
_id: UUID('some-id'),
isDeleted: false,
date: ISODate('some-date'),
responses: [{
_id: UUID('some-id'),
userId: UUID('some-id'),
response: 0
}, {
_id: UUID('some-id'),
userId: UUID('some-id'),
response: 1
}]
要记住的一件事是,响应数组中始终包含 2 或 3 个对象。不多也不少。此外,响应将只有三个值,0、1 或 2。
我想做的是根据每个用户的反应对它们进行不同的排序。
所以假设我的名为 Events
的集合在数据库中有很多对象。我希望当我过滤它们时,排序将像这样完成:
If my response is 0 and others are either 0 or 1, then sort them always first.
If all responses are 1, sort them after.
Others (if any response is 2, or if my response is 1 but others are 1 or 0), sort them last.
我们可以通过在查询中传递 userId 来确定是否是我的回复。
最重要的是,我需要分页,所以我需要实现 $skip 和 $limit。
尝试使用 $unwind 然后 $project 尝试做一些基于 scoring 的排序,但无法实现。
得分排序看起来像这样:
if my response is 0 and others are 0 or 1 -> score = 100
if all responses are 1 -> score = 50
all others -> score = 0
这样我们就可以按分数排序了。但我不知道如何实际创建这个 属性。
我想我们可以像这样创建一个 属性:
$project: {
myScore: {
$cond: {
if: {
$in: [
UUID('my-user-id'),
"$responses.userId"
],
then: "$respones.response", //this is returning array here with all responses
else: 0
}
}
},
totalScore: {
$sum: "$respones.response"
}
}
然后我们可以进行另一个阶段,以某种方式对这些数字进行排序。
谢谢! :)
这里是一个稍微简化的输入集。我们还包含一个 target
字段以帮助测试评分算法;对于最终的流水线,它不是必需的,其中 score 是 A、B、C,表示排序顺序中的第一个、中间的和最后一个。只要排序正确,分数可以是“任何东西”。我使用了 A、B 和 C,因为它在视觉上与我们正在查看的响应代码 (0,1,2) 不同,因此管道函数更容易理解,但它可能是 10、20、30 或 5,10 ,15.
var myUserId = 1;
var r = [
{
target: 'C', // last, myUserId response is 1
responses: [
{userId:0, response:0},
{userId:1, response:1}
]
}
,{
target: 'C', // last, myUserId response is 1
responses: [
{userId:1, response:1},
{userId:0, response:0}
]
}
,{
target: 'A', // first, myUserId response is 0
responses: [
{userId:1, response:0},
{userId:0, response:0}
]
}
,{
target: 'B', // mid, all 1s
responses: [
{userId:7, response:1},
{userId:9, response:1}
]
}
,{
target: 'C', // last, a 2 exists
responses: [
{userId:4, response:2},
{userId:3, response:1},
{userId:1, response:0}
]
}
];
此管道将产生所需的输出:
db.foo.aggregate([
{$addFields: {score:
{$cond: [
{$in: [2, '$responses.response']}, // any 2s?
'C', // yes, assign last
{$cond: [ // else
// All responses 1 (i.e. set diff is from 1 is empty set []?
{$eq: [ {$setDifference:['$responses.response',[1]]}, [] ] },
'B', // yes, assign mid
{$cond: [ // else
// Does myUserId have a response of 0? filter the
// array on these 2 fields and if the size of the
// filtered array != 0, that means you found one!
{$ne:[0, {$size:{$filter:{input:'$responses',
cond:{$and:[
{$eq:['$$this.userId',myUserId]},
{$eq:['$$this.response',0]}
]}
}} } ]},
'A', // yes, assign first
'C', // else last for rest
]}
]}
]}
}}
,{$sort: {'score':1}}
// TEST: Show items where target DOES NOT equal score. If the pipeline
// logic is working, this stage will produce zero output; that's
// how you know it works.
//,{$match: {$expr: {$ne:['$score','$target']}} }
]);
任何想知道这个的人,这就是我想出的。 p.s。我还决定,如果任何响应包含响应 2,我需要忽略所有项目,因此我将只关注值 0 和 1。
db.invites.aggregate([
{
$match: {
"$responses.response": {
$ne: 2
}
}
},
{
$addFields: {
"myScore": {
"$let": {
"vars": {
"invite": {
// get only object that contains my userId and get firs item from the list (as it will always be one in the list)
"$arrayElemAt": [{
"$filter": {
"input": "$responses",
"as": "item",
"cond": {"$eq": ["$$item.userId", UUID('some-id')]}
}} ,0]
}
},
// ger response value of that object that contains my userId
"in": "$$invite.response"
}
},
// as they are only 0 and 1s in the array items, we can see how many percent have voted with one.
// totalScore = sum(responses.response) / size(responses)
"totalScore": {
$divide: [{$sum: "$responses.response"} , {$size: "$responses"}]
}
}
},
{
$sort: {
//sort by my score, so if I have responded with 0, show first
"myScore": 1,
//sort by totalScore, so if I have responded 1, show those that have all 1s first.
"totalScore": -1
}
}
])
我想在 MongoDB 中进行这个复杂的排序查询,但我未能实现。
集合中的模型如下所示:
_id: UUID('some-id'),
isDeleted: false,
date: ISODate('some-date'),
responses: [{
_id: UUID('some-id'),
userId: UUID('some-id'),
response: 0
}, {
_id: UUID('some-id'),
userId: UUID('some-id'),
response: 1
}]
要记住的一件事是,响应数组中始终包含 2 或 3 个对象。不多也不少。此外,响应将只有三个值,0、1 或 2。
我想做的是根据每个用户的反应对它们进行不同的排序。
所以假设我的名为 Events
的集合在数据库中有很多对象。我希望当我过滤它们时,排序将像这样完成:
If my response is 0 and others are either 0 or 1, then sort them always first.
If all responses are 1, sort them after.
Others (if any response is 2, or if my response is 1 but others are 1 or 0), sort them last.
我们可以通过在查询中传递 userId 来确定是否是我的回复。
最重要的是,我需要分页,所以我需要实现 $skip 和 $limit。
尝试使用 $unwind 然后 $project 尝试做一些基于 scoring 的排序,但无法实现。
得分排序看起来像这样:
if my response is 0 and others are 0 or 1 -> score = 100
if all responses are 1 -> score = 50
all others -> score = 0
这样我们就可以按分数排序了。但我不知道如何实际创建这个 属性。
我想我们可以像这样创建一个 属性:
$project: {
myScore: {
$cond: {
if: {
$in: [
UUID('my-user-id'),
"$responses.userId"
],
then: "$respones.response", //this is returning array here with all responses
else: 0
}
}
},
totalScore: {
$sum: "$respones.response"
}
}
然后我们可以进行另一个阶段,以某种方式对这些数字进行排序。
谢谢! :)
这里是一个稍微简化的输入集。我们还包含一个 target
字段以帮助测试评分算法;对于最终的流水线,它不是必需的,其中 score 是 A、B、C,表示排序顺序中的第一个、中间的和最后一个。只要排序正确,分数可以是“任何东西”。我使用了 A、B 和 C,因为它在视觉上与我们正在查看的响应代码 (0,1,2) 不同,因此管道函数更容易理解,但它可能是 10、20、30 或 5,10 ,15.
var myUserId = 1;
var r = [
{
target: 'C', // last, myUserId response is 1
responses: [
{userId:0, response:0},
{userId:1, response:1}
]
}
,{
target: 'C', // last, myUserId response is 1
responses: [
{userId:1, response:1},
{userId:0, response:0}
]
}
,{
target: 'A', // first, myUserId response is 0
responses: [
{userId:1, response:0},
{userId:0, response:0}
]
}
,{
target: 'B', // mid, all 1s
responses: [
{userId:7, response:1},
{userId:9, response:1}
]
}
,{
target: 'C', // last, a 2 exists
responses: [
{userId:4, response:2},
{userId:3, response:1},
{userId:1, response:0}
]
}
];
此管道将产生所需的输出:
db.foo.aggregate([
{$addFields: {score:
{$cond: [
{$in: [2, '$responses.response']}, // any 2s?
'C', // yes, assign last
{$cond: [ // else
// All responses 1 (i.e. set diff is from 1 is empty set []?
{$eq: [ {$setDifference:['$responses.response',[1]]}, [] ] },
'B', // yes, assign mid
{$cond: [ // else
// Does myUserId have a response of 0? filter the
// array on these 2 fields and if the size of the
// filtered array != 0, that means you found one!
{$ne:[0, {$size:{$filter:{input:'$responses',
cond:{$and:[
{$eq:['$$this.userId',myUserId]},
{$eq:['$$this.response',0]}
]}
}} } ]},
'A', // yes, assign first
'C', // else last for rest
]}
]}
]}
}}
,{$sort: {'score':1}}
// TEST: Show items where target DOES NOT equal score. If the pipeline
// logic is working, this stage will produce zero output; that's
// how you know it works.
//,{$match: {$expr: {$ne:['$score','$target']}} }
]);
任何想知道这个的人,这就是我想出的。 p.s。我还决定,如果任何响应包含响应 2,我需要忽略所有项目,因此我将只关注值 0 和 1。
db.invites.aggregate([
{
$match: {
"$responses.response": {
$ne: 2
}
}
},
{
$addFields: {
"myScore": {
"$let": {
"vars": {
"invite": {
// get only object that contains my userId and get firs item from the list (as it will always be one in the list)
"$arrayElemAt": [{
"$filter": {
"input": "$responses",
"as": "item",
"cond": {"$eq": ["$$item.userId", UUID('some-id')]}
}} ,0]
}
},
// ger response value of that object that contains my userId
"in": "$$invite.response"
}
},
// as they are only 0 and 1s in the array items, we can see how many percent have voted with one.
// totalScore = sum(responses.response) / size(responses)
"totalScore": {
$divide: [{$sum: "$responses.response"} , {$size: "$responses"}]
}
}
},
{
$sort: {
//sort by my score, so if I have responded with 0, show first
"myScore": 1,
//sort by totalScore, so if I have responded 1, show those that have all 1s first.
"totalScore": -1
}
}
])