更新数组子级排序顺序



我有一个包含具有以下结构的对象的集合

{
"dep_id": "some_id",
"departament": "dep name",
"employees": [{
"name": "emp1",
"age": 31
},{
"name": "emp2",
"age": 35
}]
}

我想按employees.age降序对id为"some_id"的对象的employees数组进行排序并保存。最好的结果是使用mongodb的查询语言以原子方式实现这一点。这可能吗?

如果没有,如何在不影响父级的其他数据或子文档的数据的情况下重新排列子文档?如果我必须从数据库下载数据并保存回已排序的子数组,那么如果其他东西对其中一个子执行更新,或者在此期间添加或删除子,会发生什么?

最后,数据应该像这样持久化到数据库中:

{
"dep_id": "some_id",
"departament": "dep name",
"employees": [{
"name": "emp2",
"age": 35
},{
"name": "emp1",
"age": 31
}]
}
执行此操作的最佳方法是在向数组添加项时实际应用$sort修饰符。正如你在评论中所说,"我的实际对象有一个"rank"one_answers"created_at'",这意味着你真的应该在问题中问这个问题,而不是写一个"人为"的案例(不知道人们为什么这么做)。

因此,对于按多个属性进行"排序",以下引用将进行如下调整:

db.collection.update(
{  },
{ "$push": { "employees": { "$each": [], "$sort": { "rank": -1, "created_at": -1 } } } },
{ "multi": true }
)

但是,要更新您目前拥有的所有数据,"如问题所示",那么您将在"age"上使用进行排序

db.collection.update(
{  },
{ "$push": { "employees": { "$each": [], "$sort": { "age": -1 } } } },
{ "multi": true }
)

哪个奇怪地使用$push来实际"修改"数组?是的,这是真的,因为$each修饰符说我们实际上没有添加任何新的内容,但$sort修饰符实际上将应用于现有的数组并"重新排序"它。

当然,这将解释如何写入阵列的"新"更新,以便应用$sort并确保"最大年龄"始终是阵列中的"第一个":

db.collection.update(
{ "dep_id": "some_id" },
{ "$push": { 
"employees": { 
"$each": [{ "name": "emp": 3, "age": 32 }],
"$sort": { "age": -1 } 
}
}}
)

因此,当您在更新时将新条目添加到数组中时,会应用$sort修饰符,并将新元素重新定位在两个现有元素之间,因为这是它的排序位置。

这是MongoDB的常见模式,通常与$slice修饰符结合使用,以便在添加新项时保持数组的"最大"长度,同时保留"有序"结果。通常情况下,"排名"就是确切的用法。

因此,总的来说,您实际上可以"更新"现有数据,并使用"一个简单的原子语句"对其进行重新排序。不需要循环或集合重命名。此外,您现在有了一个简单的原子方法来"更新"数据,并在添加或删除新数组项时保持该顺序。

为了得到您想要的东西,您可以使用以下查询:

db.collection.aggregate({
$unwind: "$employees" // flatten employees array
}, {
$sort: {
"employees.name": -1 // sort all documents by employee name (descending)
}
}, {
$group: { // restore the previous structure
_id: "$_id",
"dep_id": {
$first: "$dep_id"
},
"departament": {
$first: "$departament"
},
"employees": {
$push: "$employees"
},
}
}, {
$out: "output" // write everything out to a separate collection
})

完成此步骤后,您需要删除源表,并重命名"输出"集合以匹配源表名称。

但是,这个解决方案不会处理并发问题。因此,您应该首先从集合中删除写访问权限,这样在迁移过程中就不会有人修改它,然后在完成迁移后恢复它。

或者,您可以先查询所有数据,然后在客户端对employees数组进行排序,然后使用单个更新查询,或者使用更快速但更复杂的批量写入操作和所有单独的更新调用来更新现有文档。在这里,您可以使用最初读取的整个文档作为更新操作的过滤器。因此,如果一个单独的更新没有修改任何你会马上知道的文档,那么其他的更改一定修改了你之前阅读的文档。对于这些情况,您需要稍后重试(或者直接重试,直到更新真正修改文档为止)。

最新更新