---
title: "自建表與資料模型擴充"
category: "data-center"
canonical: "https://www.ai-go.app/zh-TW/docs/guide/custom-tables"
locale: "zh-TW"
last_updated: "2026-08-10"
source: "AI GO 開發者文件（https://www.ai-go.app）"
---

# 自建表與資料模型擴充

預建的 ERP 表覆蓋了通用的企業流程，但每個產業都有自己的東西——健身房要記量測數據、旅行社要記團體行程、補習班要記課堂出席。這一章說明怎麼擴充資料模型。

---

## 三條擴充路徑

| 路徑 | 適合 | 可查詢／過濾 | 有型別約束 | 可建關聯 |
|---|---|---|---|---|
| `custom_data` JSONB | 少量、非結構化的附加欄位 | 有限 | 否 | 否 |
| **延伸欄位** | 在既有 ERP 表上加欄位 | 是 | 是 | 是 |
| **自建表** | 全新的資料實體 | 是 | 是 | 是 |

判斷方式很簡單：**這筆資料是既有實體的附加屬性，還是一個新實體？** 客戶多一個「會員卡號」是前者（延伸欄位）；「課堂出席紀錄」是後者（自建表）。

---

## 自建表：租戶層級的資源

這是自建表最重要、也最容易被誤解的特性：

**自建表綁的是組織，不是應用程式。**

同一個組織下的所有 Custom App、以及組織自己的資料中心介面，看到的是**同一批表、同一份資料**。

這帶來一條必要紀律：**建表前先確認有沒有可重用的既有表**。同一張「客戶」表不該因為兩個應用各建一次，就分裂成兩份互不相通的資料。

---

## 雙軌命名：顯示名與實體名

每張表、每個欄位都有兩個名字：

| | 顯示名 | 實體名 |
|---|---|---|
| 誰決定 | 您提供 | 系統從顯示名自動生成，或由您顯式指定 |
| 可否修改 | 隨時可改 | **建立後永不可變** |
| 字元集 | 任意（中文常見） | 純 ASCII |
| 用途 | 介面呈現 | API 識別、關聯目標指定 |

**所有 API 與 SDK 在指涉既有表／欄位時一律使用實體名。** 顯示名只是標籤——改顯示名不會影響任何既有的程式碼或引用。

實體名的生成會避開三個母體：同組織既有的自建表名、SQL 保留字、以及平台內建的 ERP 表名。例如顯示名「Customers」在空白環境下會得到 `customers_2` 而不是 `customers`，因為後者已被 ERP 佔用。

若您顯式指定的實體名撞到上述任一項，系統會回報衝突並明確告訴您撞到哪一類，而不是靜默改名。

---

## 欄位型別

| 型別 | 說明 | 額外契約 |
|---|---|---|
| `text` | 文字 | |
| `number` | 數值 | |
| `boolean` | 布林 | |
| `date` | 日期 | |
| `datetime` | 日期時間 | |
| `select` | 單選 | 必須提供選項集；值受資料庫層級約束 |
| `relation` | 關聯 | 見下節 |
| `json` | 結構化資料 | |
| `image` | 圖片 | 見下節 |

每張表都會自動帶系統欄位（識別碼、建立時間、更新時間）。系統欄位不可刪、不可改型別，也不計入欄位配額。

### relation 欄位

`relation` 可以指向**兩種目標**：另一張自建表，或平台內建的 ERP 表。

這代表您可以建立一張「課堂出席紀錄」自建表，其中的「學員」欄位直接關聯到 ERP 的 `customers` 表——不需要複製一份客戶資料，也不需要自己維護對應關係。

### image 欄位

`image` 欄位提供完整的圖片處理：上傳走平台代理（而非前端直接上傳到儲存空間），讀取時取得帶簽章的臨時網址，並受跨組織存取控制保護。圖片會計入組織的儲存配額。

---

## 誰能改結構

**結構操作僅限管理員**（持有 `system.admin` 的使用者）：建表、改表、刪表、建欄位、改欄位、刪欄位、以及 ERP 延伸欄位的定義。

**資料操作（記錄的增刪改查）與結構讀取**維持一般開發權限（`builder.access`）。

管的是 schema 的形狀，不是它的使用。這道授權在**入口層**執行，且三個入口（REST API、SDK、AI 工具）語意一致——AI 通道不能繞過 REST 的權限閘。

---

## 刪除是兩段式的

刪表與刪欄位都需要兩步，且由伺服器端強制，不是前端的確認彈窗：

1. **影響預覽**：系統先告訴您這個刪除會影響什麼——有多少筆資料、有哪些關聯指向它
2. **實體名強確認**：您必須輸入該表／欄位的**實體名**才能執行

刪除不可復原。這道設計刻意讓誤刪變得困難。

---

## 三個入口，一層服務

自建表的所有操作——結構與資料——都收斂到單一服務層。三個入口共用它：

| 入口 | 對面是誰 | 身分來源 |
|---|---|---|
| REST API | 組織使用者（資料中心介面） | 登入使用者的 session |
| SDK（`ctx.db`） | Custom App 的執行期程式碼 | 應用程式的執行身分 ＋ Scope |
| Builder 工具 | AI 開發助手 | Builder session 的組織身分 |

**這層服務是唯一的護欄所在地。** 入口不重新實作驗證，也不繞過它：

- **租戶隔離**：所有操作以組織為錨；跨組織存取一律回報「不存在」，不洩漏他人資源是否存在
- **結構操作序列化**：結構變更走組織層級的鎖，併發建表不會互相撕裂
- **配額**：每組織的表數上限、每表的欄位數上限，在鎖內檢查以防併發繞過
- **型別與值驗證**：欄位型別、選項集、預設值、關聯目標的合法性
- **錯誤反譯**：資料庫層級的錯誤（唯一鍵衝突、必填為空、外鍵違反、依賴物件存在）會翻成結構化錯誤碼與可行動的訊息，不會裸拋技術訊息給使用者

**任何新入口都應該打這層，而不是自己拼 SQL。**

---

## 從 SDK 存取自建表

```python
def execute(ctx):
    # 列出組織的所有自建表
    tables = ctx.db.list_tables()

    # 查詢（用實體名）
    rows = ctx.db.query_table('attendance', filters=[
        {'column': 'class_date', 'op': 'gte', 'value': '2026-08-01'}
    ])

    # 新增
    ctx.db.insert_row('attendance', {
        'student_id': ctx.params['customer_id'],   # relation 指向 ERP customers
        'class_date': '2026-08-10',
        'status': 'present',
    })

    # 更新 / 刪除
    ctx.db.update_row('attendance', row_id, {'status': 'late'})
    ctx.db.delete_row('attendance', row_id)
```

前端則透過內建的 `src/db.ts` SDK 呼叫，語意相同。詳見第 8 章。

---

## ERP 延伸欄位

延伸欄位讓您在**既有 ERP 表**上新增真正的欄位——與 `custom_data` 不同，它可查詢、可過濾、有型別約束，也能被引用系統授權。

適用時機：這筆資料是既有實體的固有屬性，且您需要用它來篩選或排序。例如在 `customers` 上加「會員到期日」，之後就能查詢「本月到期的會員」。

刪除延伸欄位同樣走兩段式流程，並提供影響預覽。