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つ目は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つ目は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つ目は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
リレーションフィールドのonUpdate
参照アクションをNoAction
に設定するか、Post
モデルからauthor
リレーションのアクションを変更することです。
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[]
}