Подтвердить что ты не робот

Доктрина: ОБНОВЛЕНИЕ ОБНОВЛЕНИЯ DUPLICATE

Как я могу написать запрос доктрины INSERT с опцией ON DUPLICATE KEY UPDATE?

4b9b3361

Ответ 1

Проблема в том, что это специфическая проблема MySQL, поэтому она не будет напрямую покрыта Doctrine.

В качестве упомянутого комментария вам нужно будет написать запрос RawSQL для этого. Это было бы самым простым способом.

Если вы хотите, чтобы он был более сложным и действительно независимым от DB, загляните в События и его возможности. Перед выполнением фактического запроса вы можете проверить наличие и, если оно существует, действовать соответственно.

Независимым способом ORM/PHP является запись хранимой процедуры/триггера, которая обрабатывает эту сторону базы данных проблем.

Ответ 2

для Symfony 2 используйте raw sql:

$em->getConnection()->prepare("INSERT INTO table SET 
    some_fields = "some data", created_at = NOW() 
    ON DUPLICATE KEY UPDATE
    some_fields = "some data", updated_at = NOW()
")->execute();

Ответ 3

Вы не можете. Это не поддерживается доктриной прямо сейчас.

Что вы можете сделать, это подражать тому, что делает MySQL, проверяя, существует ли сущность и обновляет/создает ее соответственно:

$em = $this->getEntityManager();

// Prevent race conditions by putting this into a transaction.
$em->transactional(function($em) use ($content, $type) {
  // Use pessimistic write lock when selecting.
  $counter = $em->createQueryBuilder()
    ->select('MyBundle:MyCounter', 'c')
    ->where('c.content = :content', 'c.type = :type')
    ->setParameters(['content' => $content, 'type' => $type])
    ->setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);
    ->getQuery()
    ->getResult()
  ;

  // Update if existing.
  if ($counter) {
    $counter->increase();
  } else {
    // Create otherwise.
    $newCounter = new Counter($content, $type, 1);
    $em->persist($newCounter);
  }
});

Хотя вам нужно проверить существование сущности при каждом обновлении, это как можно ближе к ON DUPLICATE KEY UPDATE, как вы можете получить прямо сейчас.

Транзакция и пессимистическая блокировка вместе предотвращают множественное создание одного и того же объекта в параллельных средах, как это делает ON DUPLICATE KEY UPDATE.

Литература:

Ответ 4

У меня была такая же проблема, и после изучения немного похоже, что Doctrine этого не делает. Моим решением было сделать findBy перед моей вставкой, чтобы увидеть, существуют ли какие-либо записи с уникальными полями. Если это возвращает объект, я обновляю этот объект и сохраняю его, а не создавая новый объект, который нужно сохранить.

Если вас беспокоит производительность, это не так, как мы делаем выбор перед каждой вставкой. Однако поскольку Doctrine является агностиком базы данных, это единственная альтернатива блокировке себя для MySQL. Это один из этих компромиссов: вы хотите производительность или переносимость.

Ответ 5

Вы можете использовать такую ​​функцию для создания и выполнения raw sql:

 /**
 * 
 * insertWithDuplicate('table_name', array('unique_field_name' => 'field_value', 'field_name' => 'field_value'), array('field_name' => 'field_value'))
 * 
 * @param string $tableName
 * @param array $insertData 
 * @param array $updateData
 * 
 * @return bolean
 */
public function insertWithDuplicate($tableName, $insertData, $updateData) {
    $columnPart = '';
    $valuePart = '';
    $columnAndValue = '';
    foreach ($insertData as $key => $value) {
        $value = str_replace(array('"', "'"), array('\"', "\'"), $value);
        $columnPart .= "`" . $key . "`" . ',';
        is_numeric($value) ? $valuePart .= $value . ',' : $valuePart .= "'" . $value . "'" . ',';
    }
    foreach ($updateData as $key => $value) {
        $value = str_replace(array('"', "'"), array('\"', "\'"), $value);
        is_numeric($value) ? $columnAndValue .= $key . ' = ' . $value . ',' : $columnAndValue .= "`" . $key . "`" . ' = ' . "'" . $value . "'" . ',';
    }
    $_columnPart = substr($columnPart, 0, strlen($columnPart) - 1);
    $_valuePart = substr($valuePart, 0, strlen($valuePart) - 1);
    $_columnAndValue = substr($columnAndValue, 0, strlen($columnAndValue) - 1);
    $query = "INSERT INTO " . $tableName .
            " (" . $_columnPart . ") "
            . "VALUES" .
            " (" . $_valuePart . ") "
            . "ON DUPLICATE KEY UPDATE " .
            $_columnAndValue;
    return $this->entityManager->getConnection()
                    ->prepare($query)->execute();
}

Ответ 7

Я написал для меня простое решение. Только что созданный класс AbstractRepository, который является родительским классом всех репозиториев (например, UserRepository) и создал следующий метод:

 public function onDuplicateUpdate($insertFields, $updateFields)
    {
        $table = $this->getEntityManager()->getClassMetadata($this->getEntityName())->getTableName();
        $sql = 'INSERT INTO '.$table;
        $sql .= '(`'.implode('`,`', array_flip($insertFields)).'`) ';
        $sql .= 'VALUES("'.implode('","', $insertFields).'") ';
        $sql .= 'ON DUPLICATE KEY UPDATE ';
        foreach($updateFields as $column => $value) {
            $sql .= '`'.$column . '` = "'. $value.'"';
        }
        $stmt = $this->getEntityManager()->getConnection()->prepare($sql);
        $stmt->execute();
    }

Вы можете использовать этот код следующим образом:

$this->getEntityManager()
           ->getRepository('User')
           ->onDuplicateUpdate(['column1' => 'user_reminder_1', 'column2' => 235], ['column2' => 255]);

Ответ 8

В случае, если это поможет, вы можете расширить построитель запросов, чтобы добавить произвольный SQL (очевидно, это может не работать с PDO-движками):

class MyQB extends QueryBuilder {

    private $append = '';

    /**
     * {@inheritdoc}
     */
    public function getSQL() {
        return parent::getSQL() . $this->append;
    }

    /**
     * Append raw SQL to the output query
     *
     * @param string $sql SQL to append. E.g. "ON DUPLICATE ..."
     *
     * @return self
     */
    public function appendSql($sql) {
        $this->append = $sql;
        return $this;
    }
}