¿Cómo consulto objetos referenciados en MongoDB?

Tengo dos colecciones en mi base de datos de Mongo, y las de los Foo contienen referencias a una o más Bar :

 Foo: { prop1: true, prop2: true, bars: [ { "$ref": "Bar", "$id": ObjectId("blahblahblah") } ] } Bar: { testprop: true } 

Lo que quiero es encontrar todos los Foo que tengan al menos una Bar que tenga su testprop establecido en verdadero. Intenté este comando, pero no devuelve ningún resultado:

 db.Foo.find({ "bars.testprop" : { "$in": [ true ] } }) 

¿Algunas ideas?

No puedes. Ver http://www.mongodb.org/display/DOCS/Database+References

Tienes que hacerlo en el cliente.

Ahora puede hacerlo en Mongo 3.2 utilizando $lookup

$lookup toma cuatro argumentos

from : especifica la colección en la misma base de datos para realizar la unión. La colección de from no se puede fragmentar.

localField : especifica el campo de la entrada de documentos a la etapa $ lookup. $ searchup realiza una coincidencia de igualdad en el campo local con el campo exterior de los documentos de la colección from.

foreignField : especifica el campo de los documentos en la colección from.

as : especifica el nombre del nuevo campo de matriz para agregar a los documentos de entrada. El nuevo campo de matriz contiene los documentos coincidentes de la colección from.

 db.Foo.aggregate( {$unwind: "$bars"}, {$lookup: { from:"bar", localField: "bars", foreignField: "_id", as: "bar" }}, {$match: { "bar.testprop": true }} ) 

Bueno … podrías consultar el Modelo de Bar para el _id de todos los documentos con testprop: true , luego buscar $in y rellenar bars en el Modelo Foo con una matriz de esos _id que obtuviste de la primera consulta ..: PAG

Tal vez eso cuenta como “En el cliente”: P solo un pensamiento.

No fue posible antes, pero las mejoras de Mongo v3.4 nos pueden acercar mucho.

Puedes hacerlo con mongo-join-query . Tu código se vería así:

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

¿Como funciona?

Detrás de las escenas, mongo-join-query usará su esquema de Mongoose para determinar qué modelos unir y creará una canalización de agregación que realizará la combinación y la consulta.

Divulgación : escribí esta biblioteca para abordar precisamente este caso de uso.

Hemos tenido un problema similar ya que usamos MongoDB (3.4.4, en realidad 3.5.5 para probar) en combinación con Morphia donde usamos @Referenece en un par de entidades. Aunque no estamos muy contentos con esta solución y estamos considerando eliminar estas declaraciones y en su lugar hacer las búsquedas de referencia de forma manual.

Es decir, tenemos una colección de empresas y una colección de usuarios. La entidad de usuario en Morphia contiene una statement @Refrence en una entidad de compañía. Las colecciones respectivas de la compañía contienen entradas como:

 /* 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 } 

mientras que las colecciones de usuarios contienen las siguientes entradas:

 /* 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 } 

Para crear una vista de usuario especial de la empresa, también queríamos eliminar la referencia de la empresa en el usuario y solo incluir los campos seleccionados. Basándonos en un comentario dentro de un informe de error , descubrimos que MongoDB proporciona una $objectToArray: "$$ROOT.element" que básicamente divide los campos de los elementos dados en pares clave y valor. ¡Observe que se $objectToArray operación $objectToArray en la versión 3.4.4 de MongoDB!

Una agregación en el elemento de compañía contenido en la colección de usuarios que usa la operación $objectToArray puede verse como a continuación:

 dp.user.aggregate([{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, company: { $objectToArray: "$$ROOT.company" } } }]) 

El resultado de la agregación anterior se ve así:

 /* 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 } 

Ahora es simplemente una cuestión de filtrar cosas no deseadas (es decir, usuarios que no tienen asignada ninguna compañía y seleccionar las entradas correctas de la matriz) para alimentar la operación $lookup searchup @sidgate ya explicó y copió el valor de la empresa desreferenciada en la respuesta del usuario.

Es decir, una agregación como la que se muestra a continuación realizará una combinación y agregará los datos de la empresa a los usuarios que tengan asignada una empresa como el valor definido en la búsqueda:

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

El resultado de la agregación anterior se puede ver a continuación:

 /* 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 } ] } 

Como se puede ver, solo tenemos los dos usuarios que contenían una referencia de la compañía y los dos usuarios ahora también tienen los datos completos de la compañía en la respuesta. Ahora se puede aplicar un filtro adicional para deshacerse de la clave / valor auxiliar y también para ocultar los datos no deseados.

La consulta final que surgió se ve 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" } }, { $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, "companyUuid": { $arrayElemAt: [ "$company_data.uuid", 0 ] } } } ]) 

Que finalmente devuelve nuestra representación deseada:

 /* 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" } 

Una nota final a este enfoque: esta agregación no es muy rápida, lamentablemente, pero al menos hace el trabajo. No lo he probado con una serie de referencias como se solicitó originalmente, aunque esto puede requerir algunos desenrollamientos adicionales probablemente.


Actualización: una forma adicional de agregar los datos, que está más en línea con los comentarios en el informe de errores mencionado anteriormente, se puede ver a continuación:

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

Aquí, la operación $let: { vars: ..., in: ... } copia la clave y el valor de la referencia en un objeto propio y, por lo tanto, permite luego buscar la referencia a través de la operación correspondiente.

Cuál de estas agregaciones tiene un mejor desempeño aún no se ha perfilado.