Forespørgslen er ikke så kompliceret, som den kan se ud til at begynde med - forespørgslen for at finde alle dokumenter, der "overlapper" det område, du får, er:
db.test.find( { "startTime" : { "$lt" : new_end_time },
"endTime" : { "$gt": new_start_time }
}
)
Dette vil matche ethvert dokument med en startdato tidligere end vores slutdato og en slutdato, der er større end vores starttidspunkt. Hvis du visualiserer områderne som værende punkter på en linje:
-----|*********|----------|****|-----------|******||********|--- s1 e1 s2 e2 s3 e3s4 e4
sX-eX-parrene repræsenterer eksisterende områder. Hvis du tager en ny s5-e5, kan du se, at hvis vi eliminerer par, der starter efter vores slutdato (de kan ikke overlappe os) og så eliminerer vi alle par der slutter før vores startdato, hvis vi ikke har noget tilbage, så er vi gode til at indsætte.
Denne betingelse ville være, at alle dokumenter med slutdatoen $lte
samles vores start og dem med startdato $gte
vores omfatter alle dokumenter, der allerede er i indsamling. Vores forespørgsel vender dette rundt for at sikre, at ingen dokumenter opfylder det modsatte af denne betingelse.
På ydeevnefronten er det uheldigt, at du kun gemmer dine datoer som strenge. Hvis du gemte dem som tidsstempler (eller et hvilket som helst tal, virkelig), kunne du få denne forespørgsel til at bruge indekser bedre. Som det er, for ydeevne vil du gerne have et indeks på { "startTime":1, "endTime":1 }
.
Det er nemt at finde ud af, om det område, du vil indsætte, overlapper eksisterende områder, men til dit andet spørgsmål:
Der er ingen måde at gøre det på med en inserts, da de ikke tager en forespørgsel (dvs. de er ikke betingede).
Du kan dog bruge en opdatering med upsert-tilstand. Den kan indsætte, hvis betingelsen ikke matcher noget, men hvis den matcher, vil den forsøge at opdatere det matchede dokument!
Så det trick, du ville bruge, er at gøre opdateringen til et noop, og sæt de felter, du har brug for, kun på upsert. Siden 2.4 er der en $setOnInsert
operatør for at opdatere. Den fulde ting ville se nogenlunde sådan her ud:
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 })
Jeg har lige lavet den samme "opdatering" to gange - første gang var der ingen overlapningsdokument(er), så opdateringen udførte en "upsert", som du kan se i WriteResult
den vendte tilbage.
Da jeg kørte det en anden gang, ville det overlappe (selvfølgelig), så det forsøgte at opdatere det matchede dokument, men bemærkede, at der ikke var noget arbejde at gøre. Du kan se den returnerede nMatched er 1, men intet blev indsat eller ændret.