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), я не хочу, чтобы он жаловался, я просто хочу, чтобы он обновлялся с последними данными.

Я знаю как это сделать в raw SQL мне просто интересно, есть ли чистый Yii способ сделать это.

6 ответов


вы используете модели в 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();

см. метод обновления контроллеров post в примере приложения Yii blog. Возможно, я ошибаюсь в написании имен функций, извините за это.


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

  1. Не используйте (попробуйте) "on duplicate key update" с момента его MySQL-only, как указывает txyoji.

  2. предпочитаю select->if не нашел insert->else вставка продемонстрирована 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 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();
}

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

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"специфична для диалекта SQL MySQL. Его вряд ли можно реализовать на любом уровне абстракции данных. ZendDB и Propel не имеют эквивалента.

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


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

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


вы должны использовать try catch так:

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