Mongo實現分段隨機出題

Mongo環境 3.4

需求

我們有一個題庫,需要隨機生成題目組合,爲保證每道題都可能被選中,避免某次選題都是集中在某一段中,所以希望實現分段隨機,每段隨機取一部分數據

數據樣例

db.sample.insert({"name": 1, "age": 20});
db.sample.insert({"name": 2, "age": 20});
db.sample.insert({"name": 3, "age": 20});
db.sample.insert({"name": 4, "age": 20});
db.sample.insert({"name": 5, "age": 20});
db.sample.insert({"name": 6, "age": 20});
db.sample.insert({"name": 7, "age": 20});
db.sample.insert({"name": 8, "age": 20});
db.sample.insert({"name": 9, "age": 20});
db.sample.insert({"name": 10, "age": 20});
db.sample.insert({"name": 11, "age": 20});
db.sample.insert({"name": 12, "age": 20});

實現原理

核心:使用Mongo 語法 $facet , Mongo官方文檔-$facet

官方文檔說明:對同一輸入文檔進行多個聚合管道,每個子管道在輸出文檔中都有自己的字段,其結果存儲爲文檔數組

so: 每個分段就相當於一個聚合管道,分段大小爲 從skip開始截至到limit位置, 然後採用Mongo支持的隨機函數對每段進行隨機取值

最後,查詢結果爲了方便查看,使用$concatArrays將每個子管道的結果集合合併在一起,最後使用$unwind將集合拆分成單行,這樣每行僅有一個題目

算個缺陷吧

如果分段數變化則需要添加$face中的對象,最後在$concatArrays中添加改對象,纔可以實現分段數的增加/減少

實現的Mongo語句

db.sample.aggregate([
{
	$facet: {
			"1":[{
				$limit:4
			},{
				$skip:0
			},{
				$sample:{size:2}
		}],
		"2":[{
				$limit:8
			},{
				$skip:4
			},{
				$sample:{size:2}
		}],
		"3":[{
				$limit:20
			},{
				$skip:8
			},{
				$sample:{size:2}
		}]
	}
},
// 將每段獲得的數據合併到一個集合中
{
	$project:{
		"questionList":{ 
			$concatArrays: [ "$1", "$2","$3" ] 
		}
	}
},{
	$unwind:"$questionList"
},{
	$project:{
		'name':'$questionList.name',
		'age':'$questionList.age',
	}
}
]);

在這裏插入圖片描述

JAVA 代碼生成Mongo語句示例

創建對象存儲分塊信息

import lombok.Data;

@Data
public class RandomQuestionBlockEntity {
	
	private Integer limit;// 返回記錄數
	private Integer offset; // 跳過記錄數
	private Integer index; // 指針位置
	private Integer size;// 塊大小
	private Integer returnSize;// 每塊返回數據量
	private String indexName;// 塊名稱
	
}

生成Mongo查詢語句方法
需要考慮幾種特殊情況:如題目數<分塊數, 題目數不能平分到每個塊中等

/**
 * 分段隨機出題 mongo語句生成
 * @param defaultDb			默認條件
 * @param size				查詢題目數
 * @param questionCount		題目總數
 * @return
 */
public static BasicDBList randomQuestionListSQL(BasicDBObject defaultMatchDb, Integer size, Integer questionCount) {
	List<RandomQuestionBlockEntity> splitList = new ArrayList<RandomQuestionBlockEntity>();// 分段信息存儲
	RandomQuestionBlockEntity blockEntity = null;
	int splitNum = 10;// 分塊數
	if(splitNum > questionCount) {
		// 分塊數 大於 題目總數,將分塊數設置爲1
		splitNum = 1;
	}
	if(splitNum > size) {
		// 分塊數 大於 查詢記錄數,則將分塊數設置爲記錄數
		splitNum = size;
	}
	int blockSize = questionCount/splitNum;// 塊大小
	int blockSurplus = questionCount%splitNum;// 不足平分一個塊的題目數
	int blockReturnSize = size/splitNum;// 每塊返回記錄數
	int blockReturnSurplus = size%splitNum;// 不足平分到每個塊的題目數
	
	int lastSize = 0;// 之前塊大小之和,用於計算當前塊跳過的記錄數
	for (int i = 1; i <= splitNum; i++) {
		blockEntity = new RandomQuestionBlockEntity();
		blockEntity.setSize(blockSize);// 塊大小
		blockEntity.setReturnSize(blockReturnSize);// 返回記錄數
		if(blockSurplus > 0) {
			blockEntity.setSize(blockSize+1);
			blockSurplus --;
		}
		if(blockReturnSurplus > 0) {
			blockEntity.setReturnSize(blockReturnSize+1);
			blockReturnSurplus --;
		}
		blockEntity.setIndex(i);
		blockEntity.setIndexName(String.valueOf(i));
		blockEntity.setOffset(lastSize);
		lastSize += blockEntity.getSize();
		blockEntity.setLimit(lastSize);
		splitList.add(blockEntity);
	}
	
	BasicDBList dbList = new BasicDBList();
	BasicDBObject facet = new BasicDBObject();
	
	BasicDBList concatArrays = new BasicDBList();
	BasicDBObject[] dbObjects = null;
	for (RandomQuestionBlockEntity entity : splitList) {
		dbObjects = new BasicDBObject[3];
		dbObjects[0] = new BasicDBObject("$limit", entity.getLimit());
		dbObjects[1] = new BasicDBObject("$skip",entity.getOffset());
		dbObjects[2] = new BasicDBObject("$sample", new BasicDBObject("size", entity.getReturnSize()));
		facet.append(entity.getIndexName(), dbObjects);
		concatArrays.add("$"+entity.getIndexName());
	}
	BasicDBObject match = new BasicDBObject("$match", defaultMatchDb);

	BasicDBObject project = new BasicDBObject("$project", 
			new BasicDBObject("questionList", 
					new BasicDBObject("$concatArrays", concatArrays)));
	
	dbList.add(match);
	dbList.add(new BasicDBObject("$facet", facet));
	dbList.add(project);
	// TODO 最後拆分數組請自行實現
	return dbList;
}
發佈了22 篇原創文章 · 獲贊 25 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章