メインコンテンツにスキップ

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つのスカラーフィールドが必須
onDeleteSetNullNoAction
onUpdateCascadeCascade

上記の参照において、onUpdateのデフォルトの参照アクションがCascadeであり、onDeleteSetNullであるため、サイクルが作成されます。解決策は、onUpdateonDeleteの値を明示的に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)

次のモデルは、ChickenEggFox間の循環リレーションを記述しており、各モデルが互いを参照しています。

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ですが、onUpdateCascadeであるため、参照アクションサイクルが発生します。解決策は、いずれかのリレーションで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リレーションを介してCommentUserの間で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[]
}
© . All rights reserved.