Апсерты
Апсерт вставляет строку или обновляет существующую при возникновении уникального
конфликта. CDO пишет за вас нужный диалект — ON CONFLICT на PostgreSQL,
ON DUPLICATE KEY UPDATE на MySQL/MariaDB — и даёт два переносимых токена,
:new и :current, для описания обновления.
Вставить-или-игнорировать
Передайте колонки конфликта и никаких колонок обновления, чтобы пропускать существующие строки:
// PostgreSQL: ON CONFLICT (email) DO NOTHING
// MySQL/MariaDB: INSERT IGNORE
$cdo->upsert('users',
['email' => 'alice@example.com', 'name' => 'Alice'],
conflictColumns: ['email']
);conflictColumns должен называть колонку(и), определяющую уникальность. Передача
пустого массива бросает CDOException.
Вставить-или-обновить
Добавьте updateColumns — карту колонка => выражение — чтобы обновлять при
конфликте. Выражения используют два плейсхолдера:
| Токен | Значит | PostgreSQL | MySQL / MariaDB |
|---|---|---|---|
:new |
входящее значение | EXCLUDED.column |
VALUES(column) |
:current |
значение существующей строки | table.column |
column |
$cdo->upsert('products',
['sku' => 'ABC-001', 'name' => 'Widget', 'price' => 9.99, 'stock' => 50],
conflictColumns: ['sku'],
updateColumns: [
'name' => ':new', // заменить входящим значением
'price' => ':new',
'stock' => ':current + :new', // прибавить к существующему значению
]
);Выражение, не ссылающееся ни на один токен, выдаётся дословно, поэтому SQL-функции работают напрямую:
updateColumns: [
'quantity' => ':current + :new',
'updated_at' => 'NOW()',
]updateColumns не привязывается
Строки колонка => выражение пишутся в SQL как есть (подставляются только
:new / :current). Никогда не стройте их из пользовательского ввода. Значения
строк по-прежнему привязываются безопасно; дословным является только текст
выражения. Полные правила подстановки см. в
справочнике плейсхолдеров апсерта.
Пакетный апсерт
upsertGroup() апсертит много строк с чанкованием, по умолчанию 500 строк на
запрос:
$cdo->upsertGroup('inventory', $items,
conflictColumns: ['warehouse_id', 'product_id'],
updateColumns: [
'cost' => ':new',
'quantity' => ':current + :new',
'updated_at' => 'NOW()',
],
chunkSize: 500
);Как и insertGroup, пустой входной массив — no-op; пустой conflictColumns
бросает исключение.
Возвращаемые значения
upsert()возвращает первичный ключ только на PostgreSQL (черезRETURNING). На MySQL/MariaDB возвращаетсяlastInsertId(), который равенnull, когда новая строка не была вставлена (т.е. существующая строка была обновлена или проигнорирована).upsertGroup()ничего не возвращает.
Переносимость возвращаемого id
Поскольку RETURNING при апсерте здесь только для PostgreSQL, не полагайтесь на
возвращаемое значение upsert() как на стабильный id между драйверами. Если id
нужен на MySQL/MariaDB, прочитайте его повторным запросом по колонкам конфликта.
Частые шаблоны выражений
| Цель | Выражение |
|---|---|
| Заменить новым значением | :new |
| Прибавить к текущему | :current + :new |
| Оставить большее | GREATEST(:current, :new) |
| Оставить меньшее | LEAST(:current, :new) |
| Новое значение или оставить текущее | COALESCE(:new, :current) |
| Проставить время изменения | NOW() |
Связанное
- Плейсхолдеры апсерта — полный справочник токенов
- Вставка записей — обычные вставки и пакеты
- Пакеты и чанки — как строятся чанкованные запросы
- API CDO — сигнатуры
upsert/upsertGroup