如何使用选项INSERT
编写ON DUPLICATE KEY UPDATE
学说查询?
答案 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选项。
答案 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
查询,这是一个不稳定的开销,因为它很难达到竞争条件。它是最好的,因为它适用于根和非根实体。