MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Interroger et insérer avec une seule commande

La requête n'est pas aussi compliquée qu'elle n'y paraît au premier abord - la requête pour trouver tous les documents qui "chevauchent" la plage qui vous est donnée est :

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

Cela correspondra à tout document avec une date de début antérieure à notre date de fin et une date de fin supérieure à notre heure de début. Si vous visualisez les plages comme étant des points sur une ligne :

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

les paires sX-eX représentent des gammes existantes. Si vous prenez un nouveau s5-e5, vous pouvez voir que si nous éliminons les paires qui commencent après notre date de fin (ils ne peuvent pas nous chevaucher) puis nous éliminons toutes les paires qui se terminent avant notre date de début, s'il ne nous reste plus rien, alors nous sommes bons à insérer.

Cette condition serait une union de tous les documents avec la date de fin $lte notre début et ceux avec une date de début $gte les nôtres incluent tous les documents déjà en collection. Notre requête retourne cela pour s'assurer qu'aucun document ne satisfait l'opposé de cette condition.

Sur le plan des performances, il est regrettable que vous stockiez vos dates sous forme de chaînes uniquement. Si vous les stockiez sous forme d'horodatages (ou de n'importe quel nombre, vraiment), vous pourriez faire en sorte que cette requête utilise mieux les index. En l'état, pour les performances, vous voudriez avoir un index sur { "startTime":1, "endTime":1 } .

Il est simple de déterminer si la plage que vous souhaitez insérer chevauche des plages existantes, mais pour votre deuxième question :

Il n'y a aucun moyen de le faire correctement avec des inserts car ils ne prennent pas de requête (c'est-à-dire qu'ils ne sont pas conditionnels).

Cependant, vous pouvez utiliser une mise à jour avec une condition upsert. Il peut s'insérer si la condition ne correspond à rien, mais si elle correspond, il essaiera de mettre à jour le document correspondant !

Donc, l'astuce que vous utiliseriez est de faire de la mise à jour un noop et de définir les champs dont vous avez besoin uniquement sur upsert. Depuis la 2.4 il y a un $setOnInsert opérateur à mettre à jour. Le tout ressemblerait à ceci :

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 })

Je viens de faire la même "mise à jour" deux fois - la première fois, il n'y avait pas de chevauchement de documents, donc la mise à jour a effectué un "upsert" que vous pouvez voir dans le WriteResult il est revenu.

Lorsque je l'ai exécuté une deuxième fois, il se chevauchait (lui-même, bien sûr), il a donc essayé de mettre à jour le document correspondant, mais a remarqué qu'il n'y avait pas de travail à faire. Vous pouvez voir que le nMatched renvoyé est 1 mais rien n'a été inséré ou modifié.