สคีมา การค้นหา และการกลายพันธุ์ของ Data Connect

Firebase Data Connect ช่วยให้คุณสร้างเครื่องมือเชื่อมต่อสำหรับอินสแตนซ์ PostgreSQL ที่จัดการด้วย Google Cloud SQL ได้ เครื่องมือเชื่อมต่อเหล่านี้เป็นการผสมผสานระหว่างสคีมา การค้นหา และการเปลี่ยนแปลงสำหรับการใช้ข้อมูล

คู่มือเริ่มต้นใช้งานได้แนะนำสคีมาแอปอีเมลสำหรับ PostgreSQL แต่คู่มือนี้จะเจาะลึกวิธีออกแบบสคีมา Data Connect สำหรับ PostgreSQL โดยใช้ฐานข้อมูลรีวิวภาพยนตร์เป็นตัวอย่างที่กระตุ้นให้เกิดการเรียนรู้

คู่มือนี้จะจับคู่Data Connectการค้นหาและการเปลี่ยนแปลงกับตัวอย่างสคีมา เหตุใดจึงต้องพูดถึงการค้นหา (และการเปลี่ยนแปลง) ในคำแนะนำเกี่ยวกับ Data Connectสคีมา เช่นเดียวกับแพลตฟอร์มอื่นๆ ที่อิงตาม GraphQL Firebase Data Connect เป็นแพลตฟอร์มการพัฒนาแบบคําค้นหาก่อน ดังนั้นในฐานะนักพัฒนาซอฟต์แวร์ ในการสร้างโมเดลข้อมูล คุณจะต้องคิดถึงข้อมูลที่ไคลเอ็นต์ต้องการ ซึ่งจะมีอิทธิพลอย่างมากต่อสคีมาข้อมูลที่คุณพัฒนาสําหรับโปรเจ็กต์

คู่มือนี้เริ่มต้นด้วยสคีมาใหม่สำหรับรีวิวภาพยนตร์ จากนั้นจะครอบคลุมการค้นหาและการเปลี่ยนแปลง ที่ได้มาจากสคีมานั้น และสุดท้ายจะแสดงรายการ SQL ที่เทียบเท่ากับสคีมาหลักของ Data Connect

สคีมาสำหรับแอปรีวิวภาพยนตร์

สมมติว่าคุณต้องการสร้างบริการที่ให้ผู้ใช้ส่งและดูรีวิวภาพยนตร์

คุณต้องมีสคีมาเริ่มต้นสำหรับแอปดังกล่าว และจะขยายสคีมานี้ในภายหลัง เพื่อสร้างการค้นหาเชิงสัมพันธ์ที่ซับซ้อน

ตารางภาพยนตร์

สคีมาสำหรับภาพยนตร์มีคำสั่งหลักๆ เช่น

  • @table ซึ่งช่วยให้เราตั้งชื่อการดำเนินการได้โดยใช้อาร์กิวเมนต์ singular และ plural
  • @col เพื่อตั้งชื่อคอลัมน์อย่างชัดเจน
  • @default เพื่ออนุญาตให้ตั้งค่าเริ่มต้น
# Movies
type Movie
  @table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
  id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
  title: String!
  releaseYear: Int @col(name: "release_year")
  genre: String
  rating: Int @col(name: "rating")
  description: String @col(name: "description")
}

ค่าของเซิร์ฟเวอร์และสเกลาร์คีย์

ก่อนที่จะดูแอปรีวิวภาพยนตร์ เรามาทำความรู้จักData Connect ค่าเซิร์ฟเวอร์และสเกลาร์คีย์กันก่อน

การใช้ค่าเซิร์ฟเวอร์ช่วยให้เซิร์ฟเวอร์เติมข้อมูลแบบไดนามิกลงใน ฟิลด์ในตารางได้อย่างมีประสิทธิภาพโดยใช้ค่าที่จัดเก็บไว้หรือค่าที่คำนวณได้ง่ายตาม นิพจน์ฝั่งเซิร์ฟเวอร์ที่เฉพาะเจาะจง เช่น คุณสามารถกำหนดฟิลด์ที่มีการใช้ การประทับเวลาเมื่อมีการเข้าถึงฟิลด์โดยใช้นิพจน์ updatedAt: Timestamp! @default(expr: "request.time")

สเกลาร์คีย์คือตัวระบุออบเจ็กต์แบบย่อที่ Data Connect สร้างขึ้นโดยอัตโนมัติ จากฟิลด์คีย์ในสคีมา สเกลาร์หลักเกี่ยวข้องกับประสิทธิภาพ ซึ่งช่วยให้คุณค้นหาข้อมูลเกี่ยวกับตัวตนและ โครงสร้างของข้อมูลได้ในการเรียกครั้งเดียว โดยเฉพาะอย่างยิ่งเมื่อคุณต้องการดำเนินการตามลำดับกับระเบียนใหม่และต้องการตัวระบุที่ไม่ซ้ำกันเพื่อส่งไปยังการดำเนินการที่จะเกิดขึ้น รวมถึงเมื่อคุณต้องการเข้าถึงคีย์เชิงสัมพันธ์เพื่อดำเนินการที่ซับซ้อนเพิ่มเติม

ประเภทรหัส

ใน GraphQL ระบบจะกำหนดประเภท ID เป็นประเภททึบแสงที่แปลงเป็นอนุกรมเป็น สตริง GraphQL ไม่สนใจรูปแบบรหัส แต่จะบังคับให้สตริงและจำนวนเต็ม จากอินพุต

โดยปกติแล้วคีย์ PostgreSQL จะเป็นจำนวนเต็มหรือ UUID ไม่ใช่สตริง Data Connect จะสร้างคีย์ดังกล่าวจากสคีมาของคุณโดยอัตโนมัติ คุณ ปรับแต่งการสร้างคีย์ด้วยคำสั่ง @default ได้ตามที่แสดงในคำจำกัดความฟิลด์ id ของตาราง Actorid: ID! … @default(generate: "UUID")

ตารางข้อมูลเมตาของภาพยนตร์

ตอนนี้มาติดตามผู้กำกับภาพยนตร์ รวมถึงตั้งค่าความสัมพันธ์แบบหนึ่งต่อหนึ่งกับ Movie กัน

เพิ่มคำสั่ง @ref เพื่อกำหนดความสัมพันธ์

# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
  @table(
    name: "MovieMetadata"
  ) {
  # @ref creates a field in the current table (MovieMetadata) that holds the
  # primary key of the referenced type
  # In this case, @ref(fields: "id") is implied
  movie: Movie! @ref
  # movieId: UUID <- this is created by the above @ref
  director: String @col(name: "director")
}

Actor และ MovieActor

จากนั้นคุณต้องการให้นักแสดงนำแสดงในภาพยนตร์ และเนื่องจากคุณมีความสัมพันธ์แบบหลายต่อหลาย ระหว่างภาพยนตร์กับนักแสดง ให้สร้างตารางรวม

# Actors
# Suppose an actor can participate in multiple movies and movies can have multiple actors
# Movie - Actors (or vice versa) is a many to many relationship
type Actor @table(name: "Actors", singular: "actor", plural: "actors") {
  id: UUID! @col(name: "actor_id") @default(expr: "uuidV4()")
  name: String! @col(name: "name", dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary key(s) of this table
# In this case, the keys are [movieId, actorId], the generated fields of the reference types [movie, actor]

type MovieActor @table(key: ["movie", "actor"]) {
  # @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
  # In this case, @ref(fields: "id") is implied
  movie: Movie! @ref
  # movieId: UUID! <- this is created by the above @ref, see: implicit.gql
  actor: Actor! @ref
  # actorId: UUID! <- this is created by the above @ref, see: implicit.gql
  role: String! @col(name: "role") # "main" or "supporting"
  # optional other fields
}

ผู้ใช้

สุดท้ายคือผู้ใช้แอป

# Users
# Suppose a user can leave reviews for movies
# user:reviews is a one to many relationship, movie:reviews is a one to many relationship, movie:user is a many to many relationship
type User
  @table(name: "Users", singular: "user", plural: "users", key: ["id"]) {
  id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
  auth: String @col(name: "user_auth") @default(expr: "auth.uid")
  username: String! @col(name: "username", dataType: "varchar(30)")
  # The following are generated from the @ref in the Review table
  # reviews_on_user
  # movies_via_Review
}

ประเภทข้อมูลที่รองรับ

Data Connect รองรับประเภทข้อมูลสเกลาร์ต่อไปนี้ โดยมีการ กำหนดให้กับประเภท PostgreSQL โดยใช้ @col(dataType:)

Data Connect ประเภท ประเภทในตัวของ GraphQL หรือ
Data Connect ประเภทที่กำหนดเอง
ประเภท PostgreSQL เริ่มต้น ประเภท PostgreSQL ที่รองรับ
(ชื่อแทนในวงเล็บ)
สตริง GraphQL ข้อความ text
bit(n), varbit(n)
char(n), varchar(n)
Int GraphQL int Int2 (smallint, smallserial),
int4 (integer, int, serial)
ทศนิยม GraphQL float8 float4 (real)
float8 (double precision)
numeric (decimal)
บูลีน GraphQL boolean boolean
UUID กำหนดเอง uuid UUID
Int64 กำหนดเอง bigint int8 (bigint, bigserial)
numeric (decimal)
วันที่ กำหนดเอง วันที่ วันที่
การประทับเวลา กำหนดเอง timestamptz

timestamptz

หมายเหตุ: ระบบจะไม่จัดเก็บข้อมูลเขตเวลาท้องถิ่น
PostgreSQL จะแปลงและจัดเก็บการประทับเวลาดังกล่าวเป็น UTC

การแจงนับ กำหนดเอง enum

enum

Vector กำหนดเอง vector

เวกเตอร์

ดูทำการค้นหาความคล้ายคลึงของเวกเตอร์ด้วย Vertex AI

  • GraphQL List จะแมปกับอาร์เรย์แบบ 1 มิติ
    • เช่น [Int] แมปกับ int5[], [Any] แมปกับ jsonb[]
    • Data Connect ไม่รองรับอาร์เรย์แบบซ้อน

การค้นหาและการเปลี่ยนแปลงโดยนัยและที่กำหนดไว้ล่วงหน้า

Data Connect คำค้นหาและการเปลี่ยนแปลงจะขยายชุดคำค้นหาโดยนัย และการเปลี่ยนแปลงโดยนัยที่ Data Connect สร้างขึ้นโดยอิงตาม ประเภทและความสัมพันธ์ของประเภทในสคีมา เครื่องมือในเครื่องจะสร้างการค้นหาและการเปลี่ยนแปลงโดยนัยทุกครั้งที่คุณแก้ไขสคีมา

ในกระบวนการพัฒนา คุณจะใช้คําค้นหาที่กําหนดไว้ล่วงหน้าและการเปลี่ยนแปลงที่กําหนดไว้ล่วงหน้าตามการดําเนินการโดยนัยเหล่านี้

การตั้งชื่อการค้นหาและการเปลี่ยนแปลงโดยนัย

Data Connect อนุมานชื่อที่เหมาะสมสำหรับการค้นหาและการเปลี่ยนแปลงโดยนัย จากการประกาศประเภทสคีมา เช่น เมื่อทำงานกับแหล่งข้อมูล PostgreSQL หากคุณกำหนดตารางชื่อ Movie เซิร์ฟเวอร์จะสร้างตารางต่อไปนี้โดยนัย

  • การค้นหาสำหรับกรณีการใช้งานตารางเดียวที่มีชื่อที่ใช้งานง่าย movie (เอกพจน์ สำหรับการดึงผลลัพธ์แต่ละรายการโดยส่งอาร์กิวเมนต์ เช่น eq) และ movies (พหูพจน์ สำหรับการดึงรายการผลลัพธ์โดยส่งอาร์กิวเมนต์ เช่น gt และการดำเนินการ เช่น orderby) Data Connect ยังสร้างการค้นหาสำหรับการดำเนินการแบบหลายตาราง เชิงสัมพันธ์ที่มีชื่อที่ชัดเจน เช่น actors_on_movies หรือ actors_via_actormovie
  • การเปลี่ยนแปลงชื่อ movie_insert, movie_upsert...

ภาษาคำจำกัดความของสคีมายังช่วยให้คุณตั้งชื่อการดำเนินการอย่างชัดเจนได้โดยใช้singularและpluralอาร์กิวเมนต์ของคำสั่ง

การค้นหาฐานข้อมูลรีวิวภาพยนตร์

คุณกำหนดData Connectคําค้นหาด้วยการประกาศประเภทการดำเนินการของคําค้นหา ชื่อการดำเนินการ อาร์กิวเมนต์การดำเนินการตั้งแต่ 0 รายการขึ้นไป และ คําสั่งตั้งแต่ 0 รายการขึ้นไปพร้อมอาร์กิวเมนต์

ในคู่มือเริ่มต้น คำค้นหา listEmails ตัวอย่างไม่มีพารามิเตอร์ แน่นอนว่าในหลายกรณี ข้อมูลที่ส่งไปยังฟิลด์การค้นหาจะเป็นแบบไดนามิก คุณสามารถใช้ไวยากรณ์ $variableName เพื่อทำงานกับตัวแปรเป็นส่วนประกอบหนึ่งของ คำจำกัดความการค้นหา

ดังนั้นการค้นหาต่อไปนี้จึงมี

  • query คำจำกัดความของประเภท
  • ชื่อListMoviesByGenreการดำเนินการ (คำค้นหา)
  • อาร์กิวเมนต์การดำเนินการตัวแปรเดียว $genre
  • คำสั่งเดียว @auth
query ListMoviesByGenre($genre: String!) @auth(level: USER)

อาร์กิวเมนต์การค้นหาทุกรายการต้องมีการประกาศประเภท ซึ่งเป็นประเภทในตัว เช่น String หรือประเภทที่กำหนดเองตามสคีมา เช่น Movie

มาดูรูปแบบของคำค้นหาที่ซับซ้อนขึ้นเรื่อยๆ กัน คุณจะปิดท้ายด้วยการ แนะนําการแสดงความสัมพันธ์ที่กระชับและมีประสิทธิภาพซึ่งมีอยู่ในคําค้นหา โดยนัยที่คุณสามารถสร้างต่อในคําค้นหาที่กําหนดไว้ล่วงหน้า

สเกลาร์หลักในคำค้นหา

แต่ก่อนอื่น มาดูเรื่องสเกลาร์หลักกันก่อน

Data Connect กำหนดประเภทพิเศษสำหรับสเกลาร์คีย์ ซึ่งระบุโดย _Key เช่น ประเภทของสเกลาร์คีย์สำหรับตาราง Movie ของเราคือ Movie_Key

คุณดึงข้อมูลสเกลาร์คีย์เป็นคำตอบที่การเปลี่ยนแปลงโดยนัยส่วนใหญ่ส่งคืน หรือ แน่นอนว่าจากคําค้นหาที่คุณดึงข้อมูลฟิลด์ทั้งหมดที่จําเป็นต่อการสร้าง คีย์สเกลาร์

การค้นหาอัตโนมัติแบบเอกพจน์ เช่น movie ในตัวอย่างที่กำลังใช้งาน รองรับอาร์กิวเมนต์คีย์ ที่ยอมรับสเกลาร์คีย์

คุณอาจส่งสเกลาร์คีย์เป็นค่าคงที่ แต่คุณสามารถกำหนดตัวแปรเพื่อส่งสเกลาร์ของพาสคีย์เป็นอินพุตได้

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}

โดยระบุใน JSON ของคำขอได้ดังนี้ (หรือรูปแบบการเรียงอันดับอื่นๆ)

{
  # 
  "variables": {
    "myKey": {"foo": "some-string-value", "bar": 42}
  }
}

การแยกวิเคราะห์สเกลาร์ที่กำหนดเองช่วยให้สร้าง Movie_Key โดยใช้ไวยากรณ์ออบเจ็กต์ซึ่งอาจมีตัวแปรได้ด้วย ซึ่งจะมีประโยชน์มากที่สุดเมื่อคุณต้องการ แยกคอมโพเนนต์แต่ละรายการออกเป็นตัวแปรต่างๆ ด้วยเหตุผลบางประการ

การใช้นามแฝงในการค้นหา

Data Connect รองรับการใช้นามแฝง GraphQL ในการค้นหา นามแฝงช่วยให้คุณ เปลี่ยนชื่อข้อมูลที่แสดงในผลลัพธ์ของคําค้นหาได้ คำค้นหาเดียวData Connectสามารถใช้ตัวกรองหลายตัวหรือการดำเนินการค้นหาอื่นๆ ในคำขอที่มีประสิทธิภาพเพียงคำขอเดียวไปยังเซิร์ฟเวอร์ ซึ่งเป็นการออก "คำค้นหาย่อย" หลายรายการพร้อมกันอย่างมีประสิทธิภาพ หากต้องการหลีกเลี่ยงการตั้งชื่อที่ซ้ำกันในชุดข้อมูลที่แสดงผล คุณ ใช้ชื่อแทนเพื่อแยกความแตกต่างของคําค้นหาย่อยได้

นี่คือการค้นหาที่นิพจน์ใช้นามแฝง mostPopular

query ReviewTopPopularity($genre: String) {
  mostPopular: review(first: {
    where: {genre: {eq: $genre}},
    orderBy: {popularity: DESC}
  }) {  }
}

การค้นหาแบบง่ายด้วยตัวกรอง

Data Connect การค้นหาจะแมปกับตัวกรอง SQL ทั่วไปและการเรียงลำดับ การดำเนินการทั้งหมด

โอเปอเรเตอร์ where และ orderBy (คำค้นหาเอกพจน์ พหูพจน์)

แสดงแถวที่ตรงกันทั้งหมดจากตาราง (และการเชื่อมโยงที่ซ้อนกัน) แสดงผลอาร์เรย์ว่างหากไม่มีระเบียนที่ตรงกับตัวกรอง

query MovieByTopRating($genre: String) {
  mostPopular: movies(
     where: { genre: { eq: $genre } }, orderBy: { rating: DESC }
  ) {
    # graphql: list the fields from the results to return
    id
    title
    genre
    description
  }
}

query MoviesByReleaseYear($min: Int, $max: Int) {
  movies(where: {releaseYear: {le: $max, ge: $min}}, orderBy: [{releaseYear: ASC}]) {  }
}

โอเปอเรเตอร์ limit และ offset (คำค้นหาเอกพจน์ พหูพจน์)

คุณสามารถแบ่งหน้าผลการค้นหาได้ ระบบยอมรับอาร์กิวเมนต์เหล่านี้ แต่จะไม่แสดงในผลลัพธ์

query MoviesTop10 {
  movies(orderBy: [{ rating: DESC }], limit: 10) {
    # graphql: list the fields from the results to return
    title
  }
}

รวมถึงช่องอาร์เรย์

คุณทดสอบได้ว่าฟิลด์อาร์เรย์มีรายการที่ระบุหรือไม่

# Filter using arrays and embedded fields.
query ListMoviesByTag($tag: String!) {
  movies(where: { tags: { includes: $tag }}) {
    # graphql: list the fields from the results to return
    id
    title
  }
}

การดำเนินการกับสตริงและนิพจน์ทั่วไป

การค้นหาของคุณสามารถใช้การค้นหาและการเปรียบเทียบสตริงทั่วไป รวมถึง นิพจน์ทั่วไป โปรดทราบว่าเพื่อประสิทธิภาพ คุณกำลังรวมการดำเนินการหลายอย่าง ไว้ที่นี่และแยกความแตกต่างด้วยชื่อแทน

query MoviesTitleSearch($prefix: String, $suffix: String, $contained: String, $regex: String) {
  prefixed: movies(where: {title: {startsWith: $prefix}}) {...}
  suffixed: movies(where: {title: {endsWith: $suffix}}) {...}
  contained: movies(where: {title: {contains: $contained}}) {...}
  matchRegex: movies(where: {title: {pattern: {regex: $regex}}}) {...}
}

or และ and สำหรับตัวกรองที่ประกอบขึ้น

ใช้ or และ and สำหรับตรรกะที่ซับซ้อนมากขึ้น

query ListMoviesByGenreAndGenre($minRating: Int!, $genre: String) {
  movies(
    where: { _or: [{ rating: { ge: $minRating } }, { genre: { eq: $genre } }] }
  ) {
    # graphql: list the fields from the results to return
    title
  }
}

การค้นหาที่ซับซ้อน

Data Connect สามารถเข้าถึงข้อมูลตามความสัมพันธ์ระหว่างตารางได้ คุณสามารถใช้ความสัมพันธ์แบบออบเจ็กต์ (แบบหนึ่งต่อหนึ่ง) หรืออาร์เรย์ (แบบหนึ่งต่อหลายรายการ) ที่กำหนดไว้ในสคีมาเพื่อทำการค้นหาที่ซ้อนกันได้ กล่าวคือ ดึงข้อมูลประเภทหนึ่ง พร้อมกับข้อมูลจากประเภทที่ซ้อนกันหรือเกี่ยวข้อง

การค้นหาดังกล่าวใช้ไวยากรณ์ Data Connect _on_ และ _via ใน การค้นหาโดยนัยที่สร้างขึ้น

คุณจะทำการแก้ไขสคีมาจากเวอร์ชันเริ่มต้นของเรา

หลายต่อหนึ่ง

มาเพิ่มรีวิวลงในแอปด้วยReviewตารางและแก้ไขUserกัน

# Users
# Suppose a user can leave reviews for movies
# user:reviews is a one to many relationship,
# movie:reviews is a one to many relationship,
# movie:user is a many to many relationship
type User
  @table(name: "Users", singular: "user", plural: "users", key: ["id"]) {
  id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
  auth: String @col(name: "user_auth") @default(expr: "auth.uid")
  username: String! @col(name: "username", dataType: "varchar(30)")
  # The following are generated from the @ref in the Review table
  # reviews_on_user
  # movies_via_Review
}
# Reviews
type Review @table(name: "Reviews", key: ["movie", "user"]) {
  id: UUID! @col(name: "review_id") @default(expr: "uuidV4()")
  user: User! @ref
  movie: Movie! @ref
  rating: Int
  reviewText: String
  reviewDate: Date! @default(expr: "request.time")
}

การค้นหาแบบหลายต่อหนึ่ง

ตอนนี้มาดูการค้นหาที่มีการกำหนดนามแฝงเพื่อแสดงไวยากรณ์ _via_

query UserMoviePreferences($username: String!) @auth(level: USER) {
  users(where: { username: { eq: $username } }) {
    likedMovies: movies_via_review(where: { rating: { ge: 4 } }) {
      title
      genre
      description
    }
    dislikedMovies: movies_via_review(where: { rating: { le: 2 } }) {
      title
      genre
      description
    }
  }
}

แบบตัวต่อตัว

คุณจะเห็นรูปแบบ ด้านล่างนี้คือสคีมาที่แก้ไขแล้วเพื่อเป็นตัวอย่าง

# Movies
type Movie
  @table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
  id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
  title: String!
  releaseYear: Int @col(name: "release_year")
  genre: String
  rating: Int @col(name: "rating")
  description: String @col(name: "description")
  tags: [String] @col(name: "tags")
}
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
  @table(
    name: "MovieMetadata"
  ) {
  # @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
  # In this case, @ref(fields: "id") is implied
  movie: Movie! @ref
  # movieId: UUID <- this is created by the above @ref
  director: String @col(name: "director")
}


extend type MovieMetadata {
  movieId: UUID! # matches primary key of referenced type
...
}

extend type Movie {
  movieMetadata: MovieMetadata # can only be non-nullable on ref side
  # conflict-free name, always generated
  movieMetadatas_on_movie: MovieMetadata
}

ค้นหาแบบตัวต่อตัว

คุณสามารถค้นหาโดยใช้ไวยากรณ์ _on_

# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    movieMetadatas_on_movie {
      director
    }
  }
}

หลายต่อหลาย

ภาพยนตร์ต้องมีนักแสดง และนักแสดงก็ต้องมีภาพยนตร์ มีความสัมพันธ์แบบหลายต่อหลายที่คุณสร้างแบบจำลองได้ด้วยMovieActorsตารางการเข้าร่วม

# MovieActors Join Table Definition
type MovieActors @table(
  key: ["movie", "actor"] # join key triggers many-to-many generation
) {
  movie: Movie!
  actor: Actor!
}

# generated extensions for the MovieActors join table
extend type MovieActors {
  movieId: UUID!
  actorId: UUID!
}

# Extensions for Actor and Movie to handle many-to-many relationships
extend type Movie {
  movieActors: [MovieActors!]! # standard many-to-one relation to join table
  actors: [Actor!]! # many-to-many via join table

  movieActors_on_actor: [MovieActors!]!
  # since MovieActors joins distinct types, type name alone is sufficiently precise
  actors_via_MovieActors: [Actor!]!
}

extend type Actor {
  movieActors: [MovieActors!]! # standard many-to-one relation to join table
  movies: [Movie!]! # many-to-many via join table

  movieActors_on_movie: [MovieActors!]!
  movies_via_MovieActors: [Movie!]!
}

ค้นหาความสัมพันธ์แบบหลายต่อหลาย

มาดูการค้นหาที่มีการกำหนดชื่อแทนเพื่อแสดงไวยากรณ์ _via_ กัน

query GetMovieCast($movieId: UUID!, $actorId: UUID!) @auth(level: PUBLIC) {
  movie(id: $movieId) {
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      name
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      name
    }
  }
  actor(id: $actorId) {
    mainRoles: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      title
    }
    supportingRoles: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      title
    }
  }
}

การเปลี่ยนแปลงสำหรับฐานข้อมูลรีวิวภาพยนตร์

ดังที่กล่าวไว้ เมื่อคุณกําหนดตารางในสคีมา Data Connect จะสร้างการเปลี่ยนแปลงโดยนัยพื้นฐานสําหรับแต่ละตาราง

type Movie @table { ... }

extend type Mutation {
  # Insert a row into the movie table.
  movie_insert(...): Movie_Key!
  # Upsert a row into movie."
  movie_upsert(...): Movie_Key!
  # Update a row in Movie. Returns null if a row with the specified id/key does not exist
  movie_update(...): Movie_Key
  # Update rows based on a filter in Movie.
  movie_updateMany(...): Int!
  # Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
  movie_delete(...): Movie_Key
  # Delete rows based on a filter in Movie.
  movie_deleteMany(...): Int!
}

ซึ่งจะช่วยให้คุณใช้กรณี CRUD หลักที่ซับซ้อนมากขึ้นได้ พูดคำนี้ 5 ครั้งเร็วๆ

สร้าง

มาสร้างพื้นฐานกัน

# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
  movie_insert(data: {
    title: $title
    releaseYear: $releaseYear
    genre: $genre
    rating: $rating
  })
}

# Create a movie with default values
mutation CreateMovie2 {
  movie_insert(data: {
    title: "Sherlock Holmes"
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
  })
}

หรือการอัปเดต/แทรก

# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
  movie_upsert(data: {
    title: $title
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
    genre: "Mystery/Thriller"
  })
}

อัปเดต

ข้อมูลอัปเดตมีดังนี้ โปรดิวเซอร์และผู้กำกับหวังเป็นอย่างยิ่งว่าเรตติ้งเฉลี่ยเหล่านั้นจะเพิ่มขึ้น

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id, data: {
    genre: $genre
    rating: $rating
    description: $description
  })
}

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $ratingIncrement: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    update: { rating: { inc: $ratingIncrement } }
  )
}

ดำเนินการลบ

แน่นอนว่าคุณลบข้อมูลภาพยนตร์ได้ นักอนุรักษ์ภาพยนตร์คงอยากให้เก็บรักษาฟิล์มไว้ให้นานที่สุด

# Delete by key
mutation DeleteMovie($id: UUID!) {
  movie_delete(id: $id)
}

คุณใช้ _deleteMany ได้ที่นี่

# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
  movie_deleteMany(where: { rating: { le: $minRating } })
}

เขียนการกลายพันธุ์ในความสัมพันธ์

ดูวิธีใช้การเปลี่ยนแปลงโดยนัย _upsert ในความสัมพันธ์

# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
  movieMetadata_upsert(
    data: { movie: { id: $movieId }, director: $director }
  )
}

สคีมา SQL ที่เทียบเท่า

-- Movies Table
CREATE TABLE Movies (
    movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    release_year INT,
    genre VARCHAR(30),
    rating INT,
    description TEXT,
    tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
    movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
    director VARCHAR(255) NOT NULL,
    PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
    actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
    movie_id UUID REFERENCES Movies(movie_id),
    actor_id UUID REFERENCES Actors(actor_id),
    role VARCHAR(50) NOT NULL, # "main" or "supporting"
    PRIMARY KEY (movie_id, actor_id),
    FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
    FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
    user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_auth VARCHAR(255) NOT NULL
    username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
    review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_id UUID REFERENCES Users(user_id),
    movie_id UUID REFERENCES Movies(movie_id),
    rating INT,
    review_text TEXT,
    review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE (movie_id, user_id)
    FOREIGN KEY (user_id) REFERENCES Users(user_id),
    FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);

ขั้นตอนต่อไปคืออะไร

  • ดูวิธีเรียกใช้การค้นหาและการเปลี่ยนแปลงจาก Web SDK, Android SDK, iOS SDK และ Flutter SDK ที่สร้างขึ้นโดยอัตโนมัติ