Prisma操作Summary记录时遇唯一约束失败及查询异常
问题:Prisma + Postgres 实现Summary记录「存在则更新,不存在则创建」失败
背景Schema
model Member { id String @id @default(uuid()) token String @unique @default(uuid()) summary Summary[] } model Summary { id String @id @default(uuid()) date DateTime @db.Date activityTime Int memberToken String member Member @relation(fields: [memberToken], references: [id]) @@unique([date, memberToken]) }
问题现象
无论用「先查询再判断」还是直接调用upsert方法,都出现以下问题:
- 首次执行可创建记录,但后续查询该记录始终返回
null - 后续执行触发创建逻辑时,抛出唯一约束错误:
"Invalid
prisma.summary.create()invocation:
Unique constraint failed on the fields: (date,memberToken)"
先查询再判断的代码
const summary = await prismadb.summary.findUnique({ where: { date_memberToken: { date, memberToken: payload.token, }, }, }); console.log({summary}); // 即使记录已创建仍返回null if (summary) { await prismadb.summary.update({ where: { id: summary.id, }, data: { activityTime: { increment: timeSpentInc, }, }, }); } else { await prismadb.summary.create({ data: { date, activityTime: timeSpentInc, member: { connect: { token: payload.token, }, }, }, }); }
Upsert代码
await prismadb.summary.upsert({ where: { date_memberToken: { memberToken: payload.token, date, }, }, update: { activityTime: { increment: timeSpentInc, }, }, create: { date, activityTime: timeSpentInc, member: { connect: { token: payload.token, }, }, }, });
问题根源
核心是日期类型不匹配:
- Schema中
Summary.date定义为DateTime @db.Date,对应Postgres的date类型(仅存储年月日) - 代码中传入的
date参数大概率是带时分秒的Date对象,创建记录时Prisma会自动截断时分秒存储,但查询时用原始带时分秒的Date作为条件,无法匹配到已存储的纯日期记录 - 这就导致每次执行都误判记录不存在,重复触发创建逻辑,最终触发唯一约束报错
解决方案
确保查询和创建时使用的date与Postgres存储的date类型一致(仅含年月日):
方法1:转换日期为纯格式
将传入的Date对象转换为仅含年月日的字符串或纯日期对象:
// 转换为YYYY-MM-DD格式字符串(推荐,避免时区问题) const pureDateStr = date.toISOString().split('T')[0]; // 或者转换为仅含年月日的Date对象 const pureDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
之后在所有使用date的地方替换为处理后的pureDateStr或pureDate。
方法2:使用范围查询匹配日期
如果不想转换参数,可通过Prisma的日期过滤函数匹配当天所有记录:
const summary = await prismadb.summary.findFirst({ where: { memberToken: payload.token, date: { gte: new Date(date.getFullYear(), date.getMonth(), date.getDate()), lt: new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1), }, }, });
注意这里使用findFirst而非findUnique,因为属于范围查询。
修正后的Upsert示例
const pureDateStr = date.toISOString().split('T')[0]; await prismadb.summary.upsert({ where: { date_memberToken: { memberToken: payload.token, date: pureDateStr, }, }, update: { activityTime: { increment: timeSpentInc, }, }, create: { date: pureDateStr, activityTime: timeSpentInc, member: { connect: { token: payload.token, }, }, }, });
额外注意点
- 时区处理:跨时区场景建议统一使用UTC时间处理日期,避免时区转换导致的匹配偏差
- 参数校验:确保传入的
date是有效Date对象,避免无效日期引发的匹配失败
内容的提问来源于stack exchange,提问作者Denis L
相关产品推荐
相关产品推荐

