Получить "данные из коллекции b не в коллекции a" в запросе оболочки MongoDB
У меня есть две коллекции MongoDB, которые имеют общий _id. Используя оболочку mongo, я хочу найти все документы в одной коллекции, которые не имеют соответствующего _id в другой коллекции.
Пример:
> db.Test.insert({ "_id" : ObjectId("4f08a75f306b428fb9d8bb2e"), "foo" : 1 })
> db.Test.insert({ "_id" : ObjectId("4f08a766306b428fb9d8bb2f"), "foo" : 2 })
> db.Test.insert({ "_id" : ObjectId("4f08a767306b428fb9d8bb30"), "foo" : 3 })
> db.Test.insert({ "_id" : ObjectId("4f08a769306b428fb9d8bb31"), "foo" : 4 })
> db.Test.find()
{ "_id" : ObjectId("4f08a75f306b428fb9d8bb2e"), "foo" : 1 }
{ "_id" : ObjectId("4f08a766306b428fb9d8bb2f"), "foo" : 2 }
{ "_id" : ObjectId("4f08a767306b428fb9d8bb30"), "foo" : 3 }
{ "_id" : ObjectId("4f08a769306b428fb9d8bb31"), "foo" : 4 }
> db.Test2.insert({ "_id" : ObjectId("4f08a75f306b428fb9d8bb2e"), "bar" : 1 });
> db.Test2.insert({ "_id" : ObjectId("4f08a766306b428fb9d8bb2f"), "bar" : 2 });
> db.Test2.find()
{ "_id" : ObjectId("4f08a75f306b428fb9d8bb2e"), "bar" : 1 }
{ "_id" : ObjectId("4f08a766306b428fb9d8bb2f"), "bar" : 2 }
Теперь мне нужен запрос или запросы, которые возвращают два документа в тесте, где _id не соответствуют любому документу в Test2:
{ "_id" : ObjectId("4f08a767306b428fb9d8bb30"), "foo" : 3 }
{ "_id" : ObjectId("4f08a769306b428fb9d8bb31"), "foo" : 4 }
Я пробовал различные комбинации $not, $ne, $или, $in, но просто не могу получить правильную комбинацию и синтаксис. Кроме того, я не против, если db.Test2.find({}, {"_id": 1})
выполняется сначала, сохраняется в некоторой переменной, которая затем используется во втором запросе (хотя я не могу заставить это работать).
Обновить: ответ Zachary, указывающий на $nin, ответил на ключевую часть вопроса. Например, это работает:
> db.Test.find({"_id": {"$nin": [ObjectId("4f08a75f306b428fb9d8bb2e"), ObjectId("4f08a766306b428fb9d8bb2f")]}})
{ "_id" : ObjectId("4f08a767306b428fb9d8bb30"), "foo" : 3 }
{ "_id" : ObjectId("4f08a769306b428fb9d8bb31"), "foo" : 4 }
Но (и признавая, что это не масштабируемо, но все равно пытается это сделать, потому что это не проблема в этой ситуации), я все равно не могу объединить два запроса вместе в оболочке. Это самое близкое, что я могу получить, что явно меньше идеала:
vals = db.Test2.find({}, {"_id": 1}).toArray()
db.Test.find({"_id": {"$nin": [ObjectId(vals[0]._id), ObjectId(vals[1]._id)]}})
Есть ли способ вернуть только значения в команде find, так что vals можно использовать напрямую, поскольку массив вводится в $nin?
Ответы
Ответ 1
Вам нужно будет сохранить _ids из коллекции A, чтобы не вытаскивать их из коллекции B, но вы можете сделать это, используя $nin
. См. Расширенные запросы для всех операторов MongoDB.
Ваш конечный запрос, используя приведенный вами пример, будет выглядеть примерно так:
db.Test.find({"_id": {"$nin": [ObjectId("4f08a75f306b428fb9d8bb2e"), ObjectId("4f08a766306b428fb9d8bb2f")]}})
Обратите внимание, что этот подход не будет масштабироваться. Если вам нужно решение, которое масштабируется, вы должны установить флаг в коллекциях A и B, указывающий, находится ли _id в другой коллекции, а затем запросить об этом.
Обновлено для второй части:
Вторая часть невозможна. MongoDB не поддерживает объединения или любые кросс-запросы между сборками в одном запросе. Запрос из одной коллекции, сохранение результатов, а затем запрос со второго - это ваш единственный выбор, если вы не вставляете данные в сами строки, как я упоминал ранее.
Ответ 2
Отвечая на ваш вопрос. Я бы использовал map().
Учитывая это:
> b1 = {i: 1}
> db.b.save(b1)
> db.b.save({i: 2})
> db.a.save({_id: b1._id})
Все, что вам нужно, это:
> vals = db.a.find({}, {id: 1}).map(function(a){return a._id;})
> db.b.find({_id: {$nin: vals}})
который возвращает
{ "_id" : ObjectId("4f08c60d6b5e49fa3f6b46c1"), "i" : 2 }
Ответ 3
В mongo 3.2 следующий код работает
db.collectionb.aggregate([
{
$lookup:
{
from: "collectiona",
localField: "collectionb_fk",
foreignField: "collectiona_fk",
as: "matched_docs"
}
},
{
$match: { "matched_docs": { $eq: [] } }
}
]);
на основе этого https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#use-lookup-with-an-array example
Ответ 4
Я создал script, отмечая все документы во второй коллекции, которые появляются в первой коллекции. Затем обработаны документы второй коллекции.
var first = db.firstCollection.aggregate([ {'$unwind':'$secondCollectionField'} ])
while (first.hasNext()){ var doc = first.next(); db.secondCollection.update( {_id:doc.secondCollectionField} ,{$set:{firstCollectionField:doc._id}} ); }
... обработать вторую коллекцию, которая не имеет метки
db.secondCollection.find({"firstCollectionField":{$exists:false}})
Ответ 5
db.bar.find({_ ID: {$ нин: db.foo.find({}, {_ ID:. 1}) ToArray()}})