Пакет · cdo

Плейсхолдеры апсерта

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 условное обновление

Разобранный пример

php
$cdo->upsertGroup('inventory', $items,
  conflictColumns: ['warehouse_id', 'product_id'],
  updateColumns: [
      'cost'       => ':new',
      'quantity'   => ':current + :new',
      'updated_at' => 'NOW()',
  ]
);

На PostgreSQL это становится:

sql
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, когда существующая строка была обновлена или проигнорирована, а не вставлена. Обработку см. в руководстве по апсертам.

Связанное