如何在 MongoDB 中对集合记录中的数组进行排序?
Posted
技术标签:
【中文标题】如何在 MongoDB 中对集合记录中的数组进行排序?【英文标题】:How to sort array inside collection record in MongoDB? 【发布时间】:2012-11-07 03:26:05 【问题描述】:我有一组学生,每个学生的记录如下所示,我想按score
的降序对scores
数组进行排序。
这个咒语在 mongo shell 上是什么样子的?
> db.students.find('_id': 1).pretty()
"_id" : 1,
"name" : "Aurelia Menendez",
"scores" : [
"type" : "exam",
"score" : 60.06045071030959
,
"type" : "quiz",
"score" : 52.79790691903873
,
"type" : "homework",
"score" : 71.76133439165544
,
"type" : "homework",
"score" : 34.85718117893772
]
我正在尝试这个咒语......
doc = db.students.find()
for (_id,score) in doc.scores:
print _id,score
但它不起作用。
【问题讨论】:
【参考方案1】:您将需要在应用程序代码中操作嵌入数组或使用 MongoDB 2.2 中的新 Aggregation Framework。
mongo
shell 中的示例聚合:
db.students.aggregate(
// Initial document match (uses index, if a suitable one is available)
$match:
_id : 1
,
// Expand the scores array into a stream of documents
$unwind: '$scores' ,
// Filter to 'homework' scores
$match:
'scores.type': 'homework'
,
// Sort in descending order
$sort:
'scores.score': -1
)
示例输出:
"result" : [
"_id" : 1,
"name" : "Aurelia Menendez",
"scores" :
"type" : "homework",
"score" : 71.76133439165544
,
"_id" : 1,
"name" : "Aurelia Menendez",
"scores" :
"type" : "homework",
"score" : 34.85718117893772
],
"ok" : 1
【讨论】:
您可以将聚合管道的末尾更改为按升序排序(因此最低值在前)并限制为 1 个文档:` $sort: 'scores.score': 1 , $limit : 1 ` 重复数据的问题,你要在每个对象中重复名称。所以如果我在上层有 20 个字段,那么我应该重复吗? @PrabjotSingh 我不完全清楚您的问题是什么,但您应该发布一个新问题,其中包含文档结构、所需输出和 MongoDB 服务器版本的示例,而不是在 cmets 中讨论/驱动程序。 我同意@PrabjotSingh 分数作为嵌入式数组返回?正如问题所暗示的那样。 @F.O.O 这个问题已有 6.5 年历史,现在有不同的选项,具体取决于您的 MongoDB 服务器版本。请发布一个新问题,其中包含与您的环境相关的详细信息以及您要解决的问题。【参考方案2】:由于这个问题可以通过不同的方式进行管理,我想说另一种解决方案是“插入和排序”,这样您将在创建 Find() 的那一刻得到 Ordered 数组。
考虑这些数据:
"_id" : 5,
"quizzes" : [
"wk": 1, "score" : 10 ,
"wk": 2, "score" : 8 ,
"wk": 3, "score" : 5 ,
"wk": 4, "score" : 6
]
这里我们将更新文档,进行排序。
db.students.update(
_id: 5 ,
$push:
quizzes:
$each: [ wk: 5, score: 8 , wk: 6, score: 7 , wk: 7, score: 6 ],
$sort: score: -1 ,
$slice: 3 // keep the first 3 values
)
结果是:
"_id" : 5,
"quizzes" : [
"wk" : 1, "score" : 10 ,
"wk" : 2, "score" : 8 ,
"wk" : 5, "score" : 8
]
文档: https://docs.mongodb.com/manual/reference/operator/update/sort/#up._S_sort
【讨论】:
我们可以在存储的数组字段上使用 $each 吗?【参考方案3】:这就是我们可以用 JS 和 mongo 控制台解决这个问题的方法:
db.students.find("scores.type": "homework").forEach(
function(s)
var sortedScores = s.scores.sort(
function(a, b)
return a.score<b.score && a.type=="homework";
);
var lowestHomeworkScore = sortedScores[sortedScores.length-1].score;
db.students.update(_id: s._id,$pull: scores: score: lowestHomeworkScore, multi: true);
)
【讨论】:
老兄?你破坏了乐趣。 find() 中的"scores.type": "homework"
过滤表达式有什么作用吗?
@TreefishZhang 为什么不应该呢?
@AlexanderPanasyuk 它实现了什么? -它过滤掉了一些学生吗?【参考方案4】:
这里是java代码,可用于找出数组中的最低分数并将其删除。
public class sortArrayInsideDocument
public static void main(String[] args) throws UnknownHostException
MongoClient client = new MongoClient();
DB db = client.getDB("school");
DBCollection lines = db.getCollection("students");
DBCursor cursor = lines.find();
try
while (cursor.hasNext())
DBObject cur = cursor.next();
BasicDBList dbObjectList = (BasicDBList) cur.get("scores");
Double lowestScore = new Double(0);
BasicDBObject dbObject = null;
for (Object doc : dbObjectList)
BasicDBObject basicDBObject = (BasicDBObject) doc;
if (basicDBObject.get("type").equals("homework"))
Double latestScore = (Double) basicDBObject
.get("score");
if (lowestScore.compareTo(Double.valueOf(0)) == 0)
lowestScore = latestScore;
dbObject = basicDBObject;
else if (lowestScore.compareTo(latestScore) > 0)
lowestScore = latestScore;
dbObject = basicDBObject;
// remove the lowest score here.
System.out.println("object to be removed : " + dbObject + ":"
+ dbObjectList.remove(dbObject));
// update the collection
lines.update(new BasicDBObject("_id", cur.get("_id")), cur,
true, false);
finally
cursor.close();
【讨论】:
不错!很好的例子...使用 java 8 我们可以最小化比较部分。 @Vel 如何从dbObjectList
中删除dbObject
,从cur
DBObject 中删除? cur
和 dbObjectList
之间的联系是什么?【参考方案5】:
要对数组进行排序,请按照下列步骤操作:
1) 使用 unwind 遍历数组
2)排序数组
3)使用group将数组的对象合并为一个数组
4)然后投影其他字段
查询
db.taskDetails.aggregate([
$unwind:"$counter_offer",
$match:_id:ObjectId('5bfbc0f9ac2a73278459efc1'),
$sort:"counter_offer.Counter_offer_Amount":1,
$unwind:"$counter_offer",
"$group" : _id:"$_id",
counter_offer: $push: "$counter_offer" ,
"task_name": "$first": "$task_name",
"task_status": "$first": "$task_status",
"task_location": "$first": "$task_location",
]).pretty()
【讨论】:
与$addToSet
相反,使用$push
保留数组的顺序,在上一步排序。【参考方案6】:
从 Mongo 5.2
release schedule 开始,这是新的 $sortArray
聚合运算符的确切用例:
//
// name: "Aurelia Menendez",
// scores: [
// type: "exam", score: 60.06
// type: "quiz", score: 52.79
// type: "homework", score: 71.76
// type: "homework", score: 34.85
// ]
//
db.collection.aggregate([
$set:
scores:
$sortArray:
input: "$scores",
sortBy: score: -1
])
//
// name: "Aurelia Menendez",
// scores: [
// type: "homework", score: 71.76 ,
// type: "exam", score: 60.06 ,
// type: "quiz", score: 52.79 ,
// type: "homework", score: 34.85
// ]
//
这个:
排序 ($sortArray
) scores
数组 (input: "$scores"
)
通过对score
s (sortBy: score: -1
) 应用排序
无需组合使用昂贵的 $unwind
、$sort
和 $group
阶段
【讨论】:
【参考方案7】:这很容易猜到,但无论如何,尽量不要在 mongo 大学课程上作弊,因为那样你就不会了解基础知识。
db.students.find().forEach(function(student)
var minHomeworkScore,
scoresObjects = student.scores,
homeworkArray = scoresObjects.map(
function(obj)
return obj.score;
);
minHomeworkScore = Math.min.apply(Math, homeworkArray);
scoresObjects.forEach(function(scoreObject)
if(scoreObject.score === minHomeworkScore)
scoresObjects.splice(scoresObjects.indexOf(minHomeworkScore), 1);
);
printjson(scoresObjects);
);
【讨论】:
【参考方案8】:Order Title 和 Array title 并返回整个集合数据 Collection name is menu
[
"_id": "5f27c5132160a22f005fd50d",
"title": "Gift By Category",
"children": [
"title": "Ethnic Gift Items",
"s": "/gift?by=Category&name=Ethnic"
,
"title": "Novelty Gift Items",
"link": "/gift?by=Category&name=Novelty"
],
"active": true
,
"_id": "5f2752fc2160a22f005fd50b",
"title": "Gift By Occasion",
"children": [
"title": "Gifts for Diwali",
"link": "/gift-for-diwali"
,
"title": "Gifts for Ganesh Chathurthi",
"link": "/gift-for-ganesh-chaturthi",
],
"active": true
]
如下查询
let menuList = await Menu.aggregate([
$unwind: '$children'
,
$sort:"children.title":1
,
$group : _id : "$_id",
root: $mergeObjects: '$$ROOT' ,
children: $push: "$children"
,
$replaceRoot:
newRoot:
$mergeObjects: ['$root', '$$ROOT']
,
$project:
root: 0
,
$match:
$and:['active':true],
,
$sort:"title":1
]);
【讨论】:
【参考方案9】:我相信你正在做M101P: MongoDB for Developers
,其中作业 3.1 是从两个作业分数中删除较低的一个。由于到目前为止还没有教授聚合,因此您可以执行以下操作:
import pymongo
conn = pymongo.MongoClient('mongodb://localhost:27017')
db = conn.school
students = db.students
for student_data in students.find():
smaller_homework_score_seq = None
smaller_homework_score_val = None
for score_seq, score_data in enumerate(student_data['scores']):
if score_data['type'] == 'homework':
if smaller_homework_score_seq is None or smaller_homework_score_val > score_data['score']:
smaller_homework_score_seq = score_seq
smaller_homework_score_val = score_data['score']
students.update('_id': student_data['_id'], '$pop': 'scores': smaller_homework_score_seq)
【讨论】:
OP 用于 mongo js shell,但这是一个超级干净的 Python 示例!【参考方案10】:这是我使用 pyMongo(MongoDB 的 Python 驱动程序)的方法:
import pymongo
conn = pymongo.MongoClient('mongodb://localhost')
def remove_lowest_hw():
db = conn.school
students = db.students
# first sort scores in ascending order
students.update_many(, '$push':'scores':'$each':[], '$sort':'score': 1)
# then collect the lowest homework score for each student via projection
cursor = students.find(, 'scores':'$elemMatch':'type':'homework')
# iterate over each student, trimming each of the lowest homework score
for stu in cursor:
students.update('_id':stu['_id'], '$pull':'scores':'score':stu['scores'][0]['score'])
remove_lowest_hw()
conn.close()
【讨论】:
【参考方案11】:这项工作对我来说,这是一个有点粗略的代码,但每个学生的最低任务的结果都是正确的。
var scores_homework = []
db.students.find("scores.type": "homework").forEach(
function(s)
s.scores.forEach(
function(ss)
if(ss.type=="homework")
ss.student_id = s._id
scores_homework.push(ss)
)
)
for(i = 0; i < scores_homework.length; i++)
var b = i+1;
var ss1 = scores_homework[i];
var ss2 = scores_homework[b];
var lowest_score = ;
if(ss1.score > ss2.score)
lowest_score.type = ss2.type;
lowest_score.score = ss2.score;
db.students.update(_id: ss2.student_id,$pull: scores: score: lowest_score.score);
else if(ss1.score < ss2.score)
lowest_score.type = ss1.type;
lowest_score.score = ss1.score;
db.students.update(_id: ss1.student_id,$pull: scores: score: lowest_score.score);
else
lowest_score.type = ss1.type;
lowest_score.score = ss1.score;
db.students.update(_id: ss1.student_id,$pull: scores: score: lowest_score.score);
i++
【讨论】:
【参考方案12】:这就是我在 Java 中实现的方式(保持简单以便更容易理解) -
方法:
-
从student集合中获取scores数组
从type == homework的scores数组中获取所有score值
对分数值进行排序,使最低的成为第一个元素 [score.get(0)]
然后,遍历主 scores 并创建 score 数组的新副本,同时跳过 type == homework && score == scores.get(0) 的元素
最后,将新的分数数组更新为学生文档。
下面是工作 Java 代码:
public void removeLowestScore()
//Create mongo client and database connection and get collection
MongoClient client = new MongoClient("localhost");
MongoDatabase database = client.getDatabase("school");
MongoCollection<Document> collection = database.getCollection("students");
FindIterable<Document> docs = collection.find();
for (Document document : docs)
//Get scores array
ArrayList<Document> scores = document.get("scores", ArrayList.class);
//Create a list of scores where type = homework
List<Double> homeworkScores = new ArrayList<Double>();
for (Document score : scores)
if(score.getString("type").equalsIgnoreCase("homework"))
homeworkScores.add(score.getDouble("score"));
//sort homework scores
Collections.sort(homeworkScores);
//Create a new list to update into student collection
List<Document> newScoresArray = new ArrayList<Document>();
Document scoreDoc = null;
//Below loop populates new score array with eliminating lowest score of "type" = "homework"
for (Document score : scores)
if(score.getString("type").equalsIgnoreCase("homework") && homeworkScores.get(0) == score.getDouble("score"))
continue;
else
scoreDoc = new Document("type",score.getString("type"));
scoreDoc.append("score",score.getDouble("score"));
newScoresArray.add(scoreDoc);
//Update the scores array for every student using student _id
collection.updateOne(Filters.eq("_id", document.getInteger("_id")), new Document("$set",new Document("scores",newScoresArray)));
【讨论】:
【参考方案13】:当然已经很晚了,但我只想在 Mongo Shell 上贡献我自己的解决方案:
var students = db.getCollection('students').find();
for(i = 0 ; i < students.length(); i++)
var scores = students[i].scores;
var tmp = [];
var min = -1 ;
var valueTmp = ;
for(j = 0 ; j < scores.length; j++)
if(scores[j].type != 'homework')
tmp.push(scores[j]);
else
if (min == -1)
min = scores[j].score;
valueTmp = scores[j];
else
if (min > scores[j].score)
min = scores[j].score;
tmp.push(valueTmp);
valueTmp = scores[j];
else
tmp.push(scores[j]);
db.students.updateOne(_id:students[i]._id,
$set:scores:tmp);
【讨论】:
【参考方案14】:@Stennie 的回答很好,也许$group
运算符对保留原始文档很有用,而不会在许多文档中爆炸(按分数)。
我只是在为您的应用程序使用 javascript 时添加另一种解决方案。
如果只查询一个文档,有时用 JS 对嵌入数组进行排序比进行聚合更容易。
当你的文档有很多字段时,比使用$push
操作符更好,否则你必须将所有字段一一推送,或者使用$$ROOT
操作符(我错了吗?)
我的示例代码使用 Mongoose.js : 假设您已经初始化了 Student 模型。
// Sorting
function compare(a, b)
return a.score - b.score;
Students.findById('1', function(err, foundDocument)
foundDocument.scores = foundDocument.scores.sort(compare);
// do what you want here...
// foundModel keeps all its fields
);
【讨论】:
【参考方案15】:按分数排序可以很简单:
db.students.find(_id:137).sort(score:-1).pretty()
但你需要找到 type:homework ...
【讨论】:
$sort 与 $push 和 $each 一起使用将在 mongo shell 中执行:db.students.updateMany(, '$push':'scores':'$each':[], '$sort':'score': 1)
per mongodb documentation【参考方案16】:
应该是这样的:
db.students.find().sort(scores: ("score":-1));
【讨论】:
如果这是用 mongoshell 编写的,这是无效的,它也不会完成他要求的工作。正确它应该是 db.students.find().sort("scores.score":-1) 但这不会对任何东西进行排序(至少我什么都看不到),尤其是学生中的分数数组。据我所知,您需要手动遍历这些数组条目并进行排序,mongo 不会这样做。 也 philnate 是正确的......这不会在 mongo shell 中带来预期的结果......感谢您的尝试。 我是 python 和 mongodb 菜鸟...此链接导致 php 中的答案...我正在寻找 python 或 mongo shell 中的解决方案。以上是关于如何在 MongoDB 中对集合记录中的数组进行排序?的主要内容,如果未能解决你的问题,请参考以下文章
批量查找 mongoDB 记录(使用 mongoid ruby 适配器)