Yii INSERT … ПО ОБНОВЛЕНИЮ DUPLICATE

Я работаю над проектом 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.

    Вы используете модели в Yii, это довольно просто. Попробуйте загрузить модель, где вы подозреваете, что у вас есть повторяющиеся записи, если вы обнаружите, что модель загружена, else null – это возврат. теперь, если ваша модель равна null, просто создайте новую модель. rest – ваш обычный код для вставки новой записи.

     //try to load model with available id ie 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. Возможно, я ошибаюсь в написании имен функций, извините за это.

    Я повторяю два основных момента из предыдущих ответов, которые, как я думаю, вам следует придерживаться:

    1. Не пытайтесь использовать «при повторном обновлении ключа» с момента своего MySQL-only, как указывает txyoji.

    2. Предпочитайте select->if not found, а затем insert->else insert, продемонстрированную Uday Sawant.

    Здесь есть еще один момент: параллелизм. Хотя для приложений с низким трафиком вероятность того, что вы столкнетесь с проблемой, минимальна (по-прежнему никогда не равна нулю), я думаю, что мы всегда будем осторожны в этом.

    С точки зрения транзакций "INSERT .. ON DUPLICATE UPDATE" не эквивалентно выбору в память вашего приложения, а затем вставке или обновлению. Первая – это одна транзакция, а вторая – нет.

    Вот плохой сценарий:

    1. Вы выбираете свою запись, используя findByPk() которая возвращает null
    2. Некоторые другие транзакции (из какого-либо другого пользовательского запроса) вставляют запись с идентификатором, который вы просто не смогли выбрать
    3. В следующее мгновение вы попытаетесь вставить его снова

    В этом случае вы либо получите исключение (если вы работаете с уникальным ключом, как вы здесь), либо дублируетесь. Дублировать записи гораздо сложнее (обычно ничего не кажется странным, пока ваши пользователи не видят дубликаты записей).

    Решение здесь – установить строгий уровень изоляции, например «serializable», а затем начать транзакцию.

    Вот пример для yii:

     Yii::app()->db->createCommand('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'); $trn = Yii::app()->db->beginTransaction(); try { // Try to load model with available id ie 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(); } 

    Вы можете узнать больше об уровнях изоляции по крайней мере в следующих ссылках и посмотреть, что каждый уровень изоляции может предложить в целостности данных в обмен на параллелизм

    http://technet.microsoft.com/en-us/library/ms173763.aspx

    http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html

    Я переделал beforeValidate (), где я проверил, существует ли дубликат. Если это так, я устанавливаю $this->setIsNewRecord(false);

    Кажется, работает. Не уверен, насколько он эффективен.

    Функция «On Duplicate Key Update» относится к диалоговому языку MySQL SQL. Его вряд ли можно реализовать на любом уровне абстракции данных. ZendDB и Propel не имеют эквивалента.

    Вы можете имитировать поведение, пытаясь вставить в try / catch и обновить, если вставка завершилась неудачно с правильным кодом ошибки. (повторяющаяся ошибка ключа).

    Я согласен с анализом проблемы @ txyoji, но я бы использовал другое решение.

    Вы можете расширить метод save() модели, чтобы искать существующую запись, обновить ее или вставить новую строку, если это не так.

    вам нужно использовать try catch:

      try{ $model->save(); } catch(CDbException $e){ $model->isNewRecord = false; $model->save(); }