6 мая 2013 г.

В ситуациях, когда необходимо совершать действия над таблицей, которая уже содержит в себе какие-либо данные, может возникнуть неприятная ошибка: "Mysql error 1452 - Cannot add or update a child row: a foreign key constraint fails".


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


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


Решение простое как пять копеек. Нам необходимо избавиться от NULL-значений в поле, на которое навешивается внешний ключ.

Предположим, что вы уже отредактировали схему соответствующим образом и она имеет примерно следующее содержание:

Human:
  columns:
    name:              { type: string(255), notnull: true }
    human_status_id:   { type: integer, notnull: true }
  relations:
    HumanStatus:
      local: human_status_id
      foreign: id
      onDelete: CASCADE

HumanStatus:
  columns:
    name:            { type: string,  notnull: true  }

После успешной генерации миграций должны появиться два файла миграций: один будет содержать создание таблиц, а вторая создание связей между ними. Именно второй файл мы и будем редактировать. Нам это никто не запрещает.

Итак, как я уже сказал, нам необходимо избавиться от NULL-значений. Для этого мы поправим вторую миграцию примерно таким образом:

public function up()
    {
        $conn = Doctrine_Manager::getInstance()->getCurrentConnection();

        $oHumanStatus = new HumanStatus();
        $oHumanStatus->setName('Temp');
        $oHumanStatus->save();

        Doctrine_Query::create()
            ->update()
            ->from('Human')
            ->set('human_status_id', $oHumanStatus->getId())
            ->execute();
        
        $this->createForeignKey('human', 'human_human_status_id_human_status_id', array(
             'name' => 'human_human_status_id_human_status_id',
             'local' => 'human_status_id',
             'foreign' => 'id',
             'foreignTable' => 'human_status',
             'onUpdate' => '',
             'onDelete' => 'CASCADE',
             ));
        $this->addIndex('human', 'human_human_status_id', array(
             'fields' => 
             array(
              0 => 'human_status_id',
             ),
             ));
    }

Думаю, основная идея понятна. Если нет нужды в создании новой записи в таблице HumanStatus, можно поискать уже существующие. Все зависит от вашей ситуации.

Далее накатываем обе миграции. Все должно пройти успешно. Если нет — читайте мануалы дальше. Возможно, это не ваш случай.


P.S. Не оставляйте die() в up() или down() методах миграции. Иначе она не накатится.

Автор: Артур Минимулин ⚫ 6 мая 2013 г.Тэги: php, Symfony, Doctrine, MySQL