查找型別:lookup
用途
lookup 透過本表自己的 link 欄,直接帶出目標列的一個純量欄位。例如訂單已用「客戶」link 指到客戶表,便可用 lookup 顯示該客戶的 Email。它不複製資料,也不執行自訂運算;目標值變更後,下一次讀取就會反映新值。
建立 schema
先建立 link 欄,再送出 columns.create body:
{
"name": "客戶 Email",
"type": "lookup",
"link_field": "客戶",
"target_column": "Email",
"description": "從客戶表即時帶出的聯絡信箱"
}link_field 必須是本表的 link,target_column 必須是該 link 目標表的儲存型純量欄。兩者都可用顯示名稱或內部鍵輸入,持久化時會改成 rename-stable 的內部鍵。
合法與不合法的值
lookup 是唯讀欄;合法的紀錄 payload 只寫 link,不寫 lookup:
{
"data": {
"客戶": ["33333333-3333-4333-8333-333333333333"]
}
}自行送出帶出的 Email 會被拒:
{
"data": {
"客戶": ["33333333-3333-4333-8333-333333333333"],
"客戶 Email": "buyer@example.com"
}
}設定時把 target_column 指到 link、rollup、lookup、formula、multi_select、attachment 或 json 也不合法;lookup 可帶出 string、text、integer、float、boolean、date、datetime、select、user 或 social_client 純量值。Principal lookup 回傳的是儲存的原始身分 ID,不是 principal 型別欄在所屬表讀取時產生的顯示物件。
顯示與回傳
回傳形狀由 link cardinality 決定。one link 回單一純量:
{
"data": {
"客戶": ["33333333-3333-4333-8333-333333333333"],
"客戶 Email": "buyer@example.com"
}
}many link 回與 link ID 順序對應的值陣列;除非該 lookup 帶了 pick(見下節),此時 cell 是單一值。不可讀或不存在的單一目標位置會是 null。沒有 fallback 時,沒有連結、依賴失效或整張目標表不可讀都會讓 cell 成為 null。符合條件的純量 lookup 可以帶 fallback,此時真正未連結的 primary 可查 defaults;但權限拒絕刻意不會被當成 primary 空值。
用 pick 把 many lookup 收斂成純量
建立在 cardinality-many link 上的 lookup 可以額外帶一個 pick 物件。它會依某個可排序欄位對「被連到的目標列」排名並取第一名(argmax),讓 cell 呈現單一純量而不是陣列:
{
"name": "最新一張發票金額",
"type": "lookup",
"link_field": "發票",
"target_column": "金額",
"pick": { "order_column": "開立日期", "direction": "desc" }
}這個欄位現在顯示「最近開立的那張連結發票的金額」— 也就是「最新發票金額」/「最後看診日」這一類 primitive,過去只有 cardinality-one link 做得到。
| 欄位 | 型別 | 意義 |
|---|---|---|
order_column | column ref | link 目標表上的可排序 stored 欄(integer、float、date、datetime)。可用顯示名稱或內部鍵輸入,持久化為目標表的內部鍵。 |
direction | "asc" / "desc" | "desc" 取最大值(最新/最高),"asc" 取最小值(最早/最低)。必填,沒有預設值。 |
排名規則是明確的,而且 SQL pushdown 與 Python 讀取路徑完全一致 — 你在畫面上看到的值,就是 filter/sort 比對到的那個值:
- 只有 live 且呼叫者可讀的目標列參與排名。
order_column為 NULL 的列一律排在最後,與 direction 無關。- 接著才套用指定的 direction。
- 平手時以 link 自己的
(position, id)決勝。若 order 欄不可排序或已失效,會退回這個(position, id)後備方案,而不是報錯。
數值型 order 欄使用較寬的數值 cast 比較,因此 picked lookup 可以安全地對非常大或非常精密的數字排名 — 比多欄 sort key 使用的 cast 更寬。
pick 解鎖了什麼
因為 picked cell 是純量,三件過去 unpicked many lookup 做不到的事變成合法。前兩項還要求 lookup 沒有同時帶 fallback;fallback 是下方說明的 eval-only 例外。
- 可以用
computed_filters篩選。 - 可以用
sort_by排序(僅限單欄排序 — 計算欄永遠不是合法的多欄sortkey)。 - 可以當成 formula 運算元,其型別就是目標欄自己的型別。要做算術,目標仍必須是數值型。
未帶 pick 的 many lookup 維持舊行為與舊錯誤:computed filter on '<col>': cardinality-"many" lookups render list cells and cannot be filtered or sorted (kept 400 by design)。
欄位建立之後,pick 永遠不能新增、修改或移除。columns.update 根本沒有 pick 欄位,REST 會回 Only name/description can be updated on lookup columns;IaC plan 只要改動任一 pick 子欄位就是硬性 plan error:only name/description can be updated on lookup columns (got: pick); to change computed config delete and recreate the column (state: absent + new line)。要改 pick 就得刪除並重建該欄位,並重新處理所有引用它的設定。
pick 會在哪些情況被拒絕
- 用在 cardinality-one lookup 上:
pick is only valid on a cardinality-many lookup. a cardinality-one link already yields a scalar — drop pick。 - 用在 rollup 欄上:
pick is a lookup-only field. argmax pick collapses a many-lookup; rollups aggregate instead。 - 用在 link、formula、純量、
json、attachment 與 principal 欄上(由 per-type field matrix 擋下)。 order_column不可排序:pick order_column '<name>' is type <type> — argmax ordering requires an orderable stored column (integer/float/date/datetime),後面會附上目標表可排序欄位的候選清單。用文字狀態或另一個計算欄來排名,都會在這裡失敗。- 多帶任何子 key:這個物件是
extra: "forbid",打錯字會 fail-closed 回pick has unsupported field(s): [...]. pick takes exactly order_column + direction,而不是默默存下一個永遠不生效的設定。
在 IaC 中撰寫 pick
pick.order_column 是在 link 目標表上解析的 column ref,不是在近端表上;它屬於 cross-reference 欄位集合,因此 drift 是以翻譯後的值比較:
{"kind":"column","table":"orders","ref":"latest_invoice_amount","spec":{"name":"Latest invoice amount","type":"lookup","link_field":"invoices","target_column":"amount","pick":{"order_column":"issued_on","direction":"desc"}}}- 沒有寫
link_field會在 import 階段 fail-closed:pick.order_column requires link_field (picked lookup) to resolve its owning table。 - ref 不存在時,plan 階段回報
unknown column ref '<target>.<col>' (pick.order_column)。 - Export 會 fail-closed 地把內部鍵反向翻譯回 ref,因此匯出的 bundle 可以來回轉換而不洩漏原始
col_<hex>。Export 也會依跨表相依順序輸出 table(link/lookup 的目標排在引用者之前),讓匯出的 bundle 能在 differ 的單趟、依文件順序的欄位驗證下順利重新 apply。
用 fallback 補上 null 純量 lookup
fallback 會在 primary lookup 之後增加一次 defaults-table probe。它適合「有客戶折扣時使用客戶折扣,否則使用區域預設值」這類契約:
{
"name": "有效折扣",
"type": "lookup",
"link_field": "客戶方案",
"target_column": "折扣",
"fallback": {
"table_id": "44444444-4444-4444-8444-444444444444",
"match": {
"區域": "$row.區域",
"種類": "標準"
},
"target_column": "預設折扣"
}
}Defaults table 是直接參照,不是另一段 link hop。它可以與本表同 scope,也可以位於同公司中合法的較廣 scope,因此 chatroom 表可繼承 department/company defaults,不必把預設值複製到每個房間。
| 欄位 | 精確契約 |
|---|---|
table_id | Defaults-table UUID。不存在、外公司與不可到達 scope 都使用相同的 non-oracular 錯誤。 |
match | 一至兩組等值配對。Key 是 defaults table 的 stored scalar;value 是相容的 scalar literal,或 $row.<本表 stored scalar>。 |
target_column | Defaults table 上要帶出的 stored scalar 欄位。 |
只有 primary lookup 會呈現一個純量時才能使用 fallback:cardinality-one lookup,或已經用 pick 收斂的 cardinality-many lookup。一般 many lookup 會呈現清單,沒有單一 primary 值可供 fallback,因此會被拒絕。巢狀/串接 fallback 與未知子 key 也都會被拒。
執行與權限語意
Primary 值非 null 時一律優先。只有純量 primary 是真正的 null 時才執行 fallback,包括沒有 link、可讀目標 cell 的值為 null,或 picked winner 的帶出 cell 為 null。後端接著按 record ID 升冪,選出第一筆同時符合所有配對、live 且可讀的 defaults row。沒有命中,或 $row match 輸入為 null 時,lookup 維持 null。
權限拒絕永遠不會啟動 fallback。若 primary table、target row、target column、picked ordering key、defaults table、defaults row、defaults columns 或 $row 輸入對呼叫者不可見,cell 維持 primary-only/null 行為。Fallback 可以補「不存在的值」,但不能洗出呼叫者被拒絕的值。這裡保留一個刻意接受的 residual signal:能讀 defaults table 的呼叫者,可區分「primary 不存在而取得 default」與「primary 被拒絕而維持 null」。此外,唯一 target 若已 soft-delete,會依平台一貫的 deleted-as-absent 規則視為不存在/未連結,因此可能啟動 fallback。
Fallback probe 會依 defaults table 與 match tuple 去重,且所有 fallback 欄位共用一份 page-wide budget:min(5000, max(50, page row 數 × fallback 欄位數))。若病態的大 page 耗盡 budget,後端會記錄 warning;未執行的 probe 會呈現 null,因此可能低估設定的 default。
帶 fallback 的 lookup 在 v1 是 eval-only。即使 pick 原本會讓 lookup 可篩選與排序,它仍不能用於 computed_filters 或 computed sort_by。新增 fallback 時,若 saved view 依賴這類 pushdown 也會被拒。Fallback 設定與 link_field、target_column、pick 一樣,建立後不可變更。
在 IaC 中撰寫 fallback
IaC 使用可攜的 table ref table,不是 REST 的 UUID 欄位 table_id。Match keys 與 target_column 是 defaults table 的 refs;$row value 則是 lookup 所屬表的 refs:
{"kind":"column","table":"orders","ref":"effective_discount","spec":{"name":"Effective discount","type":"lookup","link_field":"customer_plan","target_column":"discount","fallback":{"table":"discount_defaults","match":{"region":"$row.region","kind":"standard"},"target_column":"default_discount"}}}Plan 會 fail closed 地解析所有 refs,並以正規化值比較 drift。Export 會做反向翻譯,因此輸出的是 fallback.table 與 authored column refs,不會帶出環境限定 ID 或原始 col_<hex> key。
Lookup 與 formula 的差別
| 問題 | lookup | formula |
|---|---|---|
| 資料從哪裡來 | 沿本表 link 讀目標列 | 對欄位引用執行運算 |
| 設定 | link_field + target_column(可選 pick 和/或 fallback) | expression |
| 是否原樣帶出目標值 | 是 | 否,回傳運算結果 |
| many link | 回陣列;帶 pick 時回單一純量;fallback 要求純量形式 | 跨表引用只接受 one link |
| 可否寫入 | 否 | 否 |
需要「顯示客戶 Email」時用 lookup;需要「信用額度乘以風險係數」時用 formula,並引用數值 lookup 或使用公式的 one-link 跨表引用。
注意事項
- lookup 不接受
aggregation、filter或expression。除了必填的link_field與target_column,cardinality-many link 上可帶pick={order_column, direction};cardinality-one 或 picked-many lookup 可帶fallback={table_id, match, target_column}。 - Lookup 設定建立後不可變更。
PATCH columns/{column_id}只能修改name或description;要改link_field、target_column、pick、fallback或型別,必須刪除並重建欄位。 - lookup 不可
required、不可有default_value或max_length,記錄 payload 的非null值會被拒。 - one link 回純量;many link 回陣列,除非
pick把它收斂。cardinality-one lookup 與 picked many lookup 一般都可用於計算欄排序/篩選,也可當 formula 運算元;若要當數值 formula 的輸入,lookup 目標必須是數值。加入fallback後,該 lookup 不可做 computed filter 或 sort。未帶pick的 many lookup 仍然不可篩選、不可排序,也不能當 formula 運算元。 - 呼叫者完全無權讀取目標表時,值為
null且欄位標記restricted: true;逐列 ACL 不可讀時則只遮蔽對應值。 - 當
pick.order_column對某位讀者是隱藏欄時,欄位 ACL 會讓整個 picked cell 失效,即使被帶出的target_column是可見的 — 因為排名鍵本身就是一種排序 oracle。這就是 picked lookup 可能對非管理者回null的原因。 - 任何 primary 權限拒絕都不會執行 fallback。只有真正 primary null 時,才按 ID 選第一筆 live、可讀的 defaults row;fallback-side 或
$row輸入受限時會抑制 probe。 pick.order_column是跨表相依:在目標表上刪除它、或跨型別家族改型別,都會像 lookup 的target_column一樣被409擋下。- Defaults-side 的
fallback.matchkeys、fallback.target_column,以及每個透過$row引用的本表欄位也都是相依;刪除或不相容 retype 前,先移除或重建 lookup。
試試看
先用連結與彙總流程精靈建立 link,再到 API Playground 以 columns.preview 驗證 lookup 的 target_column。