Features / Query

Upsert rows with upsert()

Insert a row when its conflict key is new, or update the existing row when that key already exists.

Enable it

upsert() is generated for PostgreSQL and SQLite:

import 'drizzle-plus/pg/upsert'
// or
import 'drizzle-plus/sqlite/upsert'

MySQL is not supported by this extension.

The basic operation

When the inserted data contains a primary key or unique column, drizzle-plus uses that constraint as the conflict target:

const user = await db.query.user.upsert({
  data: {
    id: 42,
    name: 'Chewbacca',
  },
})
// user: the full upserted row

For a single object, the result is one row. For an array, the result is an array in the same operation:

const users = await db.query.user.upsert({
  data: [
    { id: 42, name: 'Chewbacca' },
    { id: 43, name: 'Han Solo' },
  ],
  returning: {
    id: true,
    name: true,
  },
})
// users: { id: number; name: string }[]

Choose the conflict target

By default, the target is inferred from the columns defined in the first row of data. Use target when the row contains more than one possible key or when a different unique constraint should decide the conflict:

await db.query.user.upsert({
  data: {
    id: 43,
    handle: 'chewie',
  },
  target: ['handle'],
})

The target must name a primary key, unique constraint, or unique index that exists on the table. If no matching constraint can be found, the query throws before it is executed.

Return only the fields you need

returning accepts a selection object or a function that receives the table columns. SQL expressions are allowed in the selection:

import { upper } from 'drizzle-plus'

const user = await db.query.user.upsert({
  data: { id: 42, name: 'Chewbacca' },
  returning: user => ({
    id: true,
    nameUpper: upper(user.name),
  }),
})

If returning is omitted, all columns are returned. An empty object asks the database for no returned fields.

Update with different data

Use update when the values to insert are not the values to write on a conflict. The callback receives current columns from the existing row and excluded columns from the incoming row:

import { sql } from 'drizzle-orm'

const user = await db.query.user.upsert({
  data: {
    id: 42,
    loginCount: 0,
  },
  update: ({ current }) => ({
    loginCount: sql`${current.loginCount} + 1`,
  }),
})

To reuse incoming values, read them from excluded:

await db.query.user.upsert({
  data: { id: 42, name: 'Chewie' },
  update: ({ excluded }) => ({
    name: excluded.name,
  }),
  updateWhere: {
    // Only update a matching row when this condition is true.
    emailVerified: true,
  },
})

updateWhere limits the update part of the operation. It does not change how the conflict target is selected.

Insert from a query

For many-row upserts, data can also be an insert-select query or a compatible subquery. This is useful when the incoming rows already live in the database; see aliases for the subquery shape.