我在一个项目中使用了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])
}

我想限制食谱至少有一种香味.

根据定义,多对多定义了zero对多关系.

我考虑过在食谱和香味之间增加一个"一对多"的关系来解决这个问题.

这意味着在食谱中添加额外的香气字段来存储所需的香气(并将香气字段重命名为additional Aromas以避免混淆):

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
}

以及根据需要在香气中添加配方字段以建立该关系:

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 },
        ],
      },
    },
  });

当然,我可以通过依赖于Mutations 函数和用户输入的验证来解决这个问题.我可能会try 在类型上添加一层安全性,因为我正在使用typescript. 但我觉得这会使数据库变得脆弱,而且容易出错,特别是如果我不得不与其他开发人员合作,甚至在不同的项目中使用数据库.

我找不到任何涉及类似情况的资源,当然我花了很多时间搜索和重读文档.

我是新的prisma(昨天开始),我没有太多的经验与关系数据库,所以感觉我错过了一些东西.

推荐答案

因此,你的问题可以用两种方法(在原始SQL中)解决,一种比另一种更好.此外,我将使用PostgreSQL语法,因为我不熟悉prisma,但我相信它最好可以翻译成ORM模型,或者最坏的情况是插入postgr中的原始SQL语句


Using Triggers

触发器与特定表相关联,并将在该表中完成特定操作时运行,对于我们的例子,我们希望在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 CONSTRAINT 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得到一个触发器之后被调用,但在RecipeToAromas得到它的一个触发器之前,


Using Check constrains

您只能在判断约束中使用shouldn't个子查询.


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

Javascript相关问答推荐

主要内部的ExtJS多个子应用程序

Redux工具包查询(RTKQ)端点无效并重新验证多次触发

嵌套异步JavaScript(微任务和macrotask队列)

如何用拉威尔惯性Vue依赖下拉?

我怎么才能得到Kotlin的密文?

覆盖TypeScrip中的Lit-Element子类的属性类型

对网格项目进行垂直排序不起作用

如何在Angular拖放组件中同步数组?

在css中放置所需 colored颜色 以填充图像的透明区域

向数组中的对象添加键而不改变原始变量

从逗号和破折号分隔的给定字符串中查找所有有效的星期几

为什么在函数中添加粒子的速率大于删除粒子的速率?

每次重新呈现时调用useState initialValue函数

输入的值的类型脚本array.排序()

Next.js无法从外部本地主机获取图像

如何在Reaction中设置缺省值, Select 下拉列表,动态追加剩余值?

当S点击按钮时,我如何才能改变它的样式?

找不到处于状态的联系人

使用JavaScript或PHP从div ID值创建锚标记和链接

JavaScript structuredClone在Chrome/Edge中获得了非法调用,但在NodeJS中没有