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. Я могу ошибаться в написании имен функций, извините за это.

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

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

  2. Предпочитаю select->if не найден тогда insert->else Вставка продемонстрирована Uday Sawant.

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

С точки зрения транзакции, "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

Функция "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'
);
Другие вопросы по тегам