Prisma 具有至少一个值的多对多关系

问题描述 投票:0回答:1

我在一个项目中使用 Prisma 和 Postgresql 来存储和管理食谱。

我正在努力实现一种多对多关系,在这种关系中我可以限制一方至少需要一个关系字段值。

食谱可以有一种或多种香气。香气可以包含在零个或多个食谱中。

我使用显式关系表,因为我需要在其中存储附加数据(配方中每种香气的数量)。

配方、香气和关系模型如下:

model Recipe {
  id          Int              @id @default(autoincrement())
  name        String
  base        String?          @default("50\/50")
  description String?          @default("aucune description")
  rating      Int?             @default(1)
  aromas      RecipeToAromas[]
}

model Aroma {
  id      Int              @id @default(autoincrement())
  name    String
  brand   Brand?           @relation(fields: [brandId], references: [id])
  brandId Int?             @default(1)
  recipes RecipeToAromas[]

  @@unique([name, brandId], name: "aromaIdentifier")
}

model RecipeToAromas {
  id         Int    @id @default(autoincrement())
  recipeId   Int
  aromaId    Int
  quantityMl Int
  recipe     Recipe @relation(fields: [recipeId], references: [id])
  aroma      Aroma  @relation(fields: [aromaId], references: [id])
}

我想限制食谱至少有一种香气。

根据定义,多对多定义了到多的关系。

我考虑通过在 Recipe 和 Aroma 之间添加额外的一对多关系来解决问题。

这意味着在配方中添加一个额外的香气字段来存储所需的一种香气(并将香气字段重命名为additionalAromas以避免混淆):

model Recipe {
  id          Int              @id @default(autoincrement())
  name        String
  base        String?          @default("50\/50")
  description String?          @default("aucune description")
  rating      Int?             @default(1)
  aromas      RecipeToAromas[]
  aroma       Aroma            @relation(fields: [aromaId], references: [id])
  aromaId     Int
}

并在 Aroma 中添加一个配方字段,以建立关系:

model Aroma {
  id      Int              @id @default(autoincrement())
  name    String
  brand   Brand?           @relation(fields: [brandId], references: [id])
  brandId Int?             @default(1)
  recipes RecipeToAromas[]
  recipe  Recipe[]

  @@unique([name, brandId], name: "aromaIdentifier")
}

但这感觉不对,因为我会有重复的内容:Aroma 中的菜谱和菜谱字段会存储相同的数据。

** 编辑 ** 我尝试使用此解决方案来解决问题,但它产生了第二个问题: 配方中的每种香气在该配方中必须是唯一的(这由关系数据库中的化合物@unique 反映)。

如果我在配方和香气之间添加一对多关系,那么香气可以在配方中存储多次:

await prisma.recipe.create({
    data: {
      name: "First recipe",
      aromaId: 1,
      aromas: {
        create: [
          { aromaId: 1, quantityMl: 2 },
          { aromaId: 2, quantityMl: 2 },
          { aromaId: 3, quantityMl: 2 },
        ],
      },
    },
  });

我当然可以通过仅依靠突变函数和用户输入的验证来解决这个问题。当我使用打字稿时,可能会尝试添加类型的安全层。 但我觉得这会使数据库变得脆弱并且容易出错,特别是如果我必须与其他开发人员协作,甚至在不同的项目中使用数据库。

我找不到任何涵盖类似情况的资源,当然我花了很多时间搜索和重新阅读文档。

我是 prisma 的新手(昨天开始),并且对 RDBMS 没有太多经验,所以感觉好像我错过了一些东西。

javascript database postgresql prisma rdbms
1个回答
0
投票

所以你的问题可以通过两种方式解决(使用原始 SQL),一种比另一种更好。另外,我将使用 PostgreSQL 语法,因为我不熟悉 prisma,但我确信这最多可以转换为 ORM 模型,最坏的情况是作为原始 SQL 语句插入 postgres


使用触发器

触发器与特定表相关联,并且将在该表中完成特定操作时运行,对于我们的情况,我们希望在

Recipe
获取新元素后运行它。语法如下(也请随意阅读文档here,因为这是一个相当复杂的主题,无法用几句话概括)

CREATE OR REPLACE FUNCTION consistency_check_recipe() RETURNS TRIGGER AS $$
  BEGIN
    IF NOT EXISTS (SELECT * FROM RecipeToAromas WHERE recipeId = NEW.ID) THEN
      DELETE FROM Recipe WHERE Recipe.id = NEW.id;
      RAISE EXCEPTION 'Must have at least 1 Aroma';
    END IF;
    RETURN NULL; 
  END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE TRIGGER recipe_aroma_check 
AFTER INSERT OR UPDATE ON Recipe 
INITIALLY DEFERRED
FOR EACH ROW EXECUTE FUNCTION consistency_check_recipe();

总结一下上面的函数/触发器的作用,一旦创建了新的

Recipe
条目,触发器就会等到事务的最后一刻,然后检查是否存在带有
RecipeToAromas
Recipe.id
条目。这也意味着,如果您希望创建新的食谱,您还必须在同一交易中添加香气,例如这会起作用

BEGIN;

INSERT INTO Recipe 
VALUES (1, 'Borsh', 'Tasty Water', 'Food Nothing more', 100);

INSERT INTO RecipeToAromas 
VALUES (DEFAULT, 1, 3, 4);

COMMIT;

但这不会

INSERT INTO Recipe 
VALUES (1, 'Borsh', 'Tasty Water', 'Food Nothing more', 100);

INSERT INTO RecipeToAromas 
VALUES (DEFAULT, 1, 3, 4);

由于每个语句都被视为单独的事务,因此触发器将在

Recipe
获取 INSERT 之后但在
RecipeToAromas
获取 INSERT 之前被调用。


使用检查约束

您可以但

不应该在检查约束中使用子查询。


这里还有很多信息要添加,我会在短时间内更新我的答案,但现在我没时间了,我必须去某个地方。

© www.soinside.com 2019 - 2024. All rights reserved.