Плейсхолдеры апсерта
upsert() и upsertGroup() принимают карту updateColumns вида
колонка => выражение. Внутри каждого выражения два токена подставляются
драйвер-специфичным SQL. Эта страница — точный справочник по подстановке.
Два токена
| Токен | Значение | PostgreSQL | MySQL / MariaDB |
|---|---|---|---|
:new |
входящее (готовое к вставке) значение | EXCLUDED.column |
VALUES(column) |
:current |
значение существующей строки | table.column |
column |
Подстановка — это буквальная замена строки для колонки, которой присвоено
выражение. Для updateColumns: ['stock' => ':current + :new'] на таблице
inventory порождённый член SET такой:
| Драйвер | Результат |
|---|---|
| PostgreSQL | stock = inventory.stock + EXCLUDED.stock |
| MySQL / MariaDB | stock = VALUES(stock) + stock |
Обёртка диалекта
Полный запрос, который CDO выдаёт вокруг членов SET:
| Драйвер | updateColumns пуст |
updateColumns задан |
|---|---|---|
| PostgreSQL | INSERT … ON CONFLICT (cols) DO NOTHING |
INSERT … ON CONFLICT (cols) DO UPDATE SET … |
| MySQL / MariaDB | INSERT IGNORE INTO … |
INSERT … ON DUPLICATE KEY UPDATE … |
Текст выражения — дословный SQL
Подставляются только :new и :current; остальная часть каждого выражения
пишется в запрос без изменений. Никогда не стройте ключи или выражения
updateColumns из пользовательского ввода. Значения строк (из сущности)
всегда привязываются безопасно.
Шаблоны выражений
Допустимо любое SQL-выражение; токены необязательны. Частые:
| Выражение | Эффект |
|---|---|
:new |
заменить входящим значением |
:current + :new |
прибавить входящее к текущему |
:current - :new |
вычесть входящее из текущего |
GREATEST(:current, :new) |
оставить большее из двух |
LEAST(:current, :new) |
оставить меньшее из двух |
COALESCE(:new, :current) |
взять входящее, иначе оставить текущее |
NOW() |
установить в текущую метку времени (без токена) |
CASE WHEN :new < :current THEN NOW() ELSE min_price_date END |
условное обновление |
Разобранный пример
$cdo->upsertGroup('inventory', $items,
conflictColumns: ['warehouse_id', 'product_id'],
updateColumns: [
'cost' => ':new',
'quantity' => ':current + :new',
'updated_at' => 'NOW()',
]
);На PostgreSQL это становится:
INSERT INTO inventory (warehouse_id, product_id, cost, quantity, updated_at)
VALUES (...), (...)
ON CONFLICT (warehouse_id, product_id) DO UPDATE SET
cost = EXCLUDED.cost,
quantity = inventory.quantity + EXCLUDED.quantity,
updated_at = NOW()О возвращаемом значении
upsert() возвращает первичный ключ через RETURNING только на PostgreSQL.
На MySQL/MariaDB возвращается lastInsertId(), равный null, когда существующая
строка была обновлена или проигнорирована, а не вставлена. Обработку см. в
руководстве по апсертам.
Связанное
- Апсерты — руководство, ориентированное на задачи
- API CDO — сигнатуры
upsert/upsertGroup - Определение драйвера — почему диалекты различаются