18

如何INSERT使用 option 编写学说查询ON DUPLICATE KEY UPDATE

4

9 回答 9

17

Symfony 2使用 原始 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();
于 2014-06-24T10:53:00.180 回答
7

问题是这是一个 MySQL 特定的问题,因此 Doctrine 不会直接涵盖它。

正如评论所提到的,您需要为此编写一个 RawSQL 查询。这将是最简单的方法。

如果您希望它更复杂且真正独立于数据库,请查看事件及其可能性。在执行实际查询之前,您可以检查是否存在,如果存在,则采取相应措施。

一种独立于 ORM/PHP 的方法是编写一个存储过程/触发器来处理这个问题数据库端。

于 2010-12-30T09:50:43.537 回答
4

你不能。Doctrine 目前不支持它。

您可以做的是通过检查实体是否存在并相应地更新/创建它来模仿 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确保在我们更新它时它没有被任何人(例如,其他线程)更新。

尽管您需要在每次更新时检查实体是否存在,但它是“如果存在则更新,如果不存在则创建”的简单复制。

正如评论中所指出的,如果记录不存在,这不会阻止竞争条件:如果在 select 和 insert 之间插入了具有相同键的行,则您将遇到重复键异常。

但考虑到这需要独立于数据库并因此使用 Doctrine 而不是使用本机 SQL 编写的限制,它在某些情况下可能会有所帮助。

参考:

于 2014-07-18T10:05:10.047 回答
1

我遇到了同样的问题,经过一番调查后,看起来 Doctrine 并没有这样做。我的解决方案是在插入之前执行findBy以查看是否存在具有唯一字段的任何记录。如果这返回一个实体,那么我更新该实体并将其持久化,而不是创建一个新实体来持久化。

如果您担心性能,那么这并不理想,因为我们在每次插入之前都会进行选择。然而,由于 Doctrine 与数据库无关,因此它是将自己锁定到 MySQL 的唯一选择。这是权衡之一:您想要性能还是便携性。

于 2014-05-10T13:15:17.893 回答
1

您可以使用这样的函数来构建和执行原始 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();
}
于 2015-09-16T10:03:55.867 回答
1

我创建了一个学说 dbal 包装器来做到这一点。它可以与带有 dbal wrapper_class 选项的 DoctrineBundle 一起使用。

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

于 2017-12-15T00:22:22.460 回答
1

你有三个选择。

一个选项是下拉到 SQL。然后,您可以使用您选择的 RDBMS 提供的所有功能。但是,除非绝对必要,否则许多程序员不想直接使用 SQL。

第二选项是锁定另一个表中的相关行。例如,如果您插入的实体每个用户都有一个唯一的密钥,您可以锁定您正在为其插入/更新实体的用户。此解决方案的问题在于它不适用于像User它自己这样的根实体,因为您无法锁定尚不存在的行

The third option is to just catch the duplicate key error/exception. That is, you don't check if a row with a particular key already exists; instead, you just attempt to insert it. If it succeeds, all is good. If it fails with the duplicate key error/exception, you catch it and update the existing row. This solution is the best because it avoids an extra SELECT query before each insertion that's a constant overhead for the low probability of hitting a race condition. And it's the best because it works for both root and non-root entities.

于 2018-03-24T01:47:38.463 回答
0

如果这有帮助,您可以扩展查询构建器以附加任意 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;
    }
}
于 2016-03-09T05:30:32.037 回答
-2

我为我写了简单的解决方案。刚刚创建了作为所有存储库的父类的 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]);
于 2015-06-07T11:58:21.890 回答