如何使用 MongoDB 正确设计配方/成分的数据模型

FIr*_*man 1 database erd many-to-many mongodb mongodb-query

最近我使用 Hackalode 设计了一个数据库模型或 ERD。所以我目前面临的问题是,根据我当前的设计,我无法按照我的意愿正确查询它。我用 MYSQL 研究了 ERD,确实知道 Mongo 的工作方式不一样

这个想法很简单,我想要一个包含一系列成分列表的食谱,并且这些成分来自单独的集合。配方还包括成分的测量,即。(1汤匙糖)

还可以从配料表中查询,找到包含该配料的菜谱

我希望这个集合处于多对多关系,并且配方可以使用数据库中已有的成分。

目前的数据模型设计

我只是不知道如何查询数据

我通过使用 $elemMatch 和 populate 尝试了很多方法,结果我得到的只是空数组列表。

我期待两种类型的查询,我可以按成分名称或配方进行查询

我的预期结果是这样的

[{
   id: ...,
   name: ....,
   description: ...,
   macros: [...],
   ingredients: [
   {
       id,
       amount: ....,
       unit: ....
       ingredient: {
           id: ....,
           name: ....
       }
   }
}, { ... }]
Run Code Online (Sandbox Code Playgroud)

但不是得到

[]
Run Code Online (Sandbox Code Playgroud)

Mar*_*erg 6

恕我直言,你的设计是完全错误的。您过度规范化了数据。我会做一些更简单的事情并使用嵌入。其背后的原因是,您首先定义用例,然后对数据进行建模,以最有效的方式回答用例中出现的问题。

假设的用例

  1. 作为用户,我想要所有食谱的列表。
  2. 作为用户,我想要一份按成分列出的所有食谱的列表。
  3. 作为一名设计师,我希望能够展示所有成分的列表。
  4. 作为用户,我希望能够链接到复合成分的食谱(如果网站上有)。

当然,这只是一小段摘录,但对于这个例子来说已经足够了。

如何回答问题

好的,第一个非常简单:

db.recipes.find()[.limit()[.skip()]]
Run Code Online (Sandbox Code Playgroud)

现在,我们怎样才能按成分找到呢?简单的答案:对成分名称(可能还有其他一些字段,因为每个集合只能有一个文本索引)进行文本索引。然后,查询同样简单:

db.recipes.find({$text:{$search:"ingredient name"}})
Run Code Online (Sandbox Code Playgroud)

“嘿,等一下!我如何获得所有成分的清单?” 让我们假设我们想要一个简单的成分列表,以及它们实际使用频率的数字:

db.recipes.aggregate([
  // We want all ingredients as single values
  {$unwind:"$Ingredients"},
  // We want the response to be "Ingredient"
  {$project:{_id:0,"Ingredient":"$Ingredients.Name"}
  // We count the occurrence of each ingredient
  // in the recipes
  {$group:{_id:"$Ingredient",count:{$sum:1}}}
])
Run Code Online (Sandbox Code Playgroud)

这实际上已经足够了,除非您有一个包含无数食谱的数据库。在这种情况下,您可能希望深入研究增量映射/归约而不是聚合。提示:您应该向配方添加时间戳以便能够使用增量映射/归约。

如果您有几百 K 到几百万个食谱,您还可以添加一个$out阶段来预聚合数据。

关于测量

恕我直言,定义测量值是没有意义的。有茶匙、汤匙、公制和英制测量单位、“打”等分组或“丁香”等规格。您确实不想相互转换,甚至不想将其设置为有限数量的测量值。一瓣大蒜有多少盎司?;)

底线:将其设为自由文本字段,也许带有一些自动完成建议。

修改后的数据模型

食谱

{
  _id: new ObjectId(),
  Name: "Surf & Turf Kebap",
  Ingredients: [
    {
      Name: "Flunk Steak",
      Measurement: "200 g"
    },
    { 
      Name: "Prawns",
      Measurement: "300g",
      Note: "Fresh ones!"
    },
    {
      Name: "Garlic Oil",
      Measurement: "1 Tablespoon",
      Link: "/recipes/5c2cc4acd98df737db7c5401"
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

以及文本索引的示例:

db.recipes.createIndex({Name:"text","Ingredients.Name":"text"})
Run Code Online (Sandbox Code Playgroud)

其背后的理论

配方是您的基本数据结构,因为您的应用程序应该存储并提供它们,可能基于某些标准。成分和测量(在有意义的范围内)可以很容易地从食谱中得出。那么为什么要费力去独立存储成分和测量值呢?它只会使您的数据模型变得不必要的复杂,而不会提供任何优势。