Подтвердить что ты не робот

Как я могу запросить объекты, на которые ссылаются в MongoDB?

У меня есть две коллекции в моей базе данных 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 ] } })

Любые идеи?

4b9b3361

Ответ 2

Теперь вы можете сделать это в 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
   }}
)

Ответ 3

Хорошо.. вы можете запросить Bar Модель для _id всех документов с помощью testprop: true, затем найти find $in и заполнить bars в Foo Model с массивом тех _id вы получили от первого запроса..: P

Возможно, это считается "В клиенте": P просто мысль.

Ответ 4

У нас была аналогичная проблема, так как мы используем 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: ... } копирует ключ и значение ссылки в собственный объект и, следовательно, позволяет позже искать ссылку через соответствующую операцию.

Какая из этих агрегаций работает лучше, еще предстоит профилировать.

Ответ 5

Это было невозможно, но улучшения от 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 и запрос.

Раскрытие: я написал эту библиотеку, чтобы решить именно этот прецедент.