docs icon indicating copy to clipboard operation
docs copied to clipboard

Explain how `upsert` can behave as a hypothetical `findOrCreate`

Open thebiglabasky opened this issue 4 years ago • 8 comments

Problem

Currently, people are wondering about having a findOrCreate method in the API (https://github.com/prisma/prisma-client-js/issues/85). We also have recently introduced connectOrCreate as an experimental feature (#568).

It happens that upsert behaves like a hypothetical findOrCreate method when receiving an empty update parameter. There has been a decision to avoid adding shortcuts in the API to already supported methods to avoid unnecessarily expand the surface to test, but having a documentation update would help people figuring that out.

Suggested solutions

  • We update the upsert documentation to cover the usage with an empty update parameter.
  • Optionally (probably a bad idea): we add a findOrCreate section which explains one should use upsert as documented above to have this behavior

thebiglabasky avatar Jul 10 '20 11:07 thebiglabasky

Would be nice to cover the usage with an empty update parameter in docs. Also having findOrCreate which is a common keyword for that would help find it through search and Google

Jolg42 avatar Jan 13 '21 13:01 Jolg42

Example

const user = await prisma.user.upsert({
  where: { email: '[email protected]' },
  update: {},
  create: { email: '[email protected]' },
})

Jolg42 avatar Jan 13 '21 13:01 Jolg42

Using it for a idempotent seed script https://github.com/Jolg42/prisma-seed-example/blob/main/javascript/prisma/seed.js https://github.com/Jolg42/prisma-seed-example/blob/main/typescript/prisma/seed.ts

Jolg42 avatar Jan 13 '21 14:01 Jolg42

What about when you want to findOrCreate based on non-unique input? (For example, finding or creating a car based on the numberplate and country.)

An upsert doesn't work, because the where only takes unique input. I would want to do something like this:

const car = await prisma.car.findOrCreate({
  where: { licensePlate: 'ABC123', country: 'NZ' }
})

olliechick avatar Jan 29 '21 00:01 olliechick

I have the same question that @olliechick asked above

codeslayer1 avatar Feb 03 '21 10:02 codeslayer1

Hey @olliechick, if license plates are scoped to country, then you can use a composite unique and then use upsert.

I've created an issue for cases where that's not possible: https://github.com/prisma/prisma/issues/5436

matthewmueller avatar Feb 03 '21 12:02 matthewmueller

There is a slight hump about this. When doing an update without any update body, the updatedAt field does not get updated. This is expected and probably desired BUT that means that it is not possible for us to tell if the retrieved entity is an existing entity or a created one. Looking for how to tell "updated vs new" led me to: https://github.com/prisma/prisma/discussions/3432 which explicitly suggests looking at the updatedAt field.

I could manually add updatedAt to the update body but that feels wrong, all I want is to find or create, and to know if it was found or created.

nrxus avatar Feb 12 '21 23:02 nrxus

What @nrxus mentioned is a good point on how upsert can't actually behave as findOrCreate. We are stuck with findFirst/findUnique + update in our controllers if we need to return the correct status code.

Thore1954 avatar Oct 09 '22 14:10 Thore1954

There is a slight hump about this. When doing an update without any update body, the updatedAt field does not get updated. This is expected and probably desired BUT that means that it is not possible for us to tell if the retrieved entity is an existing entity or a created one. Looking for how to tell "updated vs new" led me to: prisma/prisma#3432 which explicitly suggests looking at the updatedAt field.

I could manually add updatedAt to the update body but that feels wrong, all I want is to find or create, and to know if it was found or created.

Same feeling here. Im stuck with looking for a good way to identify if the record retuner was found or created.

lukasver avatar Nov 20 '22 18:11 lukasver

What about when you want to findOrCreate based on non-unique input? (For example, finding or creating a car based on the numberplate and country.)

An upsert doesn't work, because the where only takes unique input. I would want to do something like this:

const car = await prisma.car.findOrCreate({
  where: { licensePlate: 'ABC123', country: 'NZ' }
})

I think this use case is the most compelling case for a separate findOrCreate function - since findOrCreate wouldn't need to worry about supporting update, it would be able to use findFirst's where clause instead of update's.

owensj-basis avatar Dec 13 '22 18:12 owensj-basis

Agreed! findOrCreate would be super handy.

magoz avatar Dec 26 '22 19:12 magoz

2 years later and still no solution 🥲

stellenberger avatar Feb 02 '23 14:02 stellenberger

I'm with you, I just don't understand the hesitancy around adding it in. findOrCreate is similar but different than upsert :')

Thexumaker avatar Feb 11 '23 07:02 Thexumaker

Just saw this was closed and got super excited, only to find out they just added a warning to the docs 🙄. The whole point of this thread is that upsert doesn't have the flexibility of any of the find queries so it doesn't really do well as a workaround. Very disappointed in this outcome

owensj-basis avatar Apr 11 '23 15:04 owensj-basis

Hi @owensj-basis

Sorry, we haven't been able to prioritize this feature. To clarify, though, it's only the docs issue that is being closed, as the current behavior of upsert was documented.

The feature request for a dedicated findOrCreate is still open: https://github.com/prisma/prisma/issues/5436

TasinIshmam avatar Apr 11 '23 15:04 TasinIshmam

I see, apologies for the negativity

owensj-basis avatar Apr 11 '23 15:04 owensj-basis

I don't know who this may be helpful to, but it's the closest solution I found is to use connectOrCreate :)

But as it states, it's for connecting models together, but there's the findOrCreate element there, for those who want it

stellenberger avatar Apr 11 '23 16:04 stellenberger

Does this work atomically?

For example, I have the following code:

    const user = await prisma.user.upsert({
      where: { id: userId },
      update: {},
      create: { id: userId, name: name },
    });

I think if I call this function twice when there is no user with userId, then do Promise.all on the two resulting promises, I get an exception:

Invalid `prisma.user.upsert()` invocation:
Unique constraint failed on the fields: (`id`)

I think this is happening because each promise is first doing the find, then doing the create. If both happened at the same time, the exception would be impossible.

ishaangandhi avatar Mar 28 '24 15:03 ishaangandhi