【问题标题】:How to query by reference field in MongoDB?如何在 MongoDB 中通过引用字段查询?
【发布时间】:2019-07-05 15:03:00
【问题描述】:

我有两个 Mongo 模式:

用户:

{
  _id: ObjectId,
  name: String,
  country: ObjectId // Reference to schema Country
}

国家:

{
  _id: ObjectId,
  name: String
}

我想获取所有国家名称为“越南”的用户。

对于这种情况,我可以使用哪种查询(仅针对 User 架构)?

我希望它看起来像这样的 SQL 查询:

SELECT * 
FROM User
JOIN Country 
ON User.country = Country._id 
WHERE Country.name = 'VietNam'

【问题讨论】:

    标签: javascript node.js mongodb mongoose aggregation-framework


    【解决方案1】:

    您可以将以下聚合与 mongodb 3.6 及更高版本一起使用

    db.country.aggregate([
      { "$match": { "name": "VietNam" } },
      { "$lookup": {
        "from": Users.collection.name,
        "let": { "countryId": "$_id" },
        "pipeline": [
          { "$match": { "$expr": { "$eq": [ "$country", "$$countryId" ] } } },
        ],
        "as": "users",
      }},
      { "$unwind": "$users" },
      { "$replaceRoot": { "newRoot": "$users" }}
    ])
    

    【讨论】:

    • 谢谢。它工作得很好。但是我可以将条件组合为 user.name = 'Anthony' 和 country.name = 'VietNam' 吗?
    【解决方案2】:

    与关系数据库不同,这不是 Mongo 擅长的事情,当您使用 NoSQL 时,您通常应该以不同的方式构建架构。在这种情况下,您可以将countryName 字段添加到user 集合(也可能是国家/地区的索引),以便您可以查询{countryName: "Vietnam"}。 (而不是国家名称,使用 iso-2 国家代码可能更有意义)

    如果您确实需要进行“加入”,您可以使用 $lookup operator in the aggregation pipeline - 请记住,聚合不能很好地扩展并且往往有点难以编写。

    【讨论】:

    • 所以当我更新 countryId A 的国家名称时,我必须找到所有用户记录的 countryId 都是 A 并更新所有记录?性能没问题
    • 国家名称(或 iso-2 国家代码)多久更改一次?更新所有用户实体是一项非常昂贵的操作,但这种操作很少发生(并且可以通过让应用程序端代码进行重命名来避免)。对每个查询进行额外查找最终会变得更加昂贵。
    • 但是记录通常要更改?
    • 一般来说,您需要在 mongo 中对数据进行非规范化处理,其中一部分工作通常涉及确保多个文档保持同步。如果记录经常更改,使用 $lookup 可以让您从不同的集合中返回文档。如何构建数据和查询很大程度上取决于您的用例
    【解决方案3】:

    这是我常用的代码:

    模型/users.js

    
    const mongoose = require("mongoose");
    const Countries = require("./countries");
    
    const UsersSchema = new mongoose.Schema({
        name: Sting,
        country: Countries.schema
    });
    
    module.exports = mongoose.model("Users", UsersSchema);
    

    控制器/users.js

    const express = require("express");
    const router = express.Router();
    const Users = require("../models/users");
    
    router.post("/register", async(req, res) => {
        try{
          const Me = await Users.create({name: req.body.name, country: req.body.country})
          /* If you make the request correctly on the frontend req.body.name is a 
             string and req.body.country is an object with one property, it's name */
          res.status(200).json({
              data: Me
          });
        }
        catch(err){
          res.status(400).json({message:err.message})
    
    
    

    由于子模式是一个对象,因此您将其引用为 Me.country.name。希望这会有所帮助!

    【讨论】:

    • 谢谢。但我想保留 2 个模式,用户只存储国家 ID,而不是国家对象。因为国家集合将用于显示列表国家,所以应该分开到 2 个模式。主要问题是我不知道如何按国家名称查询用户
    • 您仍然有 2 个文档集合,但如果您想按国家/地区呼叫用户,您可以使用 Users.findMany({ country: req.body.country})。我想我读错了你的问题,但this 是对 MongoDB 所有事物的一个很好的参考。
    【解决方案4】:

    如果要关联 2 个或更多集合,则必须定义集合的引用对象

    我的解决方案请查看https://mongoosejs.com/docs/populate.html

    【讨论】:

      猜你喜欢
      • 2018-07-17
      • 1970-01-01
      • 1970-01-01
      • 2021-10-09
      • 2018-12-14
      • 2019-07-11
      • 2013-07-04
      • 1970-01-01
      相关资源
      最近更新 更多