如何在Prisma中查询因唯一键约束失败的已有注册记录?
竞赛注册重复提交时查询已有记录的问题
我开发了一个用户竞赛注册表单,用户提交姓名和邮箱后,注册信息会存入以contestId和clientId为复合主键的Registration表。当用户重复提交同一竞赛的注册时,Prisma会触发唯一键约束错误(错误码P2002),这符合预期,但我需要返回已存在的注册记录,却无法正确构建查询语句。
Prisma Schema 定义
model Client { id Int @id @default(autoincrement()) email String @unique first String? last String? registrations Registration[] } model Contest { id Int @id @default(autoincrement()) name String @unique registrations Registration[] } model Registration { contest Contest @relation(fields: [contestId], references: [id]) contestId Int contestant Client @relation(fields: [clientId], references: [id]) clientId Int points Int @@id ([contestId, clientId]) }
现有注册代码
try { const registration = await prisma.registration.create({ data: { points: 1, contest: { connectOrCreate: { where: { name: contest, }, create: { name: contest, } } }, contestant: { connectOrCreate: { where: { email: email, }, create: { first: first, last: last, email: email, }, }, }, } }); return res.status(201).send({ ...registration}); }
新用户注册正常,但重复注册会进入catch块。我认为这种先创建再捕获错误的方式比先查询是否存在更高效(重复注册概率低),也欢迎其他最佳实践建议。
失败的查询尝试
在catch块中尝试两种查询方式均失败:
第一种尝试及报错
catch (error) { // entry already exists if ('P2002' === error.code) { const registration = await prisma.registration.findUnique({ where: { contest: { is: { name: contest, }, }, contestant: { is: { email: email, }, }, }, }) return res.status(200).send(...registration); } return res.status(500).end(`Register for contest error: ${error.message}`); }
报错:
Argument where of type RegistrationWhereUniqueInput needs exactly one argument, but you provided contest and contestant.
第二种尝试及报错
const registration = await prisma.registration.findUnique({ where: { contestId_clientId: { contest: { is: { name: contest, }, }, contestant: { is: { email: email, }, }, }, }, })
报错:
Unknown arg `contest` in where.contestId_clientId.contest for type RegistrationContestIdClientIdCompoundUniqueInput. Did you mean `contestId`? Unknown arg `contestant` in where.contestId_clientId.contestant for type RegistrationContestIdClientIdCompoundUniqueInput. Did you mean `contestId`? Argument contestId for where.contestId_clientId.contestId is missing. Argument clientId for where.contestId_clientId.clientId is missing.
我觉得使用Prisma自动生成的contestId_clientId复合主键查询是正确方向,但不知道如何通过竞赛名称和用户邮箱构建正确的查询语句?
解决方案
方法1:先获取关联ID,再用复合主键查询
复合主键contestId_clientId要求传入具体的contestId和clientId,因此需要先通过竞赛名称、用户邮箱查询对应的ID,再执行findUnique:
catch (error) { if ('P2002' === error.code) { // 并行查询竞赛和用户的ID const [targetContest, targetClient] = await Promise.all([ prisma.contest.findUnique({ where: { name: contest } }), prisma.client.findUnique({ where: { email: email } }) ]); if (targetContest && targetClient) { const existingRegistration = await prisma.registration.findUnique({ where: { contestId_clientId: { contestId: targetContest.id, clientId: targetClient.id } }, // 可选:关联查询竞赛和用户信息,返回更完整的数据 include: { contest: true, contestant: true } }); return res.status(200).send(existingRegistration); } // 理论上不会走到这里(create阶段已保证contest和client存在) return res.status(404).send('相关竞赛或用户记录不存在'); } return res.status(500).send(`Register for contest error: ${error.message}`); }
方法2:用findFirst直接通过关联条件查询
如果不需要严格使用findUnique,可以用findFirst结合关联模型的条件查询,更简洁:
catch (error) { if ('P2002' === error.code) { const existingRegistration = await prisma.registration.findFirst({ where: { contest: { name: contest }, contestant: { email: email } }, include: { contest: true, contestant: true } }); return existingRegistration ? res.status(200).send(existingRegistration) : res.status(404).send('注册记录不存在'); } return res.status(500).send(`Register for contest error: ${error.message}`); }
最佳实践补充
你采用的先创建再捕获唯一键错误的方案是合理的,尤其适用于重复注册概率低的场景,避免了额外的前置查询开销。如果后续需要支持"重复提交时更新现有记录"的需求,可以考虑使用Prisma的upsert方法,但当前场景下你的方案更贴合需求。
内容的提问来源于stack exchange,提问作者philolegein
相关产品推荐
相关产品推荐

