A consulta não é tão complicada quanto pode parecer à primeira vista - a consulta para encontrar todos os documentos que "se sobrepõem" ao intervalo que você recebe é:
db.test.find( { "startTime" : { "$lt" : new_end_time },
"endTime" : { "$gt": new_start_time }
}
)
Isso corresponderá a qualquer documento com data de início anterior à nossa data de término e data de término maior que nossa hora de início. Se você visualizar os intervalos como pontos em uma linha:
-----|*********|----------|****|-----------|******||********|--- s1 e1 s2 e2 s3 e3s4 e4
os pares sX-eX representam intervalos existentes. Se você pegar um novo s5-e5, verá que, se eliminarmos os pares que começam depois nossa data final (eles não podem nos sobrepor) e então eliminamos todos os pares que terminam antes da nossa data inicial, se não tivermos mais nada, então estamos prontos para inserir.
Essa condição seria uma união de todos os documentos com data final
$lte
nosso início e aqueles com data de início $gte
os nossos incluem todos os documentos já em coleção. Nossa consulta inverte isso para garantir que nenhum documento satisfaça o oposto dessa condição. Na frente do desempenho, é lamentável que você esteja armazenando suas datas apenas como strings. Se você os armazenou como timestamps (ou qualquer número, na verdade), você pode fazer com que essa consulta utilize índices melhor. Como está, para desempenho, você deseja ter um índice em
{ "startTime":1, "endTime":1 }
. É simples descobrir se o intervalo que você deseja inserir se sobrepõe a qualquer intervalo existente, mas para sua segunda pergunta:
Não há uma maneira adequada de fazer isso com inserções, pois elas não recebem uma consulta (ou seja, não são condicionais).
No entanto, você pode usar uma atualização com condição de upsert. Ele pode inserir se a condição não corresponder a nada, mas se corresponder, tentará atualizar o documento correspondente!
Portanto, o truque que você usaria é tornar a atualização um noop e definir os campos necessários apenas no upsert. Desde 2.4 existe um
$setOnInsert
operador para atualizar. A coisa completa ficaria mais ou menos assim: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 })
Acabei de fazer a mesma "atualização" duas vezes - na primeira vez, não havia documento(s) de sobreposição, então a atualização executou um "upsert" que você pode ver no
WriteResult
ele voltou. Quando eu o executei uma segunda vez, ele se sobrepôs (por si só, é claro), então tentou atualizar o documento correspondente, mas notou que não havia trabalho a fazer. Você pode ver que o nMatched retornado é 1, mas nada foi inserido ou modificado.