測驗:Supabase 資料表設計與 CRUD 操作實戰
共 5 題,點選答案後會立即顯示結果
1. 在 Supabase 中使用 .insert() 新增資料後,如果想要取得剛新增的資料,應該加上什麼方法?
2. 在 Supabase 查詢中,.single() 方法的作用是什麼?
3. 以下哪個過濾方法可以用來做不區分大小寫的模糊搜尋?
4. 在 Supabase 中設定關聯查詢時,若要查詢文章及其所有留言,正確的 select 語法是?
5. 在進行 .update() 或 .delete() 操作時,如果沒有加上過濾條件(如 .eq()),會發生什麼事?
前言
在前兩篇文章中,我們已經認識了 Supabase 並完成專案建立與環境設定。現在是時候開始真正操作資料庫了。本篇將帶你從建立資料表開始,一路學會完整的 CRUD(Create, Read, Update, Delete)操作。
當你用 AI 輔助開發時,看到 Supabase 相關程式碼,你會知道每一行在做什麼,而不是盲目地複製貼上。
學習目標
讀完本篇後,你將能夠:
- 在 Supabase 中設計並建立資料表
- 使用 Supabase Client 進行完整的 CRUD 操作
- 了解 Supabase 查詢語法與過濾條件
- 處理資料關聯與 JOIN 查詢
一、使用 Table Editor 建立資料表
Supabase 提供直覺的圖形化介面讓你建立資料表,不需要手寫 SQL。
1.1 進入 Table Editor
登入 Supabase Dashboard 後,在左側選單點選 Table Editor,你會看到目前專案的所有資料表。
1.2 建立第一個資料表
點選 New Table 按鈕,會出現建立表單。以建立一個 posts(文章)資料表為例:
| 欄位名稱 | 資料型別 | 說明 |
|---|---|---|
| id | int8 | 主鍵,自動遞增 |
| title | text | 文章標題 |
| content | text | 文章內容 |
| author_id | uuid | 作者 ID(關聯 auth.users) |
| is_published | bool | 是否已發布 |
| view_count | int4 | 瀏覽次數 |
| created_at | timestamptz | 建立時間 |
1.3 常用資料型別對照
當你在 AI 生成的程式碼中看到這些型別時:
int2, int4, int8 → 整數(2/4/8 bytes)
float4, float8 → 浮點數
text, varchar → 文字
bool → 布林值
uuid → 通用唯一識別碼
timestamptz → 帶時區的時間戳記
jsonb → JSON 資料(可查詢)
Code language: JavaScript (javascript)1.4 重要約束設定
建立欄位時,你會看到這些選項:
- Primary Key(主鍵):唯一識別每一筆資料
- Is Nullable:是否允許空值(NULL)
- Is Unique:是否要求值不重複
- Default Value:預設值,例如
now()自動填入當前時間
二、Supabase Client 初始化
在進行 CRUD 操作前,需要先初始化 Supabase Client。
2.1 JavaScript/TypeScript 初始化
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://your-project.supabase.co'
const supabaseAnonKey = 'your-anon-key'
const supabase = createClient(supabaseUrl, supabaseAnonKey)
Code language: JavaScript (javascript)當你看到這段程式碼時,理解重點:
supabaseUrl:你的專案 API 端點supabaseAnonKey:公開的匿名金鑰(可以放在前端)createClient:建立連線實例,後續所有操作都透過它
2.2 Python 初始化
from supabase import create_client
url = "https://your-project.supabase.co"
key = "your-anon-key"
supabase = create_client(url, key)
Code language: JavaScript (javascript)三、CRUD 操作詳解
3.1 Create:新增資料
新增單筆資料
const { data, error } = await supabase
.from('posts') // 指定資料表
.insert({ // 插入的資料物件
title: '我的第一篇文章',
content: '這是內容...',
is_published: true
})
.select() // 回傳新增的資料
Code language: JavaScript (javascript)逐行解讀:
.from('posts'):選擇要操作的資料表.insert({...}):傳入要新增的資料物件.select():讓回傳結果包含新增的資料(否則只回傳 null)
新增多筆資料
const { data, error } = await supabase
.from('posts')
.insert([
{ title: '文章一', content: '內容一' },
{ title: '文章二', content: '內容二' },
{ title: '文章三', content: '內容三' }
])
.select()
Code language: JavaScript (javascript)傳入陣列即可批次新增。
3.2 Read:查詢資料
查詢所有資料
const { data, error } = await supabase
.from('posts')
.select('*') // * 表示選取所有欄位
Code language: JavaScript (javascript)查詢特定欄位
const { data, error } = await supabase
.from('posts')
.select('id, title, created_at') // 只取需要的欄位
Code language: JavaScript (javascript)效能提示:只選取需要的欄位可以減少傳輸量。
查詢單筆資料
const { data, error } = await supabase
.from('posts')
.select('*')
.eq('id', 1) // 條件:id 等於 1
.single() // 預期只有一筆結果
Code language: JavaScript (javascript).single() 會直接回傳物件而非陣列,如果查到多筆或零筆會報錯。
3.3 Update:更新資料
const { data, error } = await supabase
.from('posts')
.update({
title: '更新後的標題',
is_published: false
})
.eq('id', 1) // 重要:指定要更新哪一筆
.select()
Code language: JavaScript (javascript)注意:如果沒有加上過濾條件(如 .eq()),會更新整個資料表的所有資料!
3.4 Delete:刪除資料
const { data, error } = await supabase
.from('posts')
.delete()
.eq('id', 1) // 重要:指定要刪除哪一筆
Code language: JavaScript (javascript)同樣地,沒有過濾條件會刪除所有資料。
四、查詢過濾條件
Supabase 提供豐富的過濾方法,讓你精確查詢資料。
4.1 比較運算子
// 等於
.eq('status', 'active') // status = 'active'
// 不等於
.neq('status', 'deleted') // status != 'deleted'
// 大於
.gt('view_count', 100) // view_count > 100
// 大於等於
.gte('view_count', 100) // view_count >= 100
// 小於
.lt('view_count', 50) // view_count < 50
// 小於等於
.lte('view_count', 50) // view_count <= 50
Code language: JavaScript (javascript)4.2 文字搜尋
// 模糊搜尋(區分大小寫)
.like('title', '%教學%') // title LIKE '%教學%'
// 模糊搜尋(不區分大小寫)
.ilike('title', '%tutorial%') // title ILIKE '%tutorial%'
Code language: PHP (php)% 是萬用字元,代表任意字元。
4.3 陣列與範圍
// 在指定值中
.in('status', ['draft', 'review']) // status IN ('draft', 'review')
// 包含(陣列欄位)
.contains('tags', ['javascript']) // tags 陣列包含 'javascript'
// 範圍查詢
.gte('created_at', '2024-01-01')
.lt('created_at', '2024-02-01') // 查詢一月份的資料
Code language: JavaScript (javascript)4.4 組合多個條件
const { data, error } = await supabase
.from('posts')
.select('*')
.eq('is_published', true)
.gt('view_count', 100)
.order('created_at', { ascending: false })
Code language: JavaScript (javascript)多個過濾條件會以 AND 邏輯組合。
4.5 OR 條件
const { data, error } = await supabase
.from('posts')
.select('*')
.or('status.eq.draft,status.eq.review')
Code language: JavaScript (javascript)使用 .or() 時,條件格式是 欄位.運算子.值,用逗號分隔。
五、排序與分頁
5.1 排序 (order)
// 依建立時間降序排列(最新的在前)
.order('created_at', { ascending: false })
// 多欄位排序
.order('is_published', { ascending: false })
.order('created_at', { ascending: false })
Code language: JavaScript (javascript)5.2 限制筆數 (limit)
// 只取前 10 筆
.limit(10)
Code language: JavaScript (javascript)5.3 分頁 (range)
// 取第 11-20 筆(從 0 開始算)
.range(10, 19)
Code language: JavaScript (javascript)5.4 完整分頁範例
const page = 2 // 第幾頁(從 1 開始)
const pageSize = 10 // 每頁筆數
const from = (page - 1) * pageSize
const to = from + pageSize - 1
const { data, error, count } = await supabase
.from('posts')
.select('*', { count: 'exact' }) // 同時取得總筆數
.order('created_at', { ascending: false })
.range(from, to)
console.log(`總共 ${count} 筆,顯示第 ${from + 1} 到 ${to + 1} 筆`)
Code language: JavaScript (javascript){ count: 'exact' } 會額外回傳符合條件的總筆數,方便計算總頁數。
六、關聯查詢
6.1 設定 Foreign Key
在 Table Editor 中建立 comments 資料表時,設定 post_id 欄位關聯到 posts.id:
- 建立
post_id欄位(型別 int8) - 點選欄位旁的連結圖示
- 選擇 Foreign Key 關聯到
posts表的id欄位
6.2 查詢關聯資料
設定好 Foreign Key 後,可以用巢狀語法一次查詢關聯資料:
// 查詢文章,同時取得該文章的所有留言
const { data, error } = await supabase
.from('posts')
.select(`
id,
title,
comments (
id,
content,
created_at
)
`)
Code language: JavaScript (javascript)回傳結構:
[
{
id: 1,
title: '我的第一篇文章',
comments: [
{ id: 1, content: '好文章!', created_at: '...' },
{ id: 2, content: '學到很多', created_at: '...' }
]
}
]
Code language: JavaScript (javascript)6.3 反向查詢
從 comments 查詢對應的 posts:
const { data, error } = await supabase
.from('comments')
.select(`
id,
content,
posts (
id,
title
)
`)
Code language: JavaScript (javascript)6.4 多層關聯
假設還有 users 資料表:
const { data, error } = await supabase
.from('posts')
.select(`
id,
title,
users!author_id (
id,
name
),
comments (
id,
content,
users (
id,
name
)
)
`)
Code language: JavaScript (javascript)users!author_id 語法指定使用 author_id 這個 Foreign Key 關聯。
七、錯誤處理
每個操作都會回傳 error 物件,務必檢查:
const { data, error } = await supabase
.from('posts')
.select('*')
if (error) {
console.error('查詢失敗:', error.message)
// error.code 包含錯誤代碼
// error.details 包含詳細資訊
return
}
// 成功,使用 data
console.log('查詢結果:', data)
Code language: JavaScript (javascript)常見錯誤代碼:
PGRST116:查詢結果為空(使用.single()時)23505:違反唯一約束(重複資料)23503:違反 Foreign Key 約束42P01:資料表不存在
八、實戰小結
快速參考表
| 操作 | 方法 | 範例 |
|---|---|---|
| 新增 | .insert() |
.insert({ title: '...' }).select() |
| 查詢 | .select() |
.select('id, title') |
| 更新 | .update() |
.update({ title: '...' }).eq('id', 1) |
| 刪除 | .delete() |
.delete().eq('id', 1) |
| 過濾 | .eq() .gt() .like() |
.eq('status', 'active') |
| 排序 | .order() |
.order('created_at', { ascending: false }) |
| 分頁 | .range() .limit() |
.range(0, 9) |
| 關聯 | 巢狀 select | .select('*, comments(*)') |
常見模式
// 取得分頁列表
const getPostList = async (page, pageSize) => {
const from = (page - 1) * pageSize
const to = from + pageSize - 1
return await supabase
.from('posts')
.select('id, title, created_at', { count: 'exact' })
.eq('is_published', true)
.order('created_at', { ascending: false })
.range(from, to)
}
// 取得單篇文章(含留言)
const getPostDetail = async (id) => {
return await supabase
.from('posts')
.select(`
*,
comments (
id, content, created_at,
users (id, name)
)
`)
.eq('id', id)
.single()
}
Code language: JavaScript (javascript)結語
現在你已經掌握了 Supabase 的資料表設計與 CRUD 操作。這些是與資料庫互動的基本功,無論你用什麼框架,這些概念都通用。
下一篇我們將深入探討 Row Level Security(RLS),學習如何保護你的資料安全,確保使用者只能存取他們被授權的資料。
延伸閱讀
進階測驗:Supabase 資料表設計與 CRUD 操作實戰
共 5 題,包含情境題與錯誤診斷題。