You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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方法,都出现以下问题:

  1. 首次执行可创建记录,但后续查询该记录始终返回null
  2. 后续执行触发创建逻辑时,抛出唯一约束错误:

"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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:43:24