【问题标题】:MongoDB schema: store id as FK or whole documentMongoDB 模式:将 id 存储为 FK 或整个文档
【发布时间】:2020-03-04 00:01:30
【问题描述】:

我正在设计 MongoDB 结构(实际上是 NodeJS 应用程序中的模型结构)。我将拥有玩家比赛收藏。

是只存储玩家加入的比赛的 id,在每个玩家的对象中(如 RDBM 中的 FK)还是将整个匹配对象存储在玩家对象中?

在应用程序中,其中一项操作是显示比赛的详细信息,在此视图中,用户将看到参加此特定比赛的球员(他们的姓名、国家等)。这让我觉得将整个 Match 文档存储在 Player 文档中会更好。

有什么建议吗?

【问题讨论】:

    标签: node.js mongodb schema


    【解决方案1】:

    这是一个多对多关系的例子。 我猜最初会有大约 100 名球员和 100 场比赛数据。 设计选项是嵌入引用

    (1) 嵌入:

    查询最多的一侧将嵌入查询较少的一侧。 根据您的要求(显示比赛的详细信息,在此视图中,用户将看到参加此特定比赛的球员及其详细信息),比赛方将嵌入球员数据。

    结果是两个集合。主要的一个是匹配项。次要的是球员;这将包含玩家的所有源数据(id、姓名、出生日期、国家/地区和其他详细信息)。

    一场比赛只存储少数球员数据,并且比赛集合中只存储球员数据的一个子集。 导致玩家数据重复。这很好,它将被复制的主要是静态信息;名字和国家之类的东西。但是,随着时间的推移,其中一些可能需要更新,应用程序需要处理这一点。

    玩家数据以嵌入文档数组的形式存储在匹配集合中。 这种设计是可能的解决方案。

    匹配:

    _id
    matchId
    date
    place
    players [ { playerId 1, name1, country1 }, { playerId 2, ... }, ... ]
    outcome
    

    玩家:

    _id
    name
    dob
    country
    ranking
    


    (2) 参考:

    这也将有两个集合:球员和比赛。 任何一方都可以进行引用,比赛可以引用球员,反之亦然。 根据需求,查询最多的一方会有查询较少的一方的引用;比赛将有球员ID参考。这将是一组玩家 ID。

    匹配:

    _id
    matchId
    date
    place
    players [ playerId 1, playerId 2, ... ]
    

    players 集合将具有与之前情况相同的数据。

    【讨论】:

      【解决方案2】:

      我认为将整个 Match 文档存储在 Player 文档中并不是一个好选择。 每次玩家参加比赛时,您的玩家文档都需要更新。

      您有 2 个主要选择:

      1-) 使用子引用。 (在比赛中引用球员)。

      所以如果我们想使用猫鼬模型来实现它:

      玩家模型:

      const mongoose = require("mongoose");
      
      const playerSchema = mongoose.Schema({
        name: String,
        country: String
      });
      
      const Player = mongoose.model("Player", playerSchema);
      
      module.exports = Player;
      

      匹配模型:

      const mongoose = require("mongoose");
      
      const matchSchema = mongoose.Schema({
        date: {
          type: Date,
          default: Date.now()
        },
        players: [
          {
            type: mongoose.Schema.Types.ObjectId,
            ref: "Player"
          }
        ]
      });
      
      const Match = mongoose.model("Match", matchSchema);
      
      module.exports = Match;
      

      使用这些模型,我们的比赛文档将如下所示(引用 playerId):

      {
          "_id" : ObjectId("5dc419eff6ba790f4404fd07"),
          "date" : ISODate("2019-11-07T16:19:39.691+03:00"),
          "players" : [
              ObjectId("5dc41836985aaa22c0c4d423"),
              ObjectId("5dc41847985aaa22c0c4d424"),
              ObjectId("5dc4184e985aaa22c0c4d425")
          ],
          "__v" : 0
      }
      

      我们可以使用这条路线来获取所有玩家信息的比赛信息:

      const Match = require("../models/match");
      
      
      router.get("/match/:id", async (req, res) => {
        const match = await Match.findById(req.params.id).populate("players");
      
        res.send(match);
      });
      

      结果会是这样的:

      [
          {
              "date": "2019-11-07T13:19:39.691Z",
              "players": [
                  {
                      "_id": "5dc41836985aaa22c0c4d423",
                      "name": "player 1",
                      "country": "country 1",
                      "__v": 0
                  },
                  {
                      "_id": "5dc41847985aaa22c0c4d424",
                      "name": "player 2",
                      "country": "country 1",
                      "__v": 0
                  },
                  {
                      "_id": "5dc4184e985aaa22c0c4d425",
                      "name": "player 3",
                      "country": "country 2",
                      "__v": 0
                  }
              ],
              "_id": "5dc419eff6ba790f4404fd07",
              "__v": 0
          }
      ]
      

      2-) 在比赛中嵌入球员,并且仍然保持独立的球员收藏。 但这将需要比第一个选项更多的空间。

      所以你的比赛在比赛集合中看起来像这样:

          {
              "date": "2019-11-07T13:19:39.691Z",
              "players": [
                  {
                      "_id": "5dc41836985aaa22c0c4d423",
                      "name": "player 1",
                      "country": "country 1",
                      "__v": 0
                  },
                  {
                      "_id": "5dc41847985aaa22c0c4d424",
                      "name": "player 2",
                      "country": "country 1",
                      "__v": 0
                  },
                  {
                      "_id": "5dc4184e985aaa22c0c4d425",
                      "name": "player 3",
                      "country": "country 2",
                      "__v": 0
                  }
              ],
              "_id": "5dc419eff6ba790f4404fd07",
              "__v": 0
          }
      

      但这在获取比赛信息时可能会快一点,因为不需要填充球员信息。

      const Match = require("../models/match");
      
      router.get("/match/:id", async (req, res) => {
        const match = await Match.findById(req.params.id);
      
        res.send(match);
      });
      

      【讨论】:

      • 选项 1 正是我所需要的。现在的性能很惊人,甚至不能和以前版本的同一个应用程序(asp.net + MySql)相比。多亏了这一点,我意识到我必须做类似的事情才能从 countries 集合中获取国家代码以获取国家代码。谢谢一百万!
      【解决方案3】:

      在我看来,这里的 ma​​tches 集合是一个独立存在的文档集合,然后与参与比赛的玩家连接。话虽如此,我会做一个匹配键数组

      如果嵌套的文档可以被视为父文档“所有”,我建议使用嵌套文档结构。例如,todo 嵌套在 todoList 文档中的文档。

      【讨论】:

      • 获得所有参加ceratin比赛的玩家不会有问题吗?假设我有 100 个用户,每个用户加入了 10 场比赛 - 在显示一个包含某场比赛的球员列表的视图之前的查询会很疯狂。
      • 不,我认为这不会对性能造成重大影响。这一切都取决于您如何进行查询。您可以查看 Mongoose 的 .populate,以便更轻松地处理此类查询。 (mongoosejs.com/docs/populate.html)
      猜你喜欢
      • 1970-01-01
      • 2021-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多