Изменяем тип данных поля с текстового на цифровое (varchar to integer)

Человек, никогда не совершавший ошибок, никогда не пробовал ничего нового.

Альберт Эйнштейн

Всегда ли обосновано применение того или иного типа данных к некоторым полям в фильтрах? На днях столкнулся с довольно необычным, на мой взгляд, решением: поля «Стоимость» были заданы текстом.

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

Так и появилась новая подзадача: изменить тип данных. Осложнялась она тем, что проект действующий, «живой», довольно крупный, и при этом с большим массивом информации.

Анализ

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

Если все же нужно обрабатывать значения поля, от необходимо выбрать способ и методы, например если поле и его значение должно обрабатываться в частном случае (модуле, темплейте), то не имеет смысла менять тип поля в самой системе, достаточно написать функцию и использовать её.

В случае масштабного использования значения поля, например во Views, наиболее приемлемый вариант - изменить тип поля, обработать его значения и сохранить в нужном формате. Тем самым в итоге мы получим прозрачную структуру SQL запросов, снизим нагрузку на сервер.

Реализация функции

Внимание! Перед любыми действиями с Базой Данных не забывайте делать бэкапы.

Давайте рассмотрим функцию

/**
 * @file
 * Функция изменения типа поля с VARCHAR на INT.
 */

/**
 * Пример вызова функции: _change_field_type('FIELD_NAME')
 */
function _change_field_type($field_name) {

  // Меняем данные в самом поле таблицы.
  db_query("UPDATE field_data_" . $field_name . " SET " . $field_name . "_value = REGEXP_REPLACE(" . $field_name . "_value, '[^[[:digit:]]]*', 0)");
  db_query("ALTER TABLE field_data_" . $field_name . " ADD IF NOT EXISTS temp int AFTER delta");
  db_query("UPDATE field_data_" . $field_name . " SET temp = CAST(" . $field_name . "_value AS UNSIGNED)");
  db_query("ALTER TABLE field_data_" . $field_name . " DROP COLUMN " . $field_name . "_value");
  db_query("ALTER TABLE field_data_" . $field_name . " CHANGE temp " . $field_name . "_value INT(11) NULL DEFAULT NULL");

  // Меняем данные в самом ревизиях поля таблицы.
  db_query("UPDATE field_revision_" . $field_name . " SET " . $field_name . "_value = REGEXP_REPLACE(" . $field_name . "_value, '[^[[:digit:]]]*', 0)");
  db_query("ALTER TABLE field_revision_" . $field_name . " ADD IF NOT EXISTS temp int AFTER delta");
  db_query("UPDATE field_revision_" . $field_name . " SET temp = CAST(" . $field_name . "_value AS UNSIGNED)");
  db_query("ALTER TABLE field_revision_" . $field_name . " DROP COLUMN " . $field_name . "_value");
  db_query("ALTER TABLE field_revision_" . $field_name . " CHANGE temp " . $field_name . "_value INT(11) NULL DEFAULT NULL");

  // Меняем тип данных поля в таблице field_config.
  db_query("UPDATE {field_config} SET type = 'number_integer', module = 'number' WHERE field_name = '" . $field_name . "'");

  // Обновляем данные о типе поля.
  db_change_field('field_data_' . $field_name . '', '' . $field_name . '_value', '' . $field_name . '_value', array(
    'type' => 'int',
    'length' => 11,
    'not null' => FALSE,
    'default' => NULL
  ));

  // Обновляем данные о типе поля в ревизиях.
  db_change_field('field_revision_' . $field_name . '', '' . $field_name . '_value', '' . $field_name . '_value', array(
    'type' => 'int',
    'length' => 11,
    'not null' => FALSE,
    'default' => NULL
  ));

  // Выполяем сериализацию данных.
  $result = db_select('field_config', 'c')
    ->fields('c')
    ->condition('field_name', '' . $field_name . '', '=')
    ->execute()
    ->fetchAssoc();
  $data = unserialize($result['data']);
  unset($data['settings']['max_length']);
  db_update('field_config')
    ->fields(
      [
        'data' => serialize($data),
      ]
    )
    ->condition('field_name', '' . $field_name . '', '=')
    ->execute();
  $result = db_select('field_config_instance', 'c')
    ->fields('c')
    ->condition('field_name', '' . $field_name . '', '=')
    ->execute()
    ->fetchAssoc();

  $data = unserialize($result['data']);
  unset($data['widget']['settings']['size']);
  $data['widget']['type'] = 'number';
  $data['widget']['module'] = 'number';
  $data['widget']['active'] = 0;
  unset($data['settings']['better_formats']);
  db_update('field_config_instance')
    ->fields(
      [
        'data' => serialize($data),
      ]
    )
    ->condition('field_name', '' . $field_name . '', '=')
    ->execute();

  // Очищаем кэш полей.
  field_cache_clear(TRUE);

Данную функцию необходимо использовать только ОДИН раз, для этого переведите сайт в “Режим обслуживания” и выполните её, затем можете закомментировать или удалить её.

После этого не забудьте очистить кэш Drupal!

Эпилог

Данное Решение актуально для Drupal 7, так как разрабатывалось под частный случай. Если Вы столкнетесь с подобной проблемой для Drupal 8 или Drupal 9 - напишите мне!

Коллеги, будьте внимательны при проектировании архитектуры данных.

Если у Вас появились вопросы, не стесняйтесь пишите мне!
Оставьте Ваш комментарий