今天的要來介紹 Self Relations ,第一次聽到這名詞的你可能會覺得很酷, relation 除了一對一、一對多、多對多外,自己原來也可以 relation 自己,會有 Self Relations 的出現原因是,我們的業務需求中,對於 model 的使用上,會有同一個 model 但是不同解讀的方式,舉個例子以 teacher 跟 student 來說,他們的 model 都是屬於 User ,同時學生可能會有他的老師,相反老師可能也會有自己的學生,所以這個情形就很適合用 Self Relations 去描敘關聯的關係,Self Relations 的結構上,很像是一個上下層的概念去 chain 在一起, 很特別的是他可以用在 1-1、1-n、m-n 中,這邊我們將慢慢介紹~
這邊就是一個單純的一對一的關係,你會看到:
user 可能會有 0 個或一個前輩user 可能會有 0 個或一個繼任者這邊要注意的是,如果是 1-1 的 Self-Relations 兩邊的 relation 不能都是 required 因為這樣你會無法 create 第一筆 User ,1-1 必需要先有 user list 後,你才能用類似於 linklist 的方式,去指定該 User 的上下關係,並 connect 這位 User 的前輩是誰繼任者是誰
model User {
  id          Int     @id @default(autoincrement())
  name        String?
  successorId Int?    @unique
  successor   User?   @relation("BlogOwnerHistory", fields: [successorId], references: [id])
  predecessor User?   @relation("BlogOwnerHistory")
}
這邊有幾個小規則:
relation 必須要有相同的 name 例如這邊的 BlogOwnerHistory
relation 要完整描述,relation 的全部內容1-1 的寫法一樣,其中一邊要當作 foreign key 去關聯到哪個 User 這邊來說就是 successorId
同時被定義成 foreign key 的欄位,這邊來說就是 successorId ,必須要是 unique 的以確保 1-1 的唯一性
model User {
  id          Int     @id @default(autoincrement())
  name        String?
  successorId Int?    @unique
  successor   User?   @relation("BlogOwnerHistory", fields: [successorId], references: [id])
  predecessor User?   @relation("BlogOwnerHistory")
}
那因為是 1-1 所以你要反過來讓 predecessorId 當作 foreign key 也是 OK 一樣你需要確保 predecessorId 的 unique
model User {
  id            Int     @id @default(autoincrement())
  name          String?
  successor     User?   @relation("BlogOwnerHistory")
  predecessorId Int?    @unique
  predecessor   User?   @relation("BlogOwnerHistory", fields: [predecessorId], references: [id])
}
那不管你是 predecessorId 或是 successorId 當你的 foreign key ,在 prisma client 寫法都是一樣的~
const x = await prisma.user.create({
  data: {
    name: "Bob McBob",
    successor: {
      connect: {
        id: 2,
      },
    },
    predecessor: {
      connect: {
        id: 4,
      },
    },
  },
});
以下是 SQL 對於 1-1 Self Relation 的語法,跟 1-1 的 SQL 一樣的是需要對 successorId 加上 Unique 的 CONSTRAINT
CREATE TABLE "User" (
    id SERIAL PRIMARY KEY,
    "name" TEXT,
    "successorId" INTEGER
);
ALTER TABLE "User" ADD CONSTRAINT fk_successor_user FOREIGN KEY ("successorId") REFERENCES "User" (id);
ALTER TABLE "User" ADD CONSTRAINT successor_unique UNIQUE ("successorId");
這邊的 1-n 的關係會是
student 可能會有 0 個或一個 teacher
teacher 可能會有 0 個或多個 student
model User {
  id        Int     @id @default(autoincrement())
  name      String?
  teacherId Int?
  teacher   User?   @relation("TeacherStudents", fields: [teacherId], references: [id])
  students  User[]  @relation("TeacherStudents")
}
那跟 1-n 的 SQL 一樣,不需要在 teacherId 加上 unique ,因為多個學生,可能會給同一個 teacher 教到~
CREATE TABLE "User" (
    id SERIAL PRIMARY KEY,
    "name" TEXT,
    "teacherId" INTEGER
);
ALTER TABLE "User" ADD CONSTRAINT fk_teacherid_user FOREIGN KEY ("teacherId") REFERENCES "User" (id);
這邊的 m-n 的關係會是
user 可以被 followed 0 到多個 user
user 可能會去 follow 0 到多個 user
這邊跟 m-n 的寫法一樣都適用 implicit ,prisma 自動幫你管理中間表的部分
model User {
  id         Int     @id @default(autoincrement())
  name       String?
  followedBys User[]  @relation("UserFollows")
  followings  User[]  @relation("UserFollows")
}
那如果需要管理中間表的話可以用 explicit mode~
model User {
  id         Int     @id @default(autoincrement())
  name       String?
  followedBys Follows[]  @relation("followedBy")
  followings  Follows[]  @relation("following")
}
model Follows {
  followedBy   User @relation("followedBy", fields: [followedById], references: [id])
  followedById Int
  following    User @relation("following", fields: [followingId], references: [id])
  followingId  Int
  @@id([followingId, followedById])
}
對應的 SQL 語法如下
CREATE TABLE "User" (
    id integer DEFAULT nextval('"User_id_seq"'::regclass) PRIMARY KEY,
    name text
);
CREATE TABLE "_UserFollows" (
    "A" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE,
    "B" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE
);
在 prisma client 的寫法如下~你會發現不管是 implicit 或是 explicit 都跟之前的寫法一樣,只是變成兩個欄位 followedBys 跟 followings 去 connect
//implicit
const newUser = await prismaClient.user.create({
    data: {
      name: "Danny3.0",
      followedBys: {
        connect: {
          id: 1
        }
      },
      followings: {
        connect: [
          { id: 5 }, { id: 6 }, { id: 7 }
        ]
      }
    }
  })
//explicit
const newUser = await prismaClient.user.create({
    data: {
      name: "Danny3.0",
      followedBys: {
        create: [{
          followedBy: {
            connect: {
              id: 1
            }
          }
        }]
      },
      followings: {
        createMany: {
          data: [
            { followingId: 5 },
            { followingId: 6 },
            { followingId: 7 },
          ]
        }
      }
    }
  })
Self Relation 很特別的是可以同時寫 1-1、1-n、m-n 情況在同一個 model 中
model User {
  id         Int     @id @default(autoincrement())
  name       String?
  teacherId  Int?
  teacher    User?   @relation("TeacherStudents", fields: [teacherId], references: [id])
  students   User[]  @relation("TeacherStudents")
  followedBy User[]  @relation("UserFollows")
  following  User[]  @relation("UserFollows")
}
✅ 前端社群 :
https://lihi3.cc/kBe0Y