我正在研究Yii项目。在Yii模型上执行save()时,如何使用MySQL的ON DUPLICATE功能(http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html)?
我的MySQL如下:
CREATE TABLE `ck_space_calendar_cache` (
`space_id` int(11) NOT NULL,
`day` date NOT NULL,
`available` tinyint(1) unsigned NOT NULL DEFAULT '0',
`price` decimal(12,2) DEFAULT NULL,
`offer` varchar(45) DEFAULT NULL,
`presale_date` date DEFAULT NULL,
`presale_price` decimal(12,2) DEFAULT NULL,
`value_x` int(11) DEFAULT NULL,
`value_y` int(11) DEFAULT NULL,
PRIMARY KEY (`space_id`,`day`),
KEY `space` (`space_id`),
CONSTRAINT `space` FOREIGN KEY (`space_id`) REFERENCES `ck_space` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
我的PHP是以下内容:
$cache = new SpaceCalendarCache();
$cache->attributes = $day; //Some array with attributes
$cache->save();
如果我的主键(sapce_id,day)中有重复项,我不希望它抱怨,我只是想用最新数据进行更新。
我知道如何在原始SQL中执行此操作,我只是想知道是否有一个干净的Yii方法来执行它。
答案 0 :(得分:14)
你在Yii中使用模型,它非常简单..尝试加载你怀疑有重复条目的模型,如果你发现模型被加载的条目,则返回null。现在,如果您的模型为null,则只需创建新模型。 rest是插入新记录的正常代码。
//try to load model with available id i.e. unique key
$model = someModel::model()->findByPk($id);
//now check if the model is null
if(!$model) $model = new someModel();
//Apply you new changes
$model->attributes = $attributes;
//save
$model->save();
请参阅示例应用程序Yii博客中的post controllers update方法。对于函数名称的拼写我可能有问题,对不起。
答案 1 :(得分:5)
我想从以前的答案中重复两点,我认为你应该保留:
不要(尝试)使用“重复密钥更新”,因为它只支持MySQL,正如txyoji指出的那样。
- 醇>
首选
select->if
未找到,然后{UST Sawant演示insert->else
插页。
但这里还有另外一点:并发。虽然对于流量较低的应用程序,您遇到麻烦的可能性很小(仍然不会为零),我想我们总是要小心这一点。
从事务的角度来看,"INSERT .. ON DUPLICATE UPDATE"
并不等同于选择应用程序的内存然后插入或更新。第一个是单个交易,第二个不是。
这是一个糟糕的情况:
- 您使用
findByPk()
选择您的记录,返回null- 其他一些事务(来自其他一些用户请求)会插入一条记录,其中包含您未能选择的ID
- 下次尝试再次插入
醇>
在这种情况下,您将获得一个例外(如果您正在使用唯一键,就像您在这里一样)或重复条目。重复的条目更难以获取(在用户看到重复记录之前,通常没什么好看的。)
这里的解决方案是设置严格的隔离级别,例如“serializable”,然后开始一个事务。
以下是yii的示例:
Yii::app()->db->createCommand('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
$trn = Yii::app()->db->beginTransaction();
try {
// Try to load model with available id i.e. unique key
// Since we're in serializable isolation level, even if
// the record does not exist the RDBMS will lock this key
// so nobody can insert it until you commit.
// The same shold for the (most usual) case of findByAttributes()
$model = someModel::model()->findByAttributes(array(
'sapce_id' => $sapceId,
'day' => $day
));
//now check if the model is null
if (!$model) {
$model = new someModel();
}
//Apply you new changes
$model->attributes = $attributes;
//save
$model->save();
// Commit changes
$trn->commit();
} catch (Exception $e) {
// Rollback transaction
$trn->rollback();
echo $e->getMessage();
}
您可以在以下链接中看到有关隔离级别的更多信息,并查看每个隔离级别在数据完整性方面提供的内容以换取并发性
答案 2 :(得分:4)
“重复密钥更新”功能特定于MySQL的SQL方言。它不太可能在任何数据抽象层中实现。 ZendDB和Propel没有等价物。
您可以通过在try / catch中尝试插入来模拟行为,并在插入失败时使用正确的错误代码进行更新。 (重复键错误)。
答案 3 :(得分:4)
我在beforeValidate()之前覆盖了我检查是否存在重复的地方。如果有,我设置$this->setIsNewRecord(false);
似乎工作。不确定它是多么高效。
答案 4 :(得分:2)
我同意@ txyoji对问题的分析,但我会使用不同的解决方案。
您可以扩展模型的save()
方法以查找现有记录,并更新它,或者如果不存在则插入新行。
答案 5 :(得分:1)
你必须像这样使用try catch:
try{
$model->save();
}
catch(CDbException $e){
$model->isNewRecord = false;
$model->save();
}