Yii ВСТАВИТЬ... НА ОБНОВЛЕНИИ ДУБЛИКАЦИИ
Я работаю над проектом Yii. Как я могу использовать функцию ON DUPLICATE в MySQL ( http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html) при выполнении save() для модели Yii?
Мой 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 сделать это.
7 ответов
Я переопределил beforeValidate(), где я проверил, существует ли дубликат. Если так, я установил $this->setIsNewRecord(false);
Кажется, работает. Не уверен, насколько это эффективно.
Вы используете модели в Yii, это довольно просто... попробуйте загрузить модель, в которой вы подозреваете наличие дублированных записей, если вы найдете запись, в которую загружена модель, иначе возвращается null. Теперь, если ваша модель пуста, просто создайте новую модель. остальные ваш нормальный код для вставки новой записи.
//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 blog. Я могу ошибаться в написании имен функций, извините за это.
Я повторяю два основных пункта из предыдущих ответов, я думаю, вы должны сохранить:
Не пытайтесь использовать "при обновлении дубликатов ключей", поскольку он предназначен только для MySQL, как указывает txyoji.
Предпочитаю
select->if
не найден тогдаinsert->else
Вставка продемонстрирована Uday Sawant.
Здесь есть еще один момент: параллелизм. Хотя для приложений с низким трафиком вероятность возникновения проблем минимальна (но все равно никогда не равна нулю), я думаю, что мы всегда будем осторожны с этим.
С точки зрения транзакции, "INSERT .. ON DUPLICATE UPDATE"
не эквивалентно выбору в память вашего приложения, а затем вставке или обновлению. Первая - это отдельная транзакция, а вторая - нет.
Вот плохой сценарий:
- Вы выбираете свою запись, используя
findByPk()
который возвращает ноль- В другой транзакции (из другого пользовательского запроса) вставляется запись с идентификатором, который вы просто не смогли выбрать
- В следующий момент вы пытаетесь вставить его снова
В этом случае вы либо получите исключение (если вы работаете с уникальным ключом, как здесь), либо дублирующую запись. Повторять записи гораздо сложнее (обычно ничто не кажется странным, пока ваши пользователи не увидят повторяющиеся записи).
Решение здесь состоит в том, чтобы установить строгий уровень изоляции, например "сериализуемый", а затем начать транзакцию.
Вот пример для 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();
}
Вы можете узнать больше об уровнях изоляции по крайней мере в следующих ссылках и увидеть, что каждый уровень изоляции может предложить в целостности данных в обмен на параллелизм
Функция "On Duplicate Key Update" специфична для диалекта SQL MySQL. Это вряд ли будет реализовано на любом уровне абстракции данных. ZendDB и Propel не имеют эквивалента.
Вы можете смоделировать поведение, пытаясь вставить в try/catch и update, если вставка не удалась с правильным кодом ошибки. (ошибка дублирующего ключа).
Я согласен с анализом @txyoji проблемы, но я бы использовал другое решение.
Вы можете продлить save()
метод модели, чтобы найти существующую запись и обновить ее, или вставить новую строку, если это не так.
Вы должны использовать попробовать поймать, как это:
try{
$model->save();
}
catch(CDbException $e){
$model->isNewRecord = false;
$model->save();
}
Ой, извините .. этот ответ для yii2
Если вы не используете модель yii, эта функция генерирует вставку синтаксиса mysql при обновлении ключа дубликатов.
static function insertDuplicate($table, $columns, $duplicates, $values="",$ignores=false){
$params=array();
$names=array();
$tipe="VALUES";
$ignore=($ignores===true)?"IGNORE":"";
$placeholders=array();
if(is_array($columns)){
if(!isset($columns[0])){
foreach($columns as $name=>$value)
{
$names[]=$name;
if($value instanceof CDbExpression)
{
$placeholders[] = $value->expression;
foreach($value->params as $n => $v)
$params[$n] = $v;
}
else
{
$placeholders[] = ':' . $name;
$params[':' . $name] = $value;
}
}
}else{
$names=$columns;
}
$myColumn=implode(', ',$names);
if($values!=""){
$myValue=$values;
}else{
$myValue='('.implode(', ', $placeholders).')';
}
}else{
$myColumn=$columns;
$myValue=$values;
}
if($values!=""){
if(substr(strtoupper($values),0,6)=="SELECT"){
$tipe="";
}
}
$d = array();
if(is_array($duplicates)){
if(!isset($duplicates[0])){
foreach($duplicates as $duplicate=>$act)
{
if($act=="increase"){
$dup=$table.".".$duplicate . ' = '.$table.".".$duplicate.' + VALUES('.$duplicate.')';
}elseif($act=="decrease"){
$dup=$table.".".$duplicate . ' = '.$table.".".$duplicate.' - VALUES('.$duplicate.')';
}else{
$dup=$table.".".$duplicate . ' = VALUES('.$duplicate.')';
}
$d[] = $dup;
}
}else{
foreach($duplicates as $duplicate){
$dup=$duplicate . ' = VALUES('.$duplicate.')';
$d[] = $dup;
}
}
$myDuplicate= implode(', ', $d);
}else{
$myDuplicate=$duplicates;
}
$sql='INSERT '.$ignore.' INTO ' . $table
. ' (' . $myColumn . ') '.$tipe.' '
. $myValue . ' ON DUPLICATE KEY UPDATE ' .$myDuplicate;
return Yii::$app->db->createCommand($sql)->bindValues($params)->execute();
}
Поместите эту функцию в какой-нибудь класс и не забудьте использовать
use yii\db\Command;
в этом классе
Эта функция может вставлять при обновлении ключа, приращении обновления, уменьшении обновления, обновлении нескольких из значения и обновлении при выборе.
Использование :
//to update available=1 and price into 100
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id'=>1,'day'=>'2022-09-01','available'=>1,'price'=>100],
['available','price']
);
//to update price increase by 100, (if price is decrease then change it to decrease)
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id'=>1,'day'=>'2022-09-01','price'=>100],
['price'=>'increase']
);
//to update mass with a value
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id','day','price'],
['price'],
'(1,'2022-09-01',100),(2,'2022-09-01',300),(3,'2022-09-01',100)'
);
//to update mass with select from another table
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id','day','price'],
['price'],
'SELECT otherid as sapce_id, otherday as day, otherprice as price from other WHERE otherprice>100'
);