# Atomic updates

> Update counters and flags atomically in the database.

URL: https://better-drizzle.com/docs/writing/atomic-updates

(Added in 0.3.0)

Atomic update envelopes read and write a column in one database statement. They are useful for balances, counters, retry counts, and flags that may be changed concurrently.



**better-drizzle**

```ts  
const account = await client.accounts.update({
	where: { id },
	data: {
		balance: { increment: 100 },
		loginCount: { increment: 1 },
		retries: { decrement: 1 },
		locked: { toggle: true },
	},
});
```

**Drizzle**

```ts  
const [account] = await db
	.update(accounts)
	.set({
		balance: sql`${accounts.balance} + 100`,
		loginCount: sql`${accounts.loginCount} + 1`,
		retries: sql`${accounts.retries} - 1`,
		locked: sql`not ${accounts.locked}`,
	})
	.where(eq(accounts.id, id))
	.returning();
```

## Operators

Numeric columns accept `increment`, `decrement`, `multiply`, `divide`, `set`, [`max`, and `min`](#max-and-min). Boolean columns accept `toggle: true`. Direct values remain valid.

Several numeric operators may be combined, except `max` and `min`, which stand alone. Better Drizzle always applies them in this order: `set`, `increment`, `decrement`, `multiply`, then `divide`.

```ts
await client.accounts.update({
	where: { id },
	data: { balance: { set: 10, increment: 4, multiply: 3 } }, // 42
});
```

`divide: 0`, non-finite operands, empty envelopes, and operators used on incompatible columns fail before SQL is sent. `NULL` and integer-division behavior remain those of the selected database.

## `max` and `min`

`max` keeps the larger of the column and the operand, and `min` keeps the smaller. Use them for values that only move one way, such as a high score or a stock cap.



**better-drizzle**

```ts  
await client.players.update({
	where: { id },
	data: { highScore: { max: score } },
});

await client.products.updateMany({
	where: { warehouseId },
	data: { stock: { min: maxStock } },
});
```

**Drizzle**

```ts  
// PostgreSQL and MySQL; SQLite uses max() and min()
await db
	.update(players)
	.set({ highScore: sql`greatest(coalesce(${players.highScore}, ${score}), ${score})` })
	.where(eq(players.id, id));

await db
	.update(products)
	.set({ stock: sql`least(coalesce(${products.stock}, ${maxStock}), ${maxStock})` })
	.where(eq(products.warehouseId, warehouseId));
```

| Dialect    | `{ max: v }`                    | `{ min: v }`                 |
| ---------- | ------------------------------- | ---------------------------- |
| PostgreSQL | `GREATEST(col, v)`              | `LEAST(col, v)`              |
| MySQL      | `GREATEST(COALESCE(col, v), v)` | `LEAST(COALESCE(col, v), v)` |
| SQLite     | `MAX(COALESCE(col, v), v)`      | `MIN(COALESCE(col, v), v)`   |

A `NULL` column becomes `v` on every dialect. `max` and `min` cannot be combined with each other or with other operators on the same column.

## Supported writes

Use atomic envelopes in `update`, `updateMany`, `updateEach`, `upsert`, and `upsertMany`. In `updateEach`, return the envelope from the column callback:

```ts
await client.accounts.updateEach({
	by: accounts.id,
	data: [{ id: 1, delta: 5 }],
	update: { balance: (row) => ({ increment: row.delta }) },
});
```

PostgreSQL, SQLite, and MySQL support atomic updates in every write that accepts them, including `upsertMany`.

## Hooks

After an atomic write, post-write hooks receive a readonly `compiled` object containing the final `SET` expressions. Plugins still receive the declarative envelope before execution.

```ts
const client = better(db, {
	hooks: {
		afterUpdate({ compiled }) {
			console.log(compiled);
		},
	},
});
```
