Доктрина: ОБ ДУБЛИКАТЕ КЛЮЧЕВОЕ ОБНОВЛЕНИЕ - PullRequest
13 голосов
/ 29 декабря 2010

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

Ответы [ 9 ]

15 голосов
/ 24 июня 2014

для 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();
7 голосов
/ 30 декабря 2010

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

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

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

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

2 голосов
/ 18 июля 2014

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

Что вы можете сделать, это подражать тому, что делает 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);
  }
});

Если запись существует PESSIMISTIC_WRITE удостоверяется, что она не обновляется никем (например, другими потоками), пока мы ее обновляем.

Несмотря на то, что вам нужно проверять существование сущности при каждом обновлении, это просто воспроизведение «обновите, если существует, и создайте, если нет».

Как указано в комментариях это не предотвращает состояние гонки, если запись не существует : если строка с одинаковыми ключами вставляется между выбранной и вставленной вами ' повторяется исключение дубликата ключа.

Но, учитывая ограничения, которые должны быть независимы от БД и, следовательно, написаны с использованием Doctrine и без использования собственного SQL, в некоторых случаях это может помочь.

Ссылки:

1 голос
/ 15 декабря 2017

Я создал доктрину dbal wrapper для этого. Может использоваться с DoctrineBundle с опцией dbal wrapper_class.

https://github.com/iJanki/doctrine-mysql-dbal-extensions

1 голос
/ 16 сентября 2015

Вы можете использовать функцию, подобную этой, для построения и выполнения 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();
}
1 голос
/ 10 мая 2014

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

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

0 голосов
/ 24 марта 2018

У вас есть три опции.

Опция first предназначена для перехода на SQL.Затем вы можете использовать все функции, которые предоставляет выбранная вами СУБД.Но многие программисты не хотят переходить на SQL без крайней необходимости.

Опция second заключается в блокировке строки related в другой таблице.Например, если объект, который вы вставляете, имеет уникальный ключ для пользователя , вы можете заблокировать пользователя, для которого вы вставляете / обновляете объект.Проблема этого решения в том, что оно не работает для корневых объектов, таких как User, потому что вы не можете заблокировать строку, которая еще не существует .

третий вариант - просто поймать ошибку / исключение дубликата ключа.То есть вы не проверяете, существует ли строка с определенным ключом;вместо этого вы просто пытаетесь вставить его.Если это удастся, все хорошо.Если произойдет сбой с ошибкой / исключением дублирующего ключа, вы перехватываете его и обновляете существующую строку.Это лучшее решение, потому что оно избегает дополнительных запросов SELECT перед каждой вставкой, что приводит к постоянным издержкам при низкой вероятности попадания в состояние гонки.И это лучшее, потому что оно работает как для корневых, так и для некорневых объектов.

0 голосов
/ 09 марта 2016

В случае, если это поможет, вы можете расширить построитель запросов для добавления произвольного 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;
    }
}
0 голосов
/ 07 июня 2015

Я написал простое решение для меня.Просто создал класс 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]);
...