Пакет · cdo

Апсерты

Апсерт вставляет строку или обновляет существующую при возникновении уникального конфликта. CDO пишет за вас нужный диалект — ON CONFLICT на PostgreSQL, ON DUPLICATE KEY UPDATE на MySQL/MariaDB — и даёт два переносимых токена, :new и :current, для описания обновления.

Вставить-или-игнорировать

Передайте колонки конфликта и никаких колонок обновления, чтобы пропускать существующие строки:

php
// 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
php
$cdo->upsert('products',
  ['sku' => 'ABC-001', 'name' => 'Widget', 'price' => 9.99, 'stock' => 50],
  conflictColumns: ['sku'],
  updateColumns: [
      'name'  => ':new',              // заменить входящим значением
      'price' => ':new',
      'stock' => ':current + :new',   // прибавить к существующему значению
  ]
);

Выражение, не ссылающееся ни на один токен, выдаётся дословно, поэтому SQL-функции работают напрямую:

php
updateColumns: [
  'quantity'   => ':current + :new',
  'updated_at' => 'NOW()',
]

updateColumns не привязывается

Строки колонка => выражение пишутся в SQL как есть (подставляются только :new / :current). Никогда не стройте их из пользовательского ввода. Значения строк по-прежнему привязываются безопасно; дословным является только текст выражения. Полные правила подстановки см. в справочнике плейсхолдеров апсерта.

Пакетный апсерт

upsertGroup() апсертит много строк с чанкованием, по умолчанию 500 строк на запрос:

php
$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()

Связанное