🐈

PrismaのSchemaのAttributesを理解する

に公開

Prisma で Schema を書こうとすると、以下のように@id@default@uniqueなどの attributes の記述が必要になってきます。初見だと「これって必須なの?」「付けないとどうなるの?」と感じるかと思います。

prisma
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  Int?
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

この記事では、Prisma の attributes の中でも絶対に押さえるべき 7 つの基本 attributesと、その必要性を実際のケースと共に分かりやすく解説します。

ちなみに公式ドキュメントには以下にまとまっています。

https://www.prisma.io/docs/orm/reference/prisma-schema-reference#attributes

Prisma の attributes は「フィールド用」と「モデル用」の 2 種類

まず最初に理解しておきたいのが、Prisma の attributes には 2 種類あることです。

フィールド attributes(@1 つ)

フィールド(列)に対して個別に設定する属性です。

prisma
model User {
  id    Int    @id @default(autoincrement())  // idフィールドに対する設定
  email String @unique                         // emailフィールドに対する設定
  name  String                                 // 設定なし
}

ブロック attributes(@@2 つ)

モデル全体や複数フィールドの組み合わせに対して設定する属性です。

prisma
model User {
  firstName String
  lastName  String
  email     String

  @@unique([firstName, lastName])  // 複数フィールドの組み合わせに対する設定
  @@index([email])                 // モデル全体に対する設定
}

絶対に覚えるべき 7 つの基本 attributes

@id - プライマリキーの定義

何をするもの: レコードを一意に識別するための「番号札」を作ります。

prisma
model User {
  id   Int    @id @default(autoincrement())
  name String
}

付けないとどうなる?

prisma
// ❌ @idを付けない場合
model User {
  name String
}

// 問題が発生:
// - どのレコードが誰なのか特定できない
// - findUnique()などの関数が使えない
// - 同じユーザーが重複する可能性

なぜ必須?: データベースでは各レコードを区別する仕組みが必要です。@idがないと Prisma がどのレコードを操作すべきか判断できません。

@default - デフォルト値の自動設定

何をするもの: 値を指定しない場合に自動的に設定される値を定義します。

prisma
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  published Boolean  @default(false)           // デフォルトは非公開
  createdAt DateTime @default(now())           // 作成時刻を自動設定
  views     Int      @default(0)               // 閲覧数の初期値は0
}

付けないとどうなる?

prisma
// ❌ @defaultを付けない場合
await prisma.post.create({
  data: {
    title: "新しい記事",
    published: false,     // 毎回手動で指定が必要
    createdAt: new Date(), // 毎回手動で指定が必要
    views: 0              // 毎回手動で指定が必要
  }
})

// ✅ @defaultがある場合
await prisma.post.create({
  data: {
    title: "新しい記事"  // その他は自動設定
  }
})

なぜ必要?: コードが簡潔になり、設定忘れを防げます。特に作成時刻や初期値は自動化すべきです。

@unique - 重複防止の制約

何をするもの: そのフィールドの値がテーブル全体で重複しないことを保証します。

prisma
model User {
  id       Int    @id @default(autoincrement())
  email    String @unique    // メールアドレスは重複不可
  username String @unique    // ユーザー名も重複不可
  name     String           // 名前は重複OK
}

付けないとどうなる?

prisma
// ❌ @uniqueを付けない場合
await prisma.user.create({ data: { email: "test@example.com" } })
await prisma.user.create({ data: { email: "test@example.com" } }) // 成功してしまう!

// 結果として:
// - 同じメールに複数の通知が送られる
// - ログイン時にどのユーザーか特定できない
// - データの整合性が崩れる

なぜ必要?: システムが正常に動作するためには、メールアドレスやユーザー名などの識別子は重複してはいけません。

@@unique - 複数フィールドの組み合わせで重複防止

何をするもの: 複数フィールドの組み合わせが重複しないことを保証します。

prisma
model Employee {
  id           Int    @id @default(autoincrement())
  departmentId Int
  employeeCode String
  name         String

  @@unique([departmentId, employeeCode])  // 部署内で社員コードは重複不可
}

付けないとどうなる?

prisma
// 同じ部署に同じ社員コードが複数存在する状態が発生
await prisma.employee.create({
  data: { departmentId: 1, employeeCode: "E001", name: "田中" }
})
await prisma.employee.create({
  data: { departmentId: 1, employeeCode: "E001", name: "佐藤" } // 成功してしまう!
})

なぜ必要?: 部署ごとの社員コードや、学生の履修情報など、複数の条件の組み合わせで一意性を保つ必要がある場合に使用します。

@@id - 複合主キーの定義

何をするもの: 複数のフィールドを組み合わせてプライマリキーとします。

prisma
model CourseEnrollment {
  studentId Int
  courseId  Int
  grade     String?

  @@id([studentId, courseId])  // 学生IDとコースIDの組み合わせが主キー
}

なぜ使う?: 中間テーブルなどで、単一フィールドだけでは意味のある主キーが作れない場合に使用します。「学生 A が数学を履修」という情報は 1 回だけ登録できるようになります。

@relation - テーブル間の関連定義

何をするもの: テーブル同士の関係を定義し、関連データの取得を可能にします。

prisma
model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int    // 外部キー
}

model User {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[] // 1人のユーザーは複数の投稿を持てる
}

付けないとどうなる?

prisma
// ❌ @relationを付けない場合
// - author.nameのような関連データが取得できない
// - 存在しないユーザーIDを設定できてしまう

// ✅ @relationがある場合
const post = await prisma.post.findFirst({
  include: { author: true }  // 投稿者の情報も一緒に取得
})
console.log(post.author.name)  // 投稿者の名前にアクセス可能

なぜ必要?: 関連データの取得やデータの整合性チェックに必須です。

@updatedAt - 更新時刻の自動記録

何をするもの: レコードが更新されるたびに、自動的に現在時刻を設定します。

prisma
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String
  createdAt DateTime @default(now())    // 作成時刻
  updatedAt DateTime @updatedAt         // 更新時刻(自動更新)
}

付けないとどうなる?

prisma
// ❌ @updatedAtを使わない場合
await prisma.post.update({
  where: { id: 1 },
  data: {
    title: "更新されたタイトル",
    updatedAt: new Date()  // 毎回手動設定(忘れがち)
  }
})

// ✅ @updatedAtがある場合
await prisma.post.update({
  where: { id: 1 },
  data: {
    title: "更新されたタイトル"  // updatedAtは自動更新
  }
})

なぜ必要?: 更新履歴の追跡に必須で、手動での管理は忘れがちなので自動化すべきです。

パフォーマンス向上のための 3 つの attributes

@@index - 検索高速化のインデックス

よく検索するフィールドに設定すると、検索が劇的に速くなります。

prisma
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  authorId  Int
  published Boolean

  @@index([authorId])                 // 著者での検索を高速化
  @@index([authorId, published])      // 著者と公開状態での検索を高速化
}

インデックスがあれば検索パフォーマンスをよくすることができます。

@map/@map - 既存データベースとの互換性

既存のデータベースの命名規則に合わせる際に使用します。

prisma
model User {
  firstName String @map("first_name")  // DBでは first_name という列名
  lastName  String @map("last_name")   // DBでは last_name という列名

  @@map("users")  // DBでは users というテーブル名
}

@ignore - セキュリティのためのフィールド除外

機密情報を Prisma Client から除外できます。

prisma
model User {
  id             Int    @id
  name           String
  hashedPassword String @ignore  // Prisma Clientからアクセス不可
}

まとめ

Prisma の attributes は、データベースの構造と動作を定義する重要な要素です。

最低限これだけは押さえておきましょう

  • @id - レコードの識別(必須)
  • @default - 自動値設定(効率化)
  • @unique - 重複防止(データの整合性)
  • @relation - テーブル間の関連(関連データの取得)

これらの attributes を適切に使用することで、堅牢で高速、かつ保守しやすいアプリケーションを構築できます。適切な場面で使い分けていきましょう!

Discussion