Query and Insert with a single command

霸气de小男生 提交于 2019-12-30 14:44:11

问题


I have the following documents:

{
  "_id": "538584aad48c6cdc3f07a2b3",
  "startTime": "2014-06-12T21:30:00.000Z",
  "endTime": "2014-06-12T22:00:00.000Z",
},
{
  "_id": "538584b1d48c6cdc3f07a2b4",
  "startTime": "2014-06-12T22:30:00.000Z",
  "endTime": "2014-06-12T23:00:00.000Z",
}

All of them have startTime and endTime value. I need to maintain consistency that no two date spans in the collection overlap.

Let's say if I add the following document with the following dates:

db.collection.insert({
                      "startTime": "2014-06-12T19:30:00.000Z",
                      "endTime": "2014-06-12T21:00:00.000Z"
                     });

This date span insert should fail because it overlaps with an existing interval.

My questions are:

  • How to check for date span overlap?
  • How to check and insert with a single query?

EDIT: to prevent duplicate I ask there and start a bounty. I need to make update operation by using single query as described here: How to query and update document by using single query?


回答1:


The query is not as complicated as it may look at first - the query to find all documents which "overlap" the range you are given is:

db.test.find( { "startTime" : { "$lt" : new_end_time }, 
                "endTime"   : { "$gt": new_start_time } 
            } 
)

This will match any document with starting date earlier than our end date and end date greater than our start time. If you visualize the ranges as being points on a line:

-----|*********|----------|****|-----------|******||********|---
    s1         e1         s2   e2         s3     e3s4       e4

the sX-eX pairs represent existing ranges. If you take a new s5-e5 you can see that if we eliminate pairs that start after our end date (they can't overlap us) and then we eliminate all pairs that end before our start date, if we have nothing left, then we are good to insert.

That condition would be does a union of all documents with end date $lte our start and those with start date $gte ours include all documents already in collection. Our query flips this around to make sure that no documents satisfy the opposite of this condition.

On the performance front, it's unfortunate that you are storing your dates as strings only. If you stored them as timestamps (or any number, really) you could make this query utilize indexes better. As it is, for performance you would want to have an index on { "startTime":1, "endTime":1 }.

It's simple to find whether the range you want to insert overlaps any existing ranges, but to your second question:

How to check and insert with a single query?

There is no way proper way to do it with an inserts since they do not take a query (i.e. they are not conditional).

However, you can use an updates with upsert condition. It can insert if the condition doesn't match anything, but if it does match, it will try to update the matched document!

So the trick you would use is make the update a noop, and set the fields you need on upsert only. Since 2.4 there is a $setOnInsert operator to update. The full thing would look something like this:

db.test.update( 
   { startTime: { "$lt" : new_end_time }, "endTime" : { "$gt": new_start_time } }, 
   { $setOnInsert:{ startTime:new_start_time, endTime: new_end_time}},
   {upsert:1}
)
WriteResult({
"nMatched" : 0,
"nUpserted" : 1,
"nModified" : 0,
"_id" : ObjectId("538e0f6e7110dddea4383938")
})
db.test.update(
   { startTime:{ "$lt" : new_end_time }, "endTime" : { "$gt": new_start_time } },
   { $setOnInsert:{ startTime:new_start_time, endTime: new_end_time}},
   {upsert:1}
)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

I just did the same "update" twice - the first time, there was no overlap document(s) so the update performed an "upsert" which you can see in the WriteResult it returned.

When I ran it a second time, it would overlap (itself, of course) so it tried to update the matched document, but noticed there was no work to do. You can see the returned nMatched is 1 but nothing was inserted or modified.




回答2:


This query should return all documents that somehow overlap with the new start/end-Time values.

db.test.find({"$or":[
    {"$and":[{"startTime":{"$lte":"new_start_time"}, "endTime":{"$gte":"new_start_time"}},  //new time has an old startTime in the middle
             {"startTime":{"$lte":"new_end_time"},   "endTime":{"$lte":"new_end_time"}}]},
    {"$and":[{"startTime":{"$gte":"new_start_time"}, "endTime":{"$gte":"new_start_time"}},  //new time sorounds and old time
             {"startTime":{"$lte":"new_end_time"},   "endTime":{"$lte":"new_end_time"}}]},
    {"$and":[{"startTime":{"$gte":"new_start_time"}, "endTime":{"$gte":"new_start_time"}},  //an old time has the new endTime in the middle
             {"startTime":{"$lte":"new_end_time"},   "endTime":{"$gte":"new_end_time"}}]},
    {"$and":[{"startTime":{"$lte":"new_start_time"}, "endTime":{"$gte":"new_start_time"}},  //new time is within an old time
             {"startTime":{"$lte":"new_end_time"},   "endTime":{"$gte":"new_end_time"}}]}
      ]})



回答3:


You want to run both queries at the same time. It means you want Synchronous in your code visit this question it may help for your answer

Synchronous database queries with Node.js



来源:https://stackoverflow.com/questions/23908593/query-and-insert-with-a-single-command

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!