Nuxt Content 查詢 Boolean 欄位

這篇文章是自己記錄 Nuxt Content v3 的 boolean 欄位設計問題,導致 queryCollection() API 查詢資料時弄出一個大坑,如何解決。

原始狀態

我的 Nuxt Content 有一個 villages collection,schema 中原本這樣定義:

1
shown: z.boolean().optional().describe('是否於地圖顯示')

而對應的 Nuxt Content 內容 front matter 中,該欄位原則上完全不會用到,但不想顯示的 villages 資料則會增加一筆:

1
shown: False

甚至前端頁面查詢這樣寫:

1
2
// ❌ 原始查詢
queryCollection('villages').where("shown", "=", "False")

意思是「撈出等於 False 的資料」,其實邏輯完全反了,但我也不太確定怎麼寫。比如,文件上沒有 != 運算子,我也不確定 "False" 寫成正規的布林值 False 能不能動。所以就求助了一下 Claude。

AI 抓到的問題

1. 欄位命名語意不清

shown 這個名字太短、太模糊,在 schema 中閱讀時,不看 .describe() 根本不知道它的作用域是「地圖」。改成 isMapShown 更符合 boolean 欄位的命名慣例(is 前綴),語意一目了然。

2. 查詢邏輯反向

設計意圖是「撈出應顯示在地圖上的村落」,但 .where("shown", "=", "False") 的意思是「撈出等於 False 的資料」,邏輯完全相反。

3. 型別錯誤

shown 是 z.boolean(),卻拿去和字串 "False" 比較,型別不符。

4. 漏寫 .all()

queryCollection().where() 回傳的是 query builder,不是資料陣列,必須呼叫 .all() 才會真正執行查詢並回傳結果。

5. SQL NULL 三值邏輯(optional() 的隱患)

使用 optional() 時,未填寫的欄位在 SQLite 中儲存為 NULL。SQL 的三值邏輯(Three-valued Logic)中,NULL != false 的結果不是 true,而是 NULL,導致這些資料列不會出現在 != 的查詢結果中,與設計意圖相反。

解法

AI 本來建議我寫:

1
2
3
4
const villages = await queryCollection('villages')
.where('shown', '!=', false)
.orWhere('shown', 'IS NULL') // optional 欄位未填時為 NULL
.all()

但 Nuxt Content v3 的 queryCollection 底層是 SQLite,.orWhere() 未必支援。AI 給的次選項是 client-side filter ,但我嫌麻煩,請它直接教我能否從從 schema 定義消滅問題根源。

於是,AI 給了以下建議:

1
2
// ✅ 修正後的 schema 定義
isMapShown: z.boolean().default(true).describe('是否於地圖顯示')

除了欄位命名更具語意(isMapShown),改用 .default(true) 之後:

  • 每筆資料都保證有值,資料庫中不再出現 NULL
  • 只有明確寫 isMapShown: false 的資料才會被排除

前端查詢也隨之簡化:

1
2
3
4
// ✅ 修正後的查詢
await queryCollection('villages')
.where('isMapShown', '!=', false)
.all()

小結

這次遇到的問題,及其解方,大致可總結如下:

  • Boolean 欄位盡量用 is 前綴命名,並給定明確的 default 值,避免 optional() 引入 NULL
    • 設計欄位時多想一步:「這個 optional 在資料庫裡會變成什麼?」
  • queryCollection() 的 query builder 一定要以 .all()(或 .first() 等)收尾才會執行