简体   繁体   中英

Yii2 mysql how to insert a record into a table where column value already exists and should be unique?

I have a table, say 'mytable' that use a "rank" column that is unique. After having created some record where rank is successively rec A(rank=0), rec B (rank=1), rec C (rank=2), rec D (rank=3), rec E (rank=4). I need to insert a new record that will take an existing rank, say 1, and modify the rank value of the following records accordingly. The result being: rec A(rank=0), new rec (rank=1), rec B (rank=2), rec C (rank=3), rec D (rank=4), rec E (rank=5).

How can I do this? Can this be solved with mysql only or should I write some important bunch of code in PHP (Yii2)?

Assuming no rank is skipped you need to shift existing ranks before saving the new record. To do that you can use beforeSave() method of your ActiveRecord like this:

class MyModel extends \yii\db\ActiveRecord
{
    public function beforeSave($insert)
    {
        if (!parent::beforeSave($insert)) {
            return false;
        }

        if ($insert) { //only if we are saving new record
        {
            $query = self::find()
                ->where(['rank' => $this->rank]);
            if ($query->exists()) { //check if the rank is already present in DB
                //we will create the query directly because yii2
                // doesn't support order by for update
                $command = static::getDb()->createCommand(
                    "UPDATE " . static::tableName() .
                        " SET rank = rank + 1 WHERE rank >= :rank ORDER BY rank DESC",
                    [':rank' => $this->rank]
                );
                $command->execute();
            }
        }
        return true;
    }

    // ... other content of your model ...
}

MySQL allows use of ORDER BY in UPDATE query, that will help us deal with fact that doing UPDATE on table is not atomic and the UNIQUE constraint is checked after each row is updated.

It would be more problematic if there are skipped ranks. In that case you will need to shift ranks only until you hit first skipped rank.

Another option might be creating an before insert trigger on the table that would do the rank shifting.

Note:

It might be a good idea to also implement afterDelete method to shift the ranks in oposite direction when some record is removed to avoid skipped ranks.

Resources:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM