我们每隔一段时间存储温度测量数据,我们只想保留90个以前的数据。我们的数据结构如下:
{ "_id" : ObjectId("xxx"), "device" : "deviceId1", "count": 2, "values" :
[
{ "ts" : NumberLong("1471077454902"), "measureData" : 37.3 },
{ "ts" : NumberLong("1471077454911"), "measureData" : 37.4 }
]
}
count是值的大小,例如,当数组有2个元素时,则大小为2。 我们按如下方式设计Java API:
When the new measurement data comes:
Get corresponding device id count:
if count < 90:
Push the new mesaure data into values and increase count by 1
if count >90:
Pull the first element of the array and push the latest data into array.
Store the first element pulled into history collection.
是否有一个查询或一个聚合可以执行这些步骤?或者我们应该通过传统的方法,比如查询,评估,然后推或拉/推。
// // ***************第2部分传统方法*************** //
public class TSDesignMain {
public static void main(String[] args) {
MongoClient mClient = new MongoClient();
MongoDatabase db = mClient.getDatabase(MongoTSConstants.dbname);
long tWarmingStart = System.nanoTime();
InsertingWarmingDocument(db);
long tWarmingEnd = System.nanoTime();
double tDurationWarming = (double) (tWarmingEnd-tWarmingStart) / 1000/1000;
System.out.println("warming db, insert 10000 document per event duration is "+tDurationWarming+"ms");
long tInsert1Dev1DocStart = System.nanoTime();
for(int j=1;j<10000;j++){
storeMeasureDataIntoDB(db);
}
long tInsert1Dev1DocEnd = System.nanoTime();
double tInsert1Dev1DocDuration = (double) (tInsert1Dev1DocEnd-tInsert1Dev1DocStart) / 1000/1000;
System.out.println("insert 10000 document in 90*24*30 elements array duration is "+tInsert1Dev1DocDuration+"ms");
long tQuery1Dev1DocStart = System.nanoTime();
for(int j=1;j<10000;j++){
handleQueryDocument(db);
}
long tQuery1Dev1DocEnd = System.nanoTime();
double tQuery1Dev1DocDuration = (double) (tQuery1Dev1DocEnd-tQuery1Dev1DocStart) / 1000/1000;
System.out.println("query 10000 times in 90*24*30 elements array duration is "+tQuery1Dev1DocDuration+"ms");
mClient.close();
}
private static void InsertingWarmingDocument(MongoDatabase db) {
long ts = Calendar.getInstance().getTimeInMillis();
for(long i=1;i<100;i++){
db.getCollection(MongoTSConstants.tsDataPdCollection).insertOne(
new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+i+"test")
.append(MongoTSConstants.tsFn,ts+i)
);
}
}
private static void handleQueryDocument(MongoDatabase db) {
Document where = new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+1);
FindIterable<Document> it = db.getCollection(MongoTSConstants.tsDataInOneCollection).find(where);
MongoCursor<Document> cur = it.iterator();
int i=0;
if(!cur.hasNext()){
Document doc = cur.next();
ArrayList<Document> obj = (ArrayList<Document>) doc.get("values");
}
}
private static void storeMeasureDataIntoDB(MongoDatabase db) {
Document where = new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+1);
FindIterable<Document> it = db.getCollection(MongoTSConstants.tsDataInOneCollection).find(where);
MongoCursor<Document> cur = it.iterator();
int i=0;
/**
* There is no device document in DB, insert new one. and use update to store 1st measure data
*
*/
if(!cur.hasNext()){
long tsInsert = System.nanoTime();
/**
* insert device id
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).insertOne(
new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+1)
.append(MongoTSConstants.countFn,0));
/**
* using update to insert values array (store the first measure data)
*
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(
new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+1),
new Document("$push", new Document("values",new Document(MongoTSConstants.tsFn,tsInsert).
append(MongoTSConstants.measureDataFn,37.1)))
.append("$inc", new Document(MongoTSConstants.countFn,1)));
}else{
while(cur.hasNext()){
/**
* if i > 1, it means there are two doc with same device id, error!!
*/
if(i>=1){
//log error find two docs with same devicedID.
break;
}
Document doc = cur.next();
Integer count = doc.getInteger("count");
/**
* measure data has over 3 month.
*/
if( count >= MongoTSConstants.queryDataLength ){
/**
* get the first one in the array
*/
ArrayList<Document> obj = (ArrayList<Document>) doc.get("values");
Document doc1Elem = (Document) obj.get(0);
/**
* pull the first one in the array
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(where,
new Document("$pop", -1));
long ts = Calendar.getInstance().getTimeInMillis();
/**
* push the new one
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(where,
new Document("$push", new Document("values",new Document(MongoTSConstants.tsFn,ts).
append(MongoTSConstants.measureDataFn,37.8))));
//Store doc1Elem in another history collection;
db.getCollection(MongoTSConstants.tsHistoryCollection).insertOne(doc1Elem);
}else{
/**
* Measure data has not reach 3 month data
*/
long ts = Calendar.getInstance().getTimeInMillis();
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(where,
new Document("$push", new Document("values",new Document(MongoTSConstants.tsFn,ts).
append(MongoTSConstants.measureDataFn,37.9))).append("$inc", new Document(MongoTSConstants.countFn,1)));
}
i++;
}
}
}
}
答案 0 :(得分:2)
在我看来,目前很难做到(目前mongodb的最新版本是v3.2。)我不知道如何实现目标。
此处有5个操作,您希望将它们合并为1个更新操作:
我们将它们分为两部分:查询部分&amp; 更新部分
由于我不知道如何将评估纳入更新声明的更新操作部分:
db.test.update(
{/* Query criteria */},
{/* Update action */}
)
我采用了另一种方法 - 将评估纳入查询条件部分,如下所示:
// We call this statement as UPDATE_STAT_1
db.test.update(
{device: 'deviceId1', count: {$lt: 90}},
{/* Update action: push new data to values and increase count */}
);
如果计数&lt; 90 ,您的样本文档将匹配&amp;执行更新操作(此时不要关心更新操作是成功还是失败)。 然后mongoDB将把执行结果返回给你:
// Matched and updated
WriteResult({
"nMatched" : 1,
"nUpserted" : 0,
"nModified" : 1
});
结果告诉您一个文档已匹配并更新(nMatched = 1&amp; nModified = 1)
另一方面,如果 count&gt; = 90 ,则不会匹配和更新任何内容,执行结果将为:
// Not Matched
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0
})
所以你会知道计数达到90,你应该执行另一个更新声明:
// We call this statement as UPDATE_STAT_2
db.test.update(
{device: 'deviceId1', count: {$gte: 90}},
{/* Update action: push new data to values and pop the oldest*/}
);
总之 ,您可以先执行 UPDATE_STAT_1 ,然后检查执行结果以确定是否需要像这样执行 UPDATE_STAT_2 (伪代码):
exe_result = run(UPDATE_STAT_1); // Run UPDATE_STAT_1
if(exe_result.nMatched == 0 && exe_result.nModified == 0) {
run(UPDATE_STAT_2); // Check the count and Run UPDATE_STAT_2
}
这是我合并查询(第1个)和评估(第2个)的方法。我不知道如何将上述所有代码合并为一个语句。
我曾在当地尝试过,你可以把推送&amp; 将增加到一个语句中:
db.test.update(
{device: 'deviceId1', count: {$lt: 90}},
{
$push: {values: { "ts" : "1471077454988", "measureData" : 39 }},
$inc : {count: 1}
}
);
做Push(3rd)&amp ;;同时增加(第5名)。但是,如果你想推动&amp;弹出数组:
db.test.update(
{device: 'deviceId1', count: {$gte: 90}},
{
$pop: {values: -1},
$push: {values: { "ts" : "1471077454911", "measureData" : 40 }}
}
);
你会收到错误:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 16837,
"errmsg" : "Cannot update 'values' and 'values' at the same time"
}
})
这意味着你无法推进和推动同时弹出值。为此,我找到了这个解释:
问题是MongoDB不允许对其进行多项操作 同一更新调用中的相同属性。这意味着两者 操作必须在两个单独的原子操作中进行。
<强> 因此。总之 ,它无法将Push(3rd)/ Pop(4th)/ Increase(5th)合并为一个语句。
根据上面的示例,无法合并Query(1st)和Evaluation(2nd),Push(3rd)/ Pop(4th)/ Increase(5th)也不能合并。所以你可以用传统方式做到这一点。感谢。