如何在MongoDB中查询引用的对象?

Pat*_*Pat 35 mongodb

我的Mongo数据库中有两个集合,Foos包含对一个或多个Bars的引用:

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

Bar: {
   testprop: true
}
Run Code Online (Sandbox Code Playgroud)

我想要的是找到所有Foo至少有一个Bar将testprop设置为true 的s .我已经尝试过这个命令,但它没有返回任何结果:

db.Foo.find({ "bars.testprop" : { "$in": [ true ] } })
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

sid*_*ate 59

您现在可以在Mongo 3.2中使用 $lookup

$lookup 有四个论点

from:指定同一数据库中的集合以执行连接.from集合无法分片.

localField:指定输入到$ lookup阶段的文档中的字段.$ lookup通过from集合的文档在localField上执行与foreignField的相等匹配.

foreignField:指定from集合中文档的字段.

as:指定要添加到输入文档的新数组字段的名称.新数组字段包含来自集合的匹配文档.

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

   }},
   {$match: {
    "bar.testprop": true
   }}
)
Run Code Online (Sandbox Code Playgroud)

  • 这个答案需要解释. (2认同)

Chr*_*ain 18

你不能.请参阅http://www.mongodb.org/display/DOCS/Database+References

你必须在客户端做到这一点.

  • 好吧......废话.太糟糕了.谢谢. (23认同)
  • @Pat你现在可以查询3.2看下面我的答案 (4认同)
  • 答案通常是将数据去规范化为父对象的子元素.它增加了维护非规范化记录的开销,但允许这种查询模式. (2认同)

Rom*_*ner 6

我们遇到了类似的问题,因为我们将 MongoDB(3.4.4,实际上是 3.5.5 用于测试)与我们@Referenece在几个实体上使用的 Morphia 结合使用。我们对这个解决方案并不满意,正在考虑删除这些声明,而是手动进行引用查找。

即我们有一个公司集合和一个用户集合。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
}
Run Code Online (Sandbox Code Playgroud)

而用户集合包含以下条目:

/* 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
}
Run Code Online (Sandbox Code Playgroud)

为了创建一个特殊的公司用户视图,我们还想取消对用户中的公司的引用,并且只包含选定的字段。根据错误报告中的评论,我们了解到 MongoDB 提供了一种$objectToArray: "$$ROOT.element"操作,该操作基本上将给定元素的字段拆分为键和值对。注意$objectToArray操作是在 MongoDB 3.4.4 版本中添加的!

使用该$objectToArray操作对包含在用户集合中的 company 元素进行聚合可能如下所示:

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

上述聚合的结果如下所示:

/* 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
}
Run Code Online (Sandbox Code Playgroud)

现在只需过滤不需要的内容(即没有分配公司的用户并选择正确的数组条目),以便提供$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" } }
])
Run Code Online (Sandbox Code Playgroud)

上述聚合的结果如下所示:

/* 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
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

正如所希望的那样,我们只有包含公司参考的两个用户,并且这两个用户现在在响应中也有完整的公司数据。现在可以应用额外的过滤来摆脱键/值助手并隐藏不需要的数据。

我们提出的最终查询如下所示:

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 ] } } }
])
Run Code Online (Sandbox Code Playgroud)

最终返回我们想要的表示:

/* 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"
}
Run Code Online (Sandbox Code Playgroud)

这种方法的一些最后说明:遗憾的是,这种聚合不是很快,但至少它完成了工作。我还没有按照最初的要求使用一系列引用对其进行测试,尽管这可能需要一些额外的展开。


更新:另一种聚合数据的方法,更符合上述错误报告中的评论,如下所示:

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 ] } } }
])
Run Code Online (Sandbox Code Playgroud)

这里的$let: { vars: ..., in: ... }操作将引用的键和值复制到自己的对象中,从而允许稍后通过相应的操作查找引用。

这些聚合中哪一个表现更好还有待分析。

  • 你好,罗曼,如果我的话伤害了你,我很抱歉。这不是故意的。我在 SQL 方面工作了 25 年,现在在 mongo(即 NoSQL)方面只工作了 1 年。老实说,我很难尝试在 MongoDB 中执行此类基本请求,有时我想知道为什么我们要做这些事情。我正在为一个现有的项目工作,所以几乎不可能更改为 SQL。我有 6 个相互连接的实体,因此将所有内容都放在一个集合中几乎无法使用。不管怎样,我找到了另一种方法来检索我想要的数据。 (4认同)
  • 这比使用 SQL 更好吗? (2认同)