我正在尝试动态粘贴排序记录集合,其中每个查询的粘性值都不同。让我举个例子。以下是一些示例文档:
{first_name: 'Joe', last_name: 'Blow', offices: ['GA', 'FL']}
{first_name: 'Joe', last_name: 'Johnson', offices: ['FL']}
{first_name: 'Daniel', last_name: 'Aiken', offices: ['TN', 'SC']}
{first_name: 'Daniel', last_name: 'Madison', offices: ['SC', 'GA']}
... a bunch more names ...
现在假设我想按姓氏的字母顺序显示姓名,但我想在顶部以第一个名字“Joe”挂起所有记录。
在SQL中,这是非常直接的:
SELECT * FROM people ORDER first_name == 'Joe' DESC, last_name
将表达式放在排序标准中的能力使这一点变得微不足道。使用聚合框架我可以这样做:
[
{$project: {
first_name: 1,
last_name: 1
offices: 1,
sticky: {$cond: [{$eq: ['$first_name', 'Joe']}, 1, 0]}
}},
{$sort: [
'sticky': -1,
'last_name': 1
]}
]
基本上我使用聚合框架创建一个动态字段,如果名称为Joe则为1,如果名称不是Joe则为0,然后按相反顺序排序。当然,在构建我的聚合管道时,我可以很容易地将'Joe'改为'Daniel',现在'Daniel'将被固定在顶部。这部分是我所说的动态粘性排序。我粘贴排序的值将更改逐个查询
现在这对于像字符串这样的基本值很有用。当我为一个包含数组的值尝试相同的东西时,问题就来了。假设我想要挂钩“FL”办公室的所有用户。有了Mongo对数组的原生理解,我认为我可以做同样的事情。所以:
[
{$project: {
first_name: 1,
last_name: 1
offices: 1,
sticky: {$cond: [{$eq: ['$offices', 'FL']}, 1, 0]}
}},
{$sort: [
'sticky': -1,
'last_name': 1
]}
]
但这根本不起作用。我确实知道,如果我将其更改为以下内容,那么Joe Johnson(仅在FL办公室)就会出现在顶部:
[
{$project: {
first_name: 1,
last_name: 1
offices: 1,
sticky: {$cond: [{$eq: ['$offices', ['FL']]}, 1, 0]}
}},
{$sort: [
'sticky': -1,
'last_name': 1
]}
]
但它没有把Joe Blow放在首位(谁是FL和GA)。我相信它正在进行简单的匹配。所以我的第一次尝试根本不起作用,因为$ eq返回false,因为我们正在将数组与字符串进行比较。第二次尝试适用于Joe Johnson,因为我们正在比较完全相同的数组。但是Joe Blow自['GA','FL']以来没有工作!= ['FL']。此外,如果我想在顶部固定FL和SC,我不能给它值['FL','SC']来比较。
接下来,我尝试使用$ setUnion和$ size的组合。
[
{$project: {
first_name: 1,
last_name: 1
offices: 1,
sticky: {$size: {$setUnion: ['$offices', ['FL', 'SC']]}}
}},
{$sort: [
'sticky': -1,
'last_name': 1
]}
]
我尝试过使用$ let和$ literal的各种组合,但它总是抱怨我试图将一个文字数组传递给$ setUnion的参数。具体来说它说:
disallowed field type Array in object expression
有没有办法做到这一点?
答案 0 :(得分:1)
无法重现您的错误,但您有一些"拼写错误"在你的问题中,所以我无法确定你的实际情况。
但假设您实际上正在使用MongoDB 2.6或更高版本,那么您可能需要$setIntersection
或$setIsSubset
运算符而不是$setUnion
。那些运营商暗示"匹配"与它们进行比较的数组的内容,其中$setUnion
只是将提供的数组与现有数组相结合:
db.people.aggregate([
{ "$project": {
"first_name": 1,
"last_name": 1,
"sticky": {
"$size": {
"$setIntersection": [ "$offices", [ "FL", "SC" ]]
}
},
"offices": 1
}},
{ "$sort": {
"sticky": -1,
"last_name": 1
}}
])
在您没有set operators的先前版本中,您只是使用$unwind
来处理数组,并且$cond
操作与$group
之前的操作相同将它们全部重新组合在一起:
db.people.aggregate([
{ "$unwind": "$offices" },
{ "$group": {
"_id": "$_id",
"first_name": { "$first": "$first_name" },
"last_name": { "$first": "$last_name",
"sticky": { "$sum": { "$cond": [
{ "$or": [
{ "$eq": [ "$offices": "FL" ] },
{ "$eq": [ "$offices": "SC" ] },
]},
1,
0
]}},
"offices": { "$push": "$offices" }
}},
{ "$sort": {
"sticky": -1,
"last_name": 1
}}
])
但你肯定是在正确的轨道上。只需选择正确的设置操作或其他方法即可满足您的需求。
或者既然你已经发布了获得你想要的东西的方式,那么更好的方式来编写那种"有序匹配"是这样的:
db.people.aggregate([
{ "$project": {
"first_name": 1,
"last_name": 1,
"sticky": { "$cond": [
{ "$anyElementTrue": {
"$map": {
"input": "$offices",
"as": "o",
"in": { "$eq": [ "$$o", "FL" ] }
}
}},
2,
{ "$cond": [
{ "$anyElementTrue": {
"$map": {
"input": "$offices",
"as": "o",
"in": { "$eq": [ "$$o", "SC" ] }
}
}},
1,
0
]}
]},
"offices": 1
}},
{ "$sort": {
"sticky": -1,
"last_name": 1
}}
])
这将优先考虑" office"包含" FL"过" SC"因此,然后超过所有其他人,并在一个领域内进行操作。对于人们来说,这应该也很容易看到如何在没有set运算符的早期版本中使用$unwind
将其抽象为表单。你只需提供更高的重量"通过嵌套$cond
语句,将值放在顶部所需的项目上。
答案 1 :(得分:0)
我想我找到了最佳方法。
[
{$project: {
first_name: 1,
last_name: 1
offices: 1,
sticky_0: {
$cond: [{
$anyElementTrue: {
$map: {
input: "$offices",
as: "j",
in: {$eq: ['$$j', 'FL']}
}
}
}, 0, 1]
},
sticky_1": {
$cond: [{
$anyElementTrue: {
$map: {
input: '$offices',
as: 'j',
in: {$eq: ['$$j', 'SC']}
}
}
}, 0, 1]
}
}},
{$sort: [
'sticky_0': 1,
'sticky_1': 1,
'last_name': 1
]}
]
基本上在构建我的管道时,我会迭代我想要粘贴的每个项目,并从该项目创建它自己的虚拟字段,只检查一个值。要检查一个值,我使用$ cond,$ anyElementTrue和$ map的组合。这有点令人费解,但它确实有效。如果有更简单的事情,我很乐意听到。