Yii INSERT ... ОБНОВЛЕНИЕ ДУБЛИКАЦИИ - PullRequest
7 голосов
/ 25 января 2012

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

Ответы [ 6 ]

14 голосов
/ 25 января 2012

Вы используете модели в Yii, это довольно просто ... попробуйте загрузить модель, в которой вы подозреваете наличие дублированных записей, если вы найдете запись, в которую загружена модель, иначе возвращается 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 blog.Я могу ошибаться в написании имен функций, извините за это.

5 голосов
/ 25 октября 2013

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

  1. Не пытайтесь использовать (при попытке обновить дубликат ключа), так какТолько для MySQL, как указывает txyoji.

  2. Предпочитают вставку select->if не найдена, тогда insert->else продемонстрирована Удаем Савантом.

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

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

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

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

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

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

Вот пример для 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

4 голосов
/ 25 января 2012

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

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

4 голосов
/ 25 января 2012

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

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

2 голосов
/ 25 января 2012

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

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

1 голос
/ 26 октября 2013

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

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