如何INSERT
使用 option 编写学说查询ON DUPLICATE KEY UPDATE
?
9 回答
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();
问题是这是一个 MySQL 特定的问题,因此 Doctrine 不会直接涵盖它。
正如评论所提到的,您需要为此编写一个 RawSQL 查询。这将是最简单的方法。
如果您希望它更复杂且真正独立于数据库,请查看事件及其可能性。在执行实际查询之前,您可以检查是否存在,如果存在,则采取相应措施。
一种独立于 ORM/PHP 的方法是编写一个存储过程/触发器来处理这个问题数据库端。
你不能。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 编写的限制,它在某些情况下可能会有所帮助。
参考:
我遇到了同样的问题,经过一番调查后,看起来 Doctrine 并没有这样做。我的解决方案是在插入之前执行findBy以查看是否存在具有唯一字段的任何记录。如果这返回一个实体,那么我更新该实体并将其持久化,而不是创建一个新实体来持久化。
如果您担心性能,那么这并不理想,因为我们在每次插入之前都会进行选择。然而,由于 Doctrine 与数据库无关,因此它是将自己锁定到 MySQL 的唯一选择。这是权衡之一:您想要性能还是便携性。
您可以使用这样的函数来构建和执行原始 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();
}
我创建了一个学说 dbal 包装器来做到这一点。它可以与带有 dbal wrapper_class 选项的 DoctrineBundle 一起使用。
你有三个选择。
第一个选项是下拉到 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.
如果这有帮助,您可以扩展查询构建器以附加任意 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;
}
}
我为我写了简单的解决方案。刚刚创建了作为所有存储库的父类的 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]);