Firebase Data Connect ช่วยให้คุณสร้างเครื่องมือเชื่อมต่อสําหรับอินสแตนซ์ PostgreSQL ที่มีการจัดการด้วย Google Cloud SQL ได้ เครื่องมือเชื่อมต่อเหล่านี้เป็นการรวมสคีมา การค้นหา และการดัดแปลงสําหรับการใช้ข้อมูล
คู่มือเริ่มต้นใช้งานได้แนะนำสคีมาแอปรีวิวภาพยนตร์สําหรับ PostgreSQL และคู่มือนี้จะเจาะลึกวิธีออกแบบสคีมา Data Connect สําหรับ PostgreSQL
คู่มือนี้จะจับคู่Data Connectการค้นหาและการกลายพันธุ์กับตัวอย่างสคีมา เหตุใดจึงต้องพูดถึงการค้นหา (และการกลายพันธุ์) ในคู่มือเกี่ยวกับสคีมาData Connect เช่นเดียวกับแพลตฟอร์มอื่นๆ ที่ใช้ GraphQL Firebase Data Connect เป็นแพลตฟอร์มการพัฒนาแบบเน้นการค้นหา ดังนั้นในฐานะนักพัฒนาซอฟต์แวร์ คุณจะต้องคำนึงถึงข้อมูลที่จำเป็นสำหรับลูกค้าในโมเดลข้อมูล ซึ่งจะส่งผลอย่างมากต่อสคีมาข้อมูลที่พัฒนาขึ้นสำหรับโปรเจ็กต์
คู่มือนี้เริ่มต้นด้วยสคีมาใหม่สําหรับรีวิวภาพยนตร์ จากนั้นจะอธิบายการค้นหาและการดัดแปลงที่มาจากสคีมานั้น และสุดท้ายจะแสดงรายการ SQL ที่เทียบเท่ากับสคีมา Data Connect หลัก
สคีมาสําหรับแอปรีวิวภาพยนตร์
สมมติว่าคุณต้องการสร้างบริการที่อนุญาตให้ผู้ใช้ส่งและดูรีวิวภาพยนตร์
คุณต้องมีสคีมาเริ่มต้นสําหรับแอปดังกล่าว ซึ่งคุณจะขยายสคีมานี้ในภายหลังเพื่อสร้างการค้นหาเชิงสัมพันธ์ที่ซับซ้อน
ตารางภาพยนตร์
สคีมาสำหรับภาพยนตร์มีคำสั่งหลักๆ เช่น
@table(name)
และ@col(name)
เพื่อปรับแต่งชื่อตารางและคอลัมน์ SQL Data Connect จะสร้างชื่อรูปแบบ Snake_Case หากไม่ได้ระบุ@col(dataType)
เพื่อปรับแต่งประเภทคอลัมน์ SQL@default
เพื่อกําหนดค่าเริ่มต้นของคอลัมน์ SQL ระหว่างการแทรก
ดูรายละเอียดเพิ่มเติมได้ที่เอกสารอ้างอิงสำหรับ @table
, @col
, @default
# Movies
type Movie @table(name: "movie", key: "id") {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int
genre: String @col(dataType: "varchar(20)")
rating: Int
description: String
}
ค่าสเกลาร์หลักและค่าเซิร์ฟเวอร์
ก่อนที่จะดูแอปรีวิวภาพยนตร์เพิ่มเติม เราขอแนะนําData Connect ค่าสเกลาร์ของคีย์และค่าเซิร์ฟเวอร์
สเกลาร์คีย์คือตัวระบุออบเจ็กต์ที่กระชับซึ่ง Data Connect จะรวบรวมจากช่องคีย์ในสคีมาโดยอัตโนมัติ สเกลาร์คีย์เกี่ยวข้องกับประสิทธิภาพ ซึ่งช่วยให้คุณค้นหาข้อมูลเกี่ยวกับตัวตนและโครงสร้างของข้อมูลได้ในคอลเดียว ซึ่งจะเป็นประโยชน์อย่างยิ่งเมื่อคุณต้องการดําเนินการตามลําดับกับระเบียนใหม่และต้องการตัวระบุที่ไม่ซ้ำกันเพื่อส่งไปยังการดำเนินการที่กําลังจะเกิดขึ้น และในกรณีที่คุณต้องการเข้าถึงคีย์เชิงสัมพันธ์เพื่อดําเนินการที่ซับซ้อนมากขึ้น
การใช้ค่าเซิร์ฟเวอร์ช่วยให้คุณอนุญาตให้เซิร์ฟเวอร์ป้อนข้อมูลฟิลด์ในตารางแบบไดนามิกได้อย่างมีประสิทธิภาพโดยใช้ค่าที่เก็บไว้หรือค่าที่คำนวณได้ทันทีตามนิพจน์ CEL ฝั่งเซิร์ฟเวอร์ที่เฉพาะเจาะจงในอาร์กิวเมนต์ expr
ตัวอย่างเช่น คุณสามารถกําหนดช่องที่มีการใช้การประทับเวลาเมื่อมีการเข้าถึงช่องโดยใช้เวลาที่จัดเก็บไว้ในคําขอการดําเนินการ updatedAt: Timestamp! @default(expr: "request.time")
ตารางข้อมูลเมตาของภาพยนตร์
ตอนนี้มาติดตามผู้กำกับภาพยนตร์และสร้างความสัมพันธ์แบบ 1:1 กับ Movie
เพิ่มฟิลด์ข้อมูลอ้างอิงเพื่อกําหนดความสัมพันธ์
คุณสามารถใช้คำสั่ง @ref
เพื่อปรับแต่งข้อจำกัดของคีย์ต่างประเทศได้
@ref(fields)
เพื่อระบุฟิลด์คีย์ต่างประเทศ@ref(references)
เพื่อระบุฟิลด์ที่อ้างอิงในตารางเป้าหมาย โดยค่าเริ่มต้นการอ้างอิงนี้จะใช้เป็นคีย์หลัก แต่ระบบรองรับช่องที่มี@unique
ด้วย
ดูรายละเอียดเพิ่มเติมได้ที่เอกสารอ้างอิงสำหรับ @ref
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
# @unique ensures that each Movie only has one MovieMetadata.
movie: Movie! @unique
# Since it references to another table type, it adds a foreign key constraint.
# movie: Movie! @unique @ref(fields: "movieId", references: "id")
# movieId: UUID! <- implicitly added foreign key field
director: String
}
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 {
id: UUID! @default(expr: "uuidV4()")
name: String! @col(dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary keys of this table
# In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
movie: Movie!
# movieId: UUID! <- implicitly added foreign key field
actor: Actor!
# actorId: UUID! <- implicitly added foreign key field
role: String! # "main" or "supporting"
# optional other fields
}
ผู้ใช้
สุดท้ายคือผู้ใช้แอป
# Users
# Suppose a user can leave reviews for movies
type User @table {
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
}
ประเภทข้อมูลที่รองรับ
Data Connect รองรับประเภทข้อมูลสเกลาร์ต่อไปนี้ โดยมีการกำหนดค่าให้กับประเภท PostgreSQL โดยใช้ @col(dataType:)
Data Connect type | ประเภทในตัวของ 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 (จริง) float8 (ความแม่นยำแบบ Double) numeric (ทศนิยม) |
บูลีน | GraphQL | boolean | boolean |
UUID | กำหนดเอง | uuid | uuid |
Int64 | กำหนดเอง | bigint | int8 (bigint, bigserial) numeric (decimal) |
วันที่ | กำหนดเอง | date | วันที่ |
การประทับเวลา | กำหนดเอง | timestamptz | timestamptz หมายเหตุ: ระบบจะไม่จัดเก็บข้อมูลเขตเวลาท้องถิ่น |
เวกเตอร์ | กำหนดเอง | vector | เวกเตอร์ |
- GraphQL
List
จะจับคู่กับอาร์เรย์ 1 มิติ- เช่น
[Int]
แมปกับint5[]
,[Any]
แมปกับjsonb[]
- Data Connect ไม่รองรับอาร์เรย์ที่ซ้อนกัน
- เช่น
ใช้ช่องที่สร้างขึ้นเพื่อสร้างการค้นหาและการดัดแปลง
การค้นหาและการเปลี่ยนแปลง Data Connect จะขยายชุดช่อง Data Connect ที่สร้างขึ้นโดยอัตโนมัติตามประเภทและความสัมพันธ์ของประเภทในสคีมา ช่องเหล่านี้สร้างขึ้นโดยเครื่องมือในเครื่องทุกครั้งที่คุณแก้ไขสคีมา
ดังที่คุณได้เห็นในคู่มือเริ่มต้นใช้งาน Firebase Console และเครื่องมือการพัฒนาในเครื่องของเราใช้ช่องที่สร้างขึ้นโดยอัตโนมัติเหล่านี้เพื่อให้บริการการค้นหาและการเปลี่ยนแปลงการดูแลระบบเฉพาะกิจที่คุณสามารถใช้เพื่อเริ่มต้นข้อมูลและยืนยันเนื้อหาของตาราง
ในกระบวนการพัฒนา คุณจะใช้การค้นหาที่ใช้งานได้และการดัดแปลงที่ใช้งานได้ซึ่งรวมอยู่ในเครื่องมือเชื่อมต่อ โดยอิงตามฟิลด์ที่สร้างขึ้นโดยอัตโนมัติเหล่านี้
การตั้งชื่อช่องที่สร้างขึ้นโดยอัตโนมัติ
Data Connect อนุมานชื่อที่เหมาะสมสำหรับช่องที่สร้างขึ้นโดยอัตโนมัติตามประกาศประเภทสคีมา เช่น เมื่อใช้แหล่งข้อมูล PostgreSQL หากคุณกำหนดตารางชื่อ Movie
เซิร์ฟเวอร์จะสร้างสิ่งต่อไปนี้
- ฟิลด์สําหรับการอ่านข้อมูลใน Use Case ตารางเดียวที่มีชื่อที่เข้าใจง่าย
movie
(เอกพจน์ สําหรับการดึงข้อมูลผลลัพธ์แต่ละรายการโดยส่งอาร์กิวเมนต์ เช่นeq
) และmovies
(พหูพจน์ สําหรับการดึงข้อมูลรายการผลลัพธ์โดยส่งอาร์กิวเมนต์ เช่นgt
และการดำเนินการ เช่นorderby
) นอกจากนี้ Data Connect ยังสร้างฟิลด์สําหรับการดำเนินการแบบ Relational หลายตารางที่มีชื่อที่ชัดเจน เช่นactors_on_movies
หรือactors_via_actormovie
- ช่องสำหรับเขียนข้อมูลที่มีชื่อที่คุ้นเคย เช่น
movie_insert
,movie_upsert
...
ภาษาคําจํากัดความสคีมายังให้คุณควบคุมการสร้างชื่อให้กับช่องได้อย่างชัดเจนโดยใช้อาร์กิวเมนต์คําสั่ง singular
และ plural
คําสั่งสําหรับการค้นหาและการดัดแปลง
นอกเหนือจากคำสั่งที่คุณใช้ในการกำหนดประเภทและตารางแล้ว Data Connect ยังมีคำสั่ง @auth
, @check
, @redact
และ @transaction
สำหรับเสริมพฤติกรรมของคำค้นหาและการกลายพันธุ์
คำสั่ง | ใช้ได้กับ | คำอธิบาย |
---|---|---|
@auth |
การค้นหาและการกลายพันธุ์ | กำหนดนโยบายการตรวจสอบสิทธิ์สําหรับการค้นหาหรือการดัดแปลง โปรดดูคู่มือการให้สิทธิ์และการรับรอง |
@check |
การค้นหาข้อมูลการให้สิทธิ์ | ยืนยันว่าฟิลด์ที่ระบุมีอยู่ในผลการค้นหา นิพจน์ Common Expression Language (CEL) ใช้เพื่อทดสอบค่าในช่อง โปรดดูคู่มือการให้สิทธิ์และการรับรอง |
@redact |
คำค้นหา | ปกปิดข้อมูลบางส่วนในการตอบกลับจากลูกค้า โปรดดูคู่มือการให้สิทธิ์และการรับรอง |
@transaction |
การกลายพันธุ์ | บังคับให้การเปลี่ยนแปลงทำงานในธุรกรรมฐานข้อมูลเสมอ ดูตัวอย่างการกลายพันธุ์ของแอปภาพยนตร์ |
การค้นหาฐานข้อมูลรีวิวภาพยนตร์
คุณกําหนดการค้นหา 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 สามารถเข้าถึงข้อมูลตามความสัมพันธ์ระหว่างตาราง คุณสามารถใช้ความสัมพันธ์แบบออบเจ็กต์ (แบบ 1:1) หรืออาร์เรย์ (แบบ 1:หลายรายการ) ที่กําหนดไว้ในสคีมาเพื่อสร้างการค้นหาที่ฝังอยู่ เช่น ดึงข้อมูลสําหรับประเภทหนึ่งพร้อมกับข้อมูลจากประเภทที่ฝังอยู่หรือที่เกี่ยวข้อง
การค้นหาดังกล่าวใช้ไวยากรณ์ Data Connect _on_
และ _via
พิเศษในช่องการอ่านที่สร้างขึ้น
คุณจะทำการเปลี่ยนแปลงสคีมาจากเวอร์ชันเริ่มต้น
หลายรายการต่อ 1 รายการ
มาเพิ่มรีวิวลงในแอปกันโดยใช้ตาราง Review
และการแก้ไข User
# User table is keyed by Firebase Auth UID.
type User @table {
# `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
# The `user: User!` field in the Review table generates the following one-to-many query field.
# reviews_on_user: [Review!]!
# The `Review` join table the following many-to-many query field.
# movies_via_Review: [Movie!]!
}
# Reviews is a join table tween User and Movie.
# It has a composite primary keys `userUid` and `movieId`.
# A user can leave reviews for many movies. A movie can have reviews from many users.
# User <-> Review is a one-to-many relationship
# Movie <-> Review is a one-to-many relationship
# Movie <-> User is a many-to-many relationship
type Review @table(name: "Reviews", key: ["movie", "user"]) {
user: User!
# The user field adds the following foreign key field. Feel free to uncomment and customize it.
# userUid: String!
movie: Movie!
# The movie field adds the following foreign key field. Feel free to uncomment and customize it.
# movieId: UUID!
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
}
dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
title
genre
}
}
}
แบบตัวต่อตัว
คุณจะเห็นรูปแบบ ด้านล่างนี้เป็นสคีมาที่มีการแก้ไขเพื่อแสดงภาพ
# 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
}
}
}
การค้นหาการรวม
ข้อมูลรวมคืออะไรและเหตุผลที่ควรใช้
ฟิลด์รวมช่วยให้คุณทําการคํานวณในรายการผลลัพธ์ได้ การใช้ช่องข้อมูลรวมช่วยให้คุณทําสิ่งต่อไปนี้ได้
- ดูคะแนนเฉลี่ยของรีวิว
- ดูต้นทุนรวมของสินค้าในรถเข็นช็อปปิ้ง
- ค้นหาผลิตภัณฑ์ที่ได้รับคะแนนสูงสุดหรือต่ำสุด
- นับจำนวนผลิตภัณฑ์ในร้านค้า
ระบบจะดำเนินการรวมข้อมูลในเซิร์ฟเวอร์ ซึ่งมีข้อดีหลายประการเหนือกว่าการคำนวณในฝั่งไคลเอ็นต์ ดังนี้
- ประสิทธิภาพของแอปที่เร็วขึ้น (เนื่องจากคุณหลีกเลี่ยงการคำนวณฝั่งไคลเอ็นต์)
- ลดต้นทุนการส่งออกข้อมูล (เนื่องจากคุณส่งเฉพาะผลลัพธ์ที่รวบรวมแทนอินพุตทั้งหมด)
- ความปลอดภัยที่ดีขึ้น (เนื่องจากคุณสามารถให้สิทธิ์เข้าถึงข้อมูลรวมแก่ลูกค้าแทนที่จะเป็นชุดข้อมูลทั้งหมด)
ตัวอย่างสคีมาสำหรับข้อมูลรวม
ในส่วนนี้ เราจะเปลี่ยนไปใช้สคีมาตัวอย่างหน้าร้าน ซึ่งเหมาะสําหรับอธิบายวิธีใช้ข้อมูลรวม
type Product @table {
name: String!
manufacturer: String!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
การรวมข้อมูลแบบง่าย
_count สำหรับทุกช่อง
ฟิลด์การรวมที่ง่ายที่สุดคือ _count
ซึ่งจะแสดงจํานวนแถวที่ตรงกับคําค้นหา Data Connect
จะสร้างช่องรวมที่เกี่ยวข้องสำหรับแต่ละช่องในประเภทของคุณ โดยขึ้นอยู่กับประเภทของช่อง
การค้นหา
query CountProducts {
products {
_count
}
}
การตอบสนอง one
one
ตัวอย่างเช่น หากคุณมีผลิตภัณฑ์ 5 รายการในฐานข้อมูล ผลลัพธ์ที่ได้จะเป็นดังนี้
{
"products": [
{
"_count": 5
}
]
}
ฟิลด์ทั้งหมดมีฟิลด์ <field>_count
ซึ่งจะนับจํานวนแถวที่มีค่าที่ไม่ใช่ Null ในฟิลด์นั้น
การค้นหา
query CountProductsWithExpirationDate {
products {
expirationDate_count
}
}
การตอบสนองfield_count
field_count
ตัวอย่างเช่น หากคุณมีผลิตภัณฑ์ 3 รายการที่มีวันที่หมดอายุ ผลลัพธ์ที่ได้จะมีลักษณะดังนี้
{
"products": [
{
"expirationDate_count": 3
}
]
}
_min, _max, _sum และ _avg สําหรับฟิลด์ตัวเลข
ช่องตัวเลข (int, float, int64) ยังมี <field>_min
, <field>_max
,
<field>_sum
และ <field>_avg
ด้วย
การค้นหา
query NumericAggregates {
products {
quantityInStock_max
price_min
price_avg
quantityInStock_sum
}
}
การตอบสนอง_min _max _sum _avg
_min _max _sum _avg
ตัวอย่างเช่น หากคุณมีผลิตภัณฑ์ต่อไปนี้
- ผลิตภัณฑ์ ก:
quantityInStock: 10
,price: 2.99
- ผลิตภัณฑ์ ข:
quantityInStock: 5
,price: 5.99
- ผลิตภัณฑ์ ค:
quantityInStock: 20
,price: 1.99
ผลลัพธ์ที่ได้จะเป็นดังนี้
{
"products": [
{
"quantityInStock_max": 20,
"price_min": 1.99,
"price_avg": 3.6566666666666666,
"quantityInStock_sum": 35
}
]
}
_min และ _max สำหรับวันที่และการประทับเวลา
ช่องวันที่และการประทับเวลามี <field>_min
และ <field>_max
การค้นหา
query DateAndTimeAggregates {
products {
expirationDate_max
expirationDate_min
}
}
การตอบสนอง_min _maxdatetime
_min _maxdatetime
ตัวอย่างเช่น หากคุณมีวันที่หมดอายุดังต่อไปนี้
- ผลิตภัณฑ์ ก:
2024-01-01
- ผลิตภัณฑ์ ข:
2024-03-01
- ผลิตภัณฑ์ ค:
2024-02-01
ผลลัพธ์ที่ได้จะเป็นดังนี้
{
"products": [
{
"expirationDate_max": "2024-03-01",
"expirationDate_min": "2024-01-01"
}
]
}
แตกต่างกัน
อาร์กิวเมนต์ distinct
ช่วยให้คุณรับค่าที่ไม่ซ้ำกันทั้งหมดของฟิลด์ (หรือชุดค่าผสมของฟิลด์) เช่น
การค้นหา
query ListDistinctManufacturers {
products(distinct: true) {
manufacturer
}
}
การตอบสนองdistinct
distinct
ตัวอย่างเช่น หากคุณมีผู้ผลิตต่อไปนี้
- ผลิตภัณฑ์ ก:
manufacturer: "Acme"
- ผลิตภัณฑ์ ข:
manufacturer: "Beta"
- ผลิตภัณฑ์ ค:
manufacturer: "Acme"
ผลลัพธ์ที่ได้จะเป็นดังนี้
{
"products": [
{ "manufacturer": "Acme" },
{ "manufacturer": "Beta" }
]
}
นอกจากนี้ คุณยังใช้อาร์กิวเมนต์ distinct
ในช่องรวมเพื่อรวมค่าที่ไม่ซ้ำกันแทนได้ เช่น
การค้นหา
query CountDistinctManufacturers {
products {
manufacturer_count(distinct: true)
}
}
การตอบสนองdistinctonaggregate
distinctonaggregate
ตัวอย่างเช่น หากคุณมีผู้ผลิตต่อไปนี้
- ผลิตภัณฑ์ ก:
manufacturer: "Acme"
- ผลิตภัณฑ์ ข:
manufacturer: "Beta"
- ผลิตภัณฑ์ ค:
manufacturer: "Acme"
ผลลัพธ์ที่ได้จะเป็นดังนี้
{
"products": [
{
"manufacturer_count": 2
}
]
}
การรวมข้อมูลแบบจัดกลุ่ม
คุณทําการรวมแบบกลุ่มได้โดยเลือกช่องรวมและช่องที่ไม่ใช่การรวมแบบผสมในประเภทหนึ่งๆ ซึ่งจะจัดกลุ่มแถวที่ตรงกันทั้งหมดซึ่งมีค่าเดียวกันสําหรับช่องที่ไม่ใช่การรวม และคำนวณช่องรวมสําหรับกลุ่มนั้น เช่น
การค้นหา
query MostExpensiveProductByManufacturer {
products {
manufacturer
price_max
}
}
การตอบสนองgroupedaggregates
groupedaggregates
ตัวอย่างเช่น หากคุณมีผลิตภัณฑ์ต่อไปนี้
- ผลิตภัณฑ์ ก:
manufacturer: "Acme"
,price: 2.99
- ผลิตภัณฑ์ ข:
manufacturer: "Beta"
,price: 5.99
- ผลิตภัณฑ์ ค:
manufacturer: "Acme"
,price: 1.99
ผลลัพธ์ที่ได้จะเป็นดังนี้
{
"products": [
{ "manufacturer": "Acme", "price_max": 2.99 },
{ "manufacturer": "Beta", "price_max": 5.99 }
]
}
having
และ where
ที่มีข้อมูลสรุปแบบกลุ่ม
นอกจากนี้ คุณยังใช้อาร์กิวเมนต์ having
และ where
เพื่อแสดงเฉพาะกลุ่มที่ตรงกับเกณฑ์ที่ระบุไว้ได้ด้วย
having
ช่วยให้คุณกรองกลุ่มตามช่องรวมได้where
ช่วยให้คุณกรองแถวตามช่องที่ไม่ใช่การรวมได้
การค้นหา
query FilteredMostExpensiveProductByManufacturer {
products(having: {price_max: {ge: 2.99}}) {
manufacturer
price_max
}
}
การตอบสนองhavingwhere
havingwhere
ตัวอย่างเช่น หากคุณมีผลิตภัณฑ์ต่อไปนี้
- ผลิตภัณฑ์ ก:
manufacturer: "Acme"
,price: 2.99
- ผลิตภัณฑ์ ข:
manufacturer: "Beta"
,price: 5.99
- ผลิตภัณฑ์ ค:
manufacturer: "Acme"
,price: 1.99
ผลลัพธ์ที่ได้จะเป็นดังนี้
{
"products": [
{ "manufacturer": "Acme", "price_max": 2.99 },
{ "manufacturer": "Beta", "price_max": 5.99 }
]
}
รวมข้อมูลในตารางต่างๆ
คุณใช้ช่องรวมร่วมกับช่องความสัมพันธ์แบบ 1: หลายรายการที่สร้างขึ้นเพื่อตอบคําถามที่ซับซ้อนเกี่ยวกับข้อมูลได้ สคีมาที่มีการแก้ไขซึ่งมีตาราง Manufacturer
แยกต่างหากที่เราใช้ในตัวอย่างได้มีดังนี้
type Product @table {
name: String!
manufacturer: Manufacturer!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
type Manufacturer @table {
name: String!
headquartersCountry: String!
}
ตอนนี้เราสามารถใช้ช่องรวมเพื่อดำเนินการต่างๆ เช่น ค้นหาจํานวนผลิตภัณฑ์ที่ผู้ผลิตผลิต
การค้นหา
query GetProductCount($id: UUID) {
manufacturers {
name
products_on_manufacturer {
_count
}
}
}
การตอบสนอง aggregatesacrosstables
aggregatesacrosstables
ตัวอย่างเช่น หากคุณมีผู้ผลิตต่อไปนี้
- ผู้ผลิต ก:
name: "Acme"
,products_on_manufacturer: 2
- ผู้ผลิต ข:
name: "Beta"
,products_on_manufacturer: 1
ผลลัพธ์ที่ได้จะเป็นดังนี้
{
"manufacturers": [
{ "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
{ "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
]
}
การกลายพันธุ์สําหรับฐานข้อมูลรีวิวภาพยนตร์
ดังที่ได้กล่าวไว้ เมื่อคุณกําหนดตารางในสคีมา 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 ครั้ง
คำสั่ง @transaction
คำสั่งนี้บังคับให้การดำเนินการเปลี่ยนแปลงทำงานในธุรกรรมฐานข้อมูลเสมอ
การกลายพันธุ์ที่มี @transaction
รับประกันว่าจะสำเร็จหรือล้มเหลวอย่างสมบูรณ์ หากช่องใดในธุรกรรมดำเนินการไม่สำเร็จ ระบบจะยกเลิกธุรกรรมทั้งหมด จากมุมมองไคลเอ็นต์ ความล้มเหลวใดๆ จะทํางานเหมือนกับว่าคําขอทั้งหมดไม่สําเร็จเนื่องจากมีข้อผิดพลาดเกี่ยวกับคําขอและยังไม่ได้เริ่มการดําเนินการ
การกลายพันธุ์ที่ไม่มี @transaction
จะเรียกใช้ช่องรูทแต่ละช่องตามลำดับ โดยจะแสดงข้อผิดพลาดเป็นข้อผิดพลาดของฟิลด์บางส่วน แต่จะไม่แสดงผลกระทบของการดำเนินการที่ตามมา
สร้าง
มาสร้างแบบพื้นฐานกัน
# 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"
})
}
ทำการอัปเดต
ข้อมูลอัปเดตมีดังนี้ โปรดิวเซอร์และผู้กำกับย่อมหวังว่าเรตติ้งเฉลี่ยเหล่านั้นจะเพิ่มขึ้น
ช่อง movie_update
มีอาร์กิวเมนต์ id
ที่คาดไว้เพื่อระบุระเบียน และช่อง data
ที่คุณใช้เพื่อตั้งค่าในการอัปเดตนี้ได้
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
หากต้องการอัปเดตหลายรายการ ให้ใช้ช่อง movie_updateMany
# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
movie_updateMany(
where: { genre: { eq: $genre } },
data:
{
rating: $rating
})
}
ใช้การดำเนินการเพิ่ม ลด ต่อท้าย และนำหน้าด้วย _update
แม้ว่าคุณจะตั้งค่าใน data:
อย่างชัดเจนได้เมื่อทำการดัดแปลง _update
และ _updateMany
แต่การใช้โอเปอเรเตอร์อย่างการเพิ่มเพื่ออัปเดตค่ามักจะเหมาะสมกว่า
หากต้องการแก้ไขตัวอย่างการอัปเดตก่อนหน้านี้ สมมติว่าคุณต้องการเพิ่มคะแนนของภาพยนตร์บางเรื่อง คุณสามารถใช้ไวยากรณ์ rating_update
กับโอเปอเรเตอร์ inc
ได้
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect รองรับโอเปอเรเตอร์ต่อไปนี้สำหรับการอัปเดตช่อง
inc
เพื่อเพิ่มประเภทข้อมูลInt
,Int64
และFloat
dec
เพื่อลดประเภทข้อมูลInt
,Int64
และFloat
append
เพื่อเพิ่มต่อท้ายรายการประเภทต่างๆ ยกเว้นรายการเวกเตอร์prepend
เพื่อเพิ่มไว้ข้างหน้าประเภทรายการ ยกเว้นรายการเวกเตอร์
ดำเนินการลบ
แน่นอนว่าคุณลบข้อมูลภาพยนตร์ได้ แน่นอนว่าผู้อนุรักษ์ภาพยนตร์ต้องการเก็บรักษาภาพยนตร์ให้นานที่สุด
# 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 }
)
}
อนุญาตให้ Data Connect ระบุค่าโดยใช้ไวยากรณ์ field_expr
ดังที่ได้กล่าวไว้ในค่าสเกลาร์หลักและค่าเซิร์ฟเวอร์ คุณออกแบบสคีมาเพื่อให้เซิร์ฟเวอร์ป้อนข้อมูลค่าสำหรับช่องทั่วไป เช่น id
และวันที่เพื่อตอบสนองคำขอของลูกค้าได้
นอกจากนี้ คุณยังใช้ประโยชน์จากข้อมูล เช่น รหัสผู้ใช้ ที่ส่งในออบเจ็กต์ Data Connect request
จากแอปไคลเอ็นต์ได้ด้วย
เมื่อใช้การกลายพันธุ์ ให้ใช้ไวยากรณ์ field_expr
เพื่อทริกเกอร์การอัปเดตที่เซิร์ฟเวอร์สร้างขึ้นหรือเข้าถึงข้อมูลจากคำขอ เช่น หากต้องการส่งผ่านการให้สิทธิ์ uid
ที่เก็บไว้ในคําขอไปยังการดำเนินการ _upsert
ให้ส่งผ่าน "auth.uid"
ในช่อง userId_expr
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}
# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
หรือในแอปรายการสิ่งที่ต้องทำที่คุ้นเคย เมื่อสร้างรายการสิ่งที่ต้องทำใหม่ คุณสามารถส่งผ่าน id_expr
เพื่อสั่งให้เซิร์ฟเวอร์สร้าง UUID ให้กับรายการโดยอัตโนมัติ
mutation CreateTodoListWithFirstItem(
$listName: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
name: $listName,
})
}
ดูข้อมูลเพิ่มเติมได้ที่_Expr
สเกลาร์ในข้อมูลอ้างอิงสเกลาร์
การค้นหาข้อมูลการให้สิทธิ์
Data Connect คุณสามารถให้สิทธิ์การดัดแปลงได้โดยเริ่มจากการค้นหาฐานข้อมูลและยืนยันผลการค้นหาด้วยนิพจน์ CEL ซึ่งจะเป็นประโยชน์เมื่อคุณเขียนลงในตารางและต้องตรวจสอบเนื้อหาของแถวในตารางอื่น
ฟีเจอร์นี้รองรับรายการต่อไปนี้
- คำสั่ง
@check
ซึ่งช่วยให้คุณประเมินเนื้อหาของช่อง และดำเนินการต่อไปนี้ตามผลลัพธ์ของการประเมินดังกล่าว- ดำเนินการสร้าง อัปเดต และลบที่กําหนดโดย Mutation
- ดำเนินการแสดงผลการค้นหา
- ใช้ค่าที่แสดงผลเพื่อดำเนินการตามตรรกะต่างๆ ในโค้ดไคลเอ็นต์
- คำสั่ง
@redact
ซึ่งช่วยให้คุณละเว้นผลการค้นหาจากโปรโตคอลการเดินสายจากผลการค้นหาได้
ฟีเจอร์เหล่านี้มีประโยชน์สําหรับขั้นตอนการให้สิทธิ์
สคีมา 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 ที่สร้างขึ้นโดยอัตโนมัติ