Supabase Upsert在带默认值的非空字段上执行失败求助
Supabase Upsert混合操作触发created_at非空约束错误的解决
问题背景
使用以下代码执行Supabase Upsert操作:
export const updateChecklistData = async (values: BlueprintChecklistInsert[]) => { const { error } = await supabase.getClient().from('blueprint_checklist').upsert(values, { onConflict: 'id' }); if (error) { throw error; } };
- 单独插入新数据(无
created_at字段)时操作正常:
{ "title": "Keyboard", "blueprint_id": "f373e251-da55-4d3d-b4f4-69b4c7b26c7a", "id": "030d70a9-e681-476d-a9c4-c3a9ef1399ec", "order": 1, "parent_id": "9a09f8c7-b255-4725-a228-e5b8f79404c1", "event_type": "creation", "workspace_id": "7db6e71c-a24c-4710-b63a-728c9413a0cf" }
- 同时更新已有记录(含
created_at)和插入新记录时,抛出错误:
null value in column "created_at" of relation "blueprint_checklist" violates not-null constraint
传入的混合数据示例:
[ { "id": "b8b1f216-13c9-446c-b2d5-84881ff5cac4", "created_at": "2024-07-27T19:10:04.526662+00:00", "title": "Laptop", "blueprint_id": "f373e251-da55-4d3d-b4f4-69b4c7b26c7a", "workspace_id": "7db6e71c-a24c-4710-b63a-728c9413a0cf", "event_type": "creation", "description": null, "order": 0, "parent_id": "9a09f8c7-b255-4725-a228-e5b8f79404c1", "remind": null, "archived": false }, { "title": "Keyboard", "blueprint_id": "f373e251-da55-4d3d-b4f4-69b4c7b26c7a", "id": "030d70a9-e681-476d-a9c4-c3a9ef1399ec", "order": 1, "parent_id": "9a09f8c7-b255-4725-a228-e5b8f79404c1", "event_type": "creation", "workspace_id": "7db6e71c-a24c-4710-b63a-728c9413a0cf" } ]
表结构定义:
create table "public"."blueprint_checklist" ( "id" uuid not null default gen_random_uuid(), "created_at" timestamp with time zone not null default now(), -- 其他字段省略 );
created_at为非空字段,默认值为now()。
原因分析
Supabase的upsert默认采用合并更新逻辑,当批量操作中部分记录包含created_at、部分不包含时,PostgreSQL会将created_at视为需要统一处理的字段。对于未传入created_at的新记录,系统不会自动使用表的默认值,而是将其视为null,触发非空约束。
解决方案
方案1:添加defaultToNull: false配置(推荐)
在upsert选项中设置defaultToNull: false,让未传入的字段自动使用表定义的默认值,而非设为null:
export const updateChecklistData = async (values: BlueprintChecklistInsert[]) => { const { error } = await supabase.getClient().from('blueprint_checklist').upsert(values, { onConflict: 'id', defaultToNull: false }); if (error) { throw error; } };
方案2:手动给新记录补充created_at值
遍历批量数据,为缺失created_at的记录补充当前时间:
export const updateChecklistData = async (values: BlueprintChecklistInsert[]) => { const processedValues = values.map(item => item.created_at ? item : { ...item, created_at: new Date().toISOString() } ); const { error } = await supabase.getClient().from('blueprint_checklist').upsert(processedValues, { onConflict: 'id' }); if (error) { throw error; } };
方案3:拆分插入和更新操作
将新记录和已有记录分开处理,先插入新记录(自动使用默认值),再更新已有记录:
export const updateChecklistData = async (values: BlueprintChecklistInsert[]) => { const existing = values.filter(item => item.created_at); const newItems = values.filter(item => !item.created_at); if (newItems.length) { const { error: insertErr } = await supabase.getClient().from('blueprint_checklist').insert(newItems); if (insertErr) throw insertErr; } if (existing.length) { const { error: updateErr } = await supabase.getClient().from('blueprint_checklist').upsert(existing, { onConflict: 'id' }); if (updateErr) throw updateErr; } };
内容的提问来源于stack exchange,提问作者Alwaysblue
相关产品推荐
相关产品推荐

