Yii插入...重复更新

kdfy810k  于 2022-11-09  发布在  其他
关注(0)|答案(7)|浏览(166)

我正在做一个Yii项目,如何使用MySQL(http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html)的ON DUPLICATE功能在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方式来做它。

vm0i2vca

vm0i2vca1#

您正在使用Yii中的模型,它很简单..尝试加载您的模型,你怀疑有重复的条目,如果你找到的条目,模型被加载,否则返回null.现在,如果您的模型为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博客中的post controllers更新方法。我可能对函数名称的拼写有错误,很抱歉。

o2g1uqev

o2g1uqev2#

我在此重复之前回答中的两个要点,我认为您应该保留:
1.正如txyoji指出的那样,不要(尝试)使用“on duplicate key update”,因为它只适用于MySQL。
1.更喜欢没有发现的select->if,而不是Uday Sawant演示的insert->else插入。
不过,这里还有一点:虽然对于低流量的应用程序,遇到麻烦的可能性是最小的(仍然不会为零),但我认为我们总是要小心这一点。
从事务性的Angular 来看,"INSERT .. ON DUPLICATE UPDATE"并不等同于选择应用程序的内存,然后插入或更新。
下面是一个糟糕的场景:
1.您确实使用findByPk()选择了记录,这将返回空值
1.其他某个事务(来自其他某个用户请求)插入了一个记录,该记录具有您刚才未能选择的ID
1.下一瞬间你又试着把它插入
在这种情况下,你要么会得到一个异常(如果你使用的是一个唯一键,就像这里所做的那样),要么会得到一个重复的条目。重复的条目很难被发现(通常在你的用户看到重复的记录之前,没有什么是奇怪的)。
这里的解决方案是设置一个严格的隔离级别,例如“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

xoshrz7s

xoshrz7s3#

我重写了beforeValidate(),检查是否存在重复项。如果存在,则设置$this->setIsNewRecord(false);
看起来很有效,不确定它的性能如何。

mzillmmw

mzillmmw4#

“On Duplicate Key Update”特性是MySQL特有的SQL方言。它不太可能在任何数据抽象层中实现。ZendDB和Propel没有等价的特性。
您可以尝试在try/catch中插入,并在插入失败并显示正确的错误码(重复索引键错误)时进行更新,以仿真此行为。

2wnc66cl

2wnc66cl5#

我同意@txyoji对这个问题的分析,但我会使用不同的解决方案。
您可以扩展模型的save()方法来查找现有记录,并更新它,或者如果没有,则插入新行。

brjng4g3

brjng4g36#

您必须像这样使用try catch:

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

ifsvaxew7#

不好意思...这是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'
);

相关问题