Ответ 1
Вы не можете. См. http://www.mongodb.org/display/DOCS/Database+References
Вы должны сделать это в клиенте.
У меня есть две коллекции в моей базе данных Mongo, а Foo
содержат ссылки на один или несколько Bar
s:
Foo: {
prop1: true,
prop2: true,
bars: [
{
"$ref": "Bar",
"$id": ObjectId("blahblahblah")
}
]
}
Bar: {
testprop: true
}
Я хочу найти все Foo
, у которых есть хотя бы один Bar
, для которого testprop установлен в true. Я пробовал эту команду, но она не возвращает никаких результатов:
db.Foo.find({ "bars.testprop" : { "$in": [ true ] } })
Любые идеи?
Вы не можете. См. http://www.mongodb.org/display/DOCS/Database+References
Вы должны сделать это в клиенте.
Теперь вы можете сделать это в Mongo 3.2, используя $lookup
$lookup
принимает четыре аргумента
from
: указывает коллекцию в той же базе данных для выполнения соединения. Невозможно отложить сборку.
localField
: указывает поле из входных документов на этап поиска $. $lookup выполняет сопоставление равенства на localField с foreignField из документов коллекции.
foreignField
: указывает поле из документов в коллекции.
as
: указывает имя нового поля массива для добавления во входные документы. В поле нового массива содержатся соответствующие документы из коллекции.
db.Foo.aggregate(
{$unwind: "$bars"},
{$lookup: {
from:"bar",
localField: "bars",
foreignField: "_id",
as: "bar"
}},
{$match: {
"bar.testprop": true
}}
)
Хорошо.. вы можете запросить Bar
Модель для _id
всех документов с помощью testprop: true
, затем найти find $in
и заполнить bars
в Foo
Model с массивом тех _id
вы получили от первого запроса..: P
Возможно, это считается "В клиенте": P просто мысль.
У нас была аналогичная проблема, так как мы используем MongoDB (3.4.4, фактически 3.5.5 для тестирования) в сочетании с Morphia, где мы используем @Referenece
для нескольких объектов. Мы, однако, не очень довольны этим решением и рассматриваем возможность удаления этих объявлений и вместо этого выполняем поиск ссылок вручную.
т.е. у нас есть коллекция компаний и коллекция пользователей. Пользовательский объект в Morphia содержит объявление @Refrence
для юридического лица компании. Соответствующие коллекции компаний содержат такие записи, как:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5dee"),
"name" : "Test",
"gln" : "1234567890123",
"uuid" : "f1f86961-e8d5-40bb-9d3f-fdbcf549066e",
"creationDate" : ISODate("2017-09-01T09:14:41.551Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.551Z"),
"version" : NumberLong(1),
"disabled" : false
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5def"),
"name" : "Sample",
"gln" : "3210987654321",
"uuid" : "fee69ee4-b29c-483b-b40d-e702b50b0451",
"creationDate" : ISODate("2017-09-01T09:14:41.562Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.562Z"),
"version" : NumberLong(1),
"disabled" : false
}
в то время как пользовательские коллекции содержат следующие записи:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df0"),
"userId" : "admin",
"userKeyEncrypted" : {
"salt" : "78e0528db239fd86",
"encryptedAttribute" : "e4543ddac7cca9757721379e4e70567bb13956694f473b73f7723ac2e2fc5245"
},
"passwordHash" : "$2a$10$STRNORu9rcbq4qYUMld4G.HJk8QQQQBmAswSNC/4PBn2bih0BvjM6",
"roles" : [
"ADMIN"
],
"company" : {
"$ref" : "company",
"$id" : ObjectId("59a92501df01110fbb6a5dee")
},
"uuid" : "b8aafdcf-d5c4-4040-a96d-8ab1a8608af8",
"creationDate" : ISODate("2017-09-01T09:14:41.673Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.765Z"),
"version" : NumberLong(1),
"disabled" : false
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df1"),
"userId" : "sample",
"userKeyEncrypted" : {
"salt" : "e3ac48695dea5f51",
"encryptedAttribute" : "e804758b0fd13c219c3fc383eaa9267b70f7b8a1ed74f05575add713ce11804a"
},
"passwordHash" : "$2a$10$Gt2dq1vy4J9MeqDnXjokAOtvFcvbhe/g9wAENXFPaPxLAw1L4EULG",
"roles" : [
"USER"
],
"company" : {
"$ref" : "company",
"$id" : ObjectId("59a92501df01110fbb6a5def")
},
"uuid" : "55b62d4c-e5ee-408d-80c0-b79e02085b02",
"creationDate" : ISODate("2017-09-01T09:14:41.873Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.878Z"),
"version" : NumberLong(1),
"disabled" : false
}
/* 3 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df2"),
"userId" : "user",
"userKeyEncrypted" : {
"salt" : "ab9df671340a7d8b",
"encryptedAttribute" : "7d8ad4ca6ad88686d810c70498407032f1df830596f72d931880483874d9cce3"
},
"passwordHash" : "$2a$10$0FLFw3ixW79JIBrD82Ly6ebOwnEDliS.e7GmrNkFp2nkWDA9OE/RC",
"uuid" : "d02aef94-fc3c-4539-a22e-e43b8cd78aaf",
"creationDate" : ISODate("2017-09-01T09:14:41.991Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.995Z"),
"version" : NumberLong(1),
"disabled" : false
}
Для создания специального пользовательского представления компании мы также хотели бы разыменовать компанию у пользователя и включать только выбранные поля. Основываясь на комментарии в отчете , мы узнали, что MongoDB предоставляет операцию $objectToArray: "$$ROOT.element"
, которая в основном разделяет поля данных элементов на ключ и значение пар. Обратите внимание, что операция $objectToArray
была добавлена в версию MongoDB 3.4.4!
Агрегация элемента компании, содержащегося в пользовательской коллекции с помощью операции $objectToArray
, может выглядеть следующим образом:
dp.user.aggregate([{
$project: {
"userId": 1,
"userKeyEncrypted": 1,
"uuid":1,
"roles": 1,
"passwordHash": 1,
"disabled": 1,
company: { $objectToArray: "$$ROOT.company" }
}
}])
Результат вышеуказанной агрегации выглядит следующим образом:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df0"),
"userId" : "admin",
"userKeyEncrypted" : {
"salt" : "78e0528db239fd86",
"encryptedAttribute" : "e4543ddac7cca9757721379e4e70567bb13956694f473b73f7723ac2e2fc5245"
},
"passwordHash" : "$2a$10$STRNORu9rcbq4qYUMld4G.HJk8QQQQBmAswSNC/4PBn2bih0BvjM6",
"roles" : [
"ADMIN"
],
"uuid" : "b8aafdcf-d5c4-4040-a96d-8ab1a8608af8",
"disabled" : false,
"company" : [
{
"k" : "$ref",
"v" : "company"
},
{
"k" : "$id",
"v" : ObjectId("59a92501df01110fbb6a5dee")
}
]
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df1"),
"userId" : "sample",
"userKeyEncrypted" : {
"salt" : "e3ac48695dea5f51",
"encryptedAttribute" : "e804758b0fd13c219c3fc383eaa9267b70f7b8a1ed74f05575add713ce11804a"
},
"passwordHash" : "$2a$10$Gt2dq1vy4J9MeqDnXjokAOtvFcvbhe/g9wAENXFPaPxLAw1L4EULG",
"roles" : [
"USER"
],
"uuid" : "55b62d4c-e5ee-408d-80c0-b79e02085b02",
"disabled" : false,
"company" : [
{
"k" : "$ref",
"v" : "company"
},
{
"k" : "$id",
"v" : ObjectId("59a92501df01110fbb6a5def")
}
]
}
/* 3 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df2"),
"userId" : "user",
"userKeyEncrypted" : {
"salt" : "ab9df671340a7d8b",
"encryptedAttribute" : "7d8ad4ca6ad88686d810c70498407032f1df830596f72d931880483874d9cce3"
},
"passwordHash" : "$2a$10$0FLFw3ixW79JIBrD82Ly6ebOwnEDliS.e7GmrNkFp2nkWDA9OE/RC",
"uuid" : "d02aef94-fc3c-4539-a22e-e43b8cd78aaf",
"disabled" : false,
"company" : null
}
Теперь это просто вопрос фильтрации нежелательных материалов (то есть пользователей, у которых нет назначенной компании и выбора правильных записей массива), чтобы передать операцию $lookup
@sidgate уже объяснил и скопировал значение разыменованной компании в ответ пользователя.
т.е. агрегация, подобная приведенной ниже, оставит соединение данных компании с пользователем как значение as
, определенное в поиске:
db.user.aggregate([
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, company: { $objectToArray: "$$ROOT.company" }} },
{ $unwind: "$company" },
{ $match: { "company.k": "$id"} },
{ $lookup: { from: "company", localField: "company.v", foreignField: "_id", as: "company_data" } }
])
Результат приведенной выше агрегации можно увидеть ниже:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df0"),
"userId" : "admin",
"userKeyEncrypted" : {
"salt" : "78e0528db239fd86",
"encryptedAttribute" : "e4543ddac7cca9757721379e4e70567bb13956694f473b73f7723ac2e2fc5245"
},
"passwordHash" : "$2a$10$STRNORu9rcbq4qYUMld4G.HJk8QQQQBmAswSNC/4PBn2bih0BvjM6",
"roles" : [
"ADMIN"
],
"uuid" : "b8aafdcf-d5c4-4040-a96d-8ab1a8608af8",
"disabled" : false,
"company" : {
"k" : "$id",
"v" : ObjectId("59a92501df01110fbb6a5dee")
},
"company_data" : [
{
"_id" : ObjectId("59a92501df01110fbb6a5dee"),
"name" : "Test",
"gln" : "1234567890123",
"uuid" : "f1f86961-e8d5-40bb-9d3f-fdbcf549066e",
"creationDate" : ISODate("2017-09-01T09:14:41.551Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.551Z"),
"version" : NumberLong(1),
"disabled" : false
}
]
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df1"),
"userId" : "sample",
"userKeyEncrypted" : {
"salt" : "e3ac48695dea5f51",
"encryptedAttribute" : "e804758b0fd13c219c3fc383eaa9267b70f7b8a1ed74f05575add713ce11804a"
},
"passwordHash" : "$2a$10$Gt2dq1vy4J9MeqDnXjokAOtvFcvbhe/g9wAENXFPaPxLAw1L4EULG",
"roles" : [
"USER"
],
"uuid" : "55b62d4c-e5ee-408d-80c0-b79e02085b02",
"disabled" : false,
"company" : {
"k" : "$id",
"v" : ObjectId("59a92501df01110fbb6a5def")
},
"company_data" : [
{
"_id" : ObjectId("59a92501df01110fbb6a5def"),
"name" : "Sample",
"gln" : "3210987654321",
"uuid" : "fee69ee4-b29c-483b-b40d-e702b50b0451",
"creationDate" : ISODate("2017-09-01T09:14:41.562Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.562Z"),
"version" : NumberLong(1),
"disabled" : false
}
]
}
Как можно надеяться, у нас есть только два пользователя, которые содержали ссылку на компанию, и теперь у двух пользователей также есть полные данные компании в ответе. Теперь можно использовать дополнительную фильтрацию, чтобы избавиться от помощника ключа/значения, а также для скрытия нежелательных данных.
Окончательный запрос, который мы создали, выглядит следующим образом:
db.user.aggregate([
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, company: { $objectToArray: "$$ROOT.company" }} },
{ $unwind: "$company" },
{ $match: { "company.k": "$id"} },
{ $lookup: { from: "company", localField: "company.v", foreignField: "_id", as: "company_data" } },
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, "companyUuid": { $arrayElemAt: [ "$company_data.uuid", 0 ] } } }
])
Что, наконец, возвращает наше желаемое представление:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df0"),
"userId" : "admin",
"userKeyEncrypted" : {
"salt" : "78e0528db239fd86",
"encryptedAttribute" : "e4543ddac7cca9757721379e4e70567bb13956694f473b73f7723ac2e2fc5245"
},
"passwordHash" : "$2a$10$STRNORu9rcbq4qYUMld4G.HJk8QQQQBmAswSNC/4PBn2bih0BvjM6",
"roles" : [
"ADMIN"
],
"uuid" : "b8aafdcf-d5c4-4040-a96d-8ab1a8608af8",
"disabled" : false,
"companyUuid" : "f1f86961-e8d5-40bb-9d3f-fdbcf549066e"
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df1"),
"userId" : "sample",
"userKeyEncrypted" : {
"salt" : "e3ac48695dea5f51",
"encryptedAttribute" : "e804758b0fd13c219c3fc383eaa9267b70f7b8a1ed74f05575add713ce11804a"
},
"passwordHash" : "$2a$10$Gt2dq1vy4J9MeqDnXjokAOtvFcvbhe/g9wAENXFPaPxLAw1L4EULG",
"roles" : [
"USER"
],
"uuid" : "55b62d4c-e5ee-408d-80c0-b79e02085b02",
"disabled" : false,
"companyUuid" : "fee69ee4-b29c-483b-b40d-e702b50b0451"
}
Некоторая заключительная записка к этому подходу: эта агрегировка не очень быстро, к сожалению, но, по крайней мере, она выполняет свою работу. Я не тестировал его с помощью массива ссылок, как изначально спросил, хотя это может потребовать некоторых дополнительных разрывов.
Обновление. Еще один способ агрегирования данных, который больше соответствует комментариям в вышеупомянутом отчете об ошибке, можно увидеть ниже:
db.user.aggregate([
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, companyRefs: { $let: { vars: { refParts: { $objectToArray: "$$ROOT.company" }}, in: "$$refParts.v" } } } },
{ $match: { "companyRefs": { $exists: true } } },
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, "companyRef": { $arrayElemAt: [ "$companyRefs", 1 ] } } },
{ $lookup: { from: "company", localField: "companyRef", foreignField: "_id", as: "company_data" } },
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, "companyUuid": { $arrayElemAt: [ "$company_data.uuid", 0 ] } } }
])
Здесь операция $let: { vars: ..., in: ... }
копирует ключ и значение ссылки в собственный объект и, следовательно, позволяет позже искать ссылку через соответствующую операцию.
Какая из этих агрегаций работает лучше, еще предстоит профилировать.
Это было невозможно, но улучшения от Mongo v3.4 мы можем приблизиться к нему.
Вы можете сделать это с помощью mongo-join-query
. Ваш код будет выглядеть так:
const mongoose = require("mongoose");
const joinQuery = require("mongo-join-query");
joinQuery(
mongoose.models.Foo,
{
find: { "bars.testprop": { $in: [true] } },
populate: ["bars"]
},
(err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);
За кулисами mongo-join-query
будет использоваться ваша схема Mongoose, чтобы определить, к каким моделям присоединиться, и создаст конвейер агрегации , который будет выполнять join и запрос.
Раскрытие: я написал эту библиотеку, чтобы решить именно этот прецедент.