SQL ServerとMongoDBにおける参照整合性アクションの特別なルール
一部のデータベースには、参照整合性アクションを使用する場合に考慮すべき特定の要件があります。
-
Microsoft SQL Serverでは、リレーションチェーンが循環または複数のカスケードパスを引き起こす場合、外部キーに対するカスケード参照整合性アクションは許可されていません。外部キーの参照整合性アクションが `NO ACTION` (またはPrisma ORMが参照整合性を管理している場合は `NoAction` )以外に設定されている場合、サーバーは循環または複数のカスケードパスをチェックし、SQL実行時にエラーを返します。
-
MongoDBでは、Prisma ORMで参照整合性アクションを使用する場合、自己参照リレーションまたは3つのモデル間の循環を持つデータモデルでは、参照整合性アクションのエミュレーションが無限にループするのを防ぐために、参照整合性アクションを `NoAction` に設定する必要があります。デフォルトでは、MongoDBには `relationMode = "prisma"` モードが使用されており、これはPrisma ORMが参照整合性を管理していることを意味することに注意してください。
次のSQLを考えます。
CREATE TABLE [dbo].[Employee] (
[id] INT NOT NULL IDENTITY(1,1),
[managerId] INT,
CONSTRAINT [PK__Employee__id] PRIMARY KEY ([id])
);
ALTER TABLE [dbo].[Employee]
ADD CONSTRAINT [FK__Employee__managerId]
FOREIGN KEY ([managerId]) REFERENCES [dbo].[Employee]([id])
ON DELETE CASCADE ON UPDATE CASCADE;
このSQLを実行すると、データベースは次のエラーをスローします。
Introducing FOREIGN KEY constraint 'FK__Employee__managerId' on table 'Employee' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
より複雑なデータモデルでは、カスケードパスを見つけるのが複雑になることがあります。そのため、Prisma ORMでは、データモデルは、移行中に実行されるSQLを生成する*前*に検証され、パスの一部であるリレーションを強調表示します。これにより、これらのアクションチェーンを見つけて解除することが非常に容易になります。
自己リレーション(SQL ServerとMongoDB)
次のモデルは、`Employee` がマネージャーと部下を持ち、同じモデルのエントリを参照する自己リレーションを記述しています。
model Employee {
id Int @id @default(autoincrement())
manager Employee? @relation(name: "management", fields: [managerId], references: [id])
managees Employee[] @relation(name: "management")
managerId Int?
}
これは次のエラーになります。
Error parsing attribute "@relation": A self-relation must have `onDelete` and `onUpdate` referential actions set to `NoAction` in one of the @relation attributes. (Implicit default `onDelete`: `SetNull`, and `onUpdate`: `Cascade`)
アクションを定義しない場合、Prisma ORMは、基になるスカラーフィールドがオプションまたは必須に設定されているかに応じて、次のデフォルト値を使用します。
条項 | すべてのスカラーフィールドがオプション | 少なくとも1つのスカラーフィールドが必須 |
---|---|---|
onDelete | SetNull | NoAction |
onUpdate | Cascade | Cascade |
上記のリレーションにおける `onUpdate` のデフォルトの参照整合性アクションは `Cascade` であり、 `onDelete` は `SetNull` であるため、循環が発生し、解決策は `onUpdate` と `onDelete` の値を明示的に `NoAction` に設定することです。
model Employee {
id Int @id @default(autoincrement())
manager Employee @relation(name: "management", fields: [managerId], references: [id])
manager Employee @relation(name: "management", fields: [managerId], references: [id], onDelete: NoAction, onUpdate: NoAction)
managees Employee[] @relation(name: "management")
managerId Int
}
3つのテーブル間の循環リレーション(SQL ServerとMongoDB)
次のモデルは、`Chicken`、`Egg`、`Fox` の間の循環リレーションを記述しており、各モデルが互いを参照しています。
model Chicken {
id Int @id @default(autoincrement())
egg Egg @relation(fields: [eggId], references: [id])
eggId Int
predators Fox[]
}
model Egg {
id Int @id @default(autoincrement())
predator Fox @relation(fields: [predatorId], references: [id])
predatorId Int
parents Chicken[]
}
model Fox {
id Int @id @default(autoincrement())
meal Chicken @relation(fields: [mealId], references: [id])
mealId Int
foodStore Egg[]
}
これは、循環の一部であるすべてのリレーションフィールドで3つの検証エラーになります。
最初の1つは、`Chicken` モデルの `egg` リレーションにあります。
Error parsing attribute "@relation": Reference causes a cycle. One of the @relation attributes in this cycle must have `onDelete` and `onUpdate` referential actions set to `NoAction`. Cycle path: Chicken.egg → Egg.predator → Fox.meal. (Implicit default `onUpdate`: `Cascade`)
2番目の1つは、`Egg` モデルの `predator` リレーションにあります。
Error parsing attribute "@relation": Reference causes a cycle. One of the @relation attributes in this cycle must have `onDelete` and `onUpdate` referential actions set to `NoAction`. Cycle path: Egg.predator → Fox.meal → Chicken.egg. (Implicit default `onUpdate`: `Cascade`)
そして3番目の1つは、`Fox` モデルの `meal` リレーションにあります。
Error parsing attribute "@relation": Reference causes a cycle. One of the @relation attributes in this cycle must have `onDelete` and `onUpdate` referential actions set to `NoAction`. Cycle path: Fox.meal → Chicken.egg → Egg.predator. (Implicit default `onUpdate`: `Cascade`)
リレーションフィールドは必須であるため、 `onDelete` のデフォルトの参照整合性アクションは `NoAction` ですが、 `onUpdate` は `Cascade` であり、参照整合性アクションの循環を引き起こします。解決策は、いずれかのリレーションで `onUpdate` の値を `NoAction` に設定することです。
model Chicken {
id Int @id @default(autoincrement())
egg Egg @relation(fields: [eggId], references: [id])
egg Egg @relation(fields: [eggId], references: [id], onUpdate: NoAction)
eggId Int
predators Fox[]
}
または
model Egg {
id Int @id @default(autoincrement())
predator Fox @relation(fields: [predatorId], references: [id])
predator Fox @relation(fields: [predatorId], references: [id], onUpdate: NoAction)
predatorId Int
parents Chicken[]
}
または
model Fox {
id Int @id @default(autoincrement())
meal Chicken @relation(fields: [mealId], references: [id])
meal Chicken @relation(fields: [mealId], references: [id], onUpdate: NoAction)
mealId Int
foodStore Egg[]
}
2つのモデル間の複数のカスケードパス(SQL Serverのみ)
このデータモデルは、同じモデル間の2つの異なるパスを記述しており、両方のリレーションがカスケード参照整合性アクションをトリガーしています。
model User {
id Int @id @default(autoincrement())
comments Comment[]
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
authorId Int
author User @relation(fields: [authorId], references: [id])
comments Comment[]
}
model Comment {
id Int @id @default(autoincrement())
writtenById Int
postId Int
writtenBy User @relation(fields: [writtenById], references: [id])
post Post @relation(fields: [postId], references: [id])
}
このデータモデルの問題は、 `Comment` から `User` への2つのパスがあり、両方のリレーションのデフォルトの `onUpdate` アクションが `Cascade` であることです。これにより、2つの検証エラーが発生します。
最初の1つは、 `writtenBy` リレーションにあります。
Error parsing attribute "@relation": When any of the records in model `User` is updated or deleted, the referential actions on the relations cascade to model `Comment` through multiple paths. Please break one of these paths by setting the `onUpdate` and `onDelete` to `NoAction`. (Implicit default `onUpdate`: `Cascade`)
2番目の1つは、 `post` リレーションにあります。
Error parsing attribute "@relation": When any of the records in model `User` is updated or deleted, the referential actions on the relations cascade to model `Comment` through multiple paths. Please break one of these paths by setting the `onUpdate` and `onDelete` to `NoAction`. (Implicit default `onUpdate`: `Cascade`)
このエラーは、 `User` モデルのレコードの主キーを更新すると、更新が `writtenBy` リレーションを介して `Comment` と `User` の間で1回カスケードし、 `Post` が `Comment` モデルに関連付けられているため、 `post` リレーションから `Post` モデルを介して再度カスケードすることを意味します。
修正方法は、 `writtenBy` または `post` リレーションフィールド、または `author` リレーションのアクションを変更することにより、 `Post` モデルから `onUpdate` 参照整合性アクションを `NoAction` に設定することです。
model Comment {
id Int @id @default(autoincrement())
writtenById Int
postId Int
writtenBy User @relation(fields: [writtenById], references: [id])
writtenBy User @relation(fields: [writtenById], references: [id], onUpdate: NoAction)
post Post @relation(fields: [postId], references: [id])
}
または
model Comment {
id Int @id @default(autoincrement())
writtenById Int
postId Int
writtenBy User @relation(fields: [writtenById], references: [id])
post Post @relation(fields: [postId], references: [id])
post Post @relation(fields: [postId], references: [id], onUpdate: NoAction)
}
または
model Post {
id Int @id @default(autoincrement())
authorId Int
author User @relation(fields: [authorId], references: [id])
author User @relation(fields: [authorId], references: [id], onUpdate: NoAction)
comments Comment[]
}