学说:重复主题更新

时间:2010-12-29 15:23:46

标签: mysql pdo doctrine on-duplicate-key

如何使用选项INSERT编写ON DUPLICATE KEY UPDATE学说查询?

9 个答案:

答案 0 :(得分:15)

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();

答案 1 :(得分:7)

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

如上所述,您需要为此编写RawSQL查询。这将是最简单的方法。

如果您希望它更复杂且真正与数据库无关,请查看Events,这是可能的。在执行实际查询之前,您可以检查是否存在,如果存在,则相应地采取相应措施。

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

你做不到。现在,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和插入之间插入了具有相同键的行“正在遇到重复的密钥异常。

但是考虑到这些约束需要与DB无关,因此使用Doctrine编写而不使用本机SQL,在某些情况下可能有所帮助。

参考文献:

答案 4 :(得分: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();
}

答案 5 :(得分:1)

我创建了一个教义dbal包装器来做到这一点。它可以与DoctrineBundle一起使用dbal wrapper_class选项。

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

答案 6 :(得分:0)

我为我写了简单的解决方案。刚刚创建了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]);

答案 7 :(得分: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;
    }
}

答案 8 :(得分:0)

您有三个选项。

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

第二个选项是锁定另一个表中的相关行。例如,如果您要插入的实体每个用户都有一个唯一键,则可以锁定您要插入/更新实体的用户。此解决方案的问题在于它不适用于User本身等根实体,因为您无法锁定尚不存在的行

第三个选项只是捕获重复键错误/异常。也就是说,您不会检查具有特定键的行是否已存在;相反,你只是试图插入它。如果成功,一切都很好。如果它因重复键错误/异常而失败,则会捕获它并更新现有行。这个解决方案是最好的,因为它避免了在每次插入之前的额外SELECT查询,这是一个不稳定的开销,因为它很难达到竞争条件。它是最好的,因为它适用于根和非根实体。