Почему тип поля enum на уровне базы — зло

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Часто разработчики интересуются почему не рекомендуется использовать тип поля enum в базе данных, и в этой статье мы рассмотрим все плюсы и минусы данного типа.

Тип колонки enum используется для хранения данных, которые могут принимать определённые значения из заранее определённого набора. Он обеспечивает ограничение значений, которые может принимать колонка, и позволяет более строго контролировать данные. Это может быть полезно для хранения статусов, категорий, типов или любых других значений, которые могут быть заданы только из ограниченного набора вариантов.

Но это в теории. А что на практике? Давайте рассмотрим.

Допустим у нас есть таблица со списком игр, содержащая колонку "жанр" ("genre") со следующими значениями:

CREATE TABLE `games`(  
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(255) NOT NULL,
  `genre` ENUM('action','adventure','shooter','rasing') NOT NULL,
   KEY(`id`)
) CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Через какое-то время была замечена грамматическая ошибка в слове "racing" и принято решение её исправить.

Нюанс изменения enum поля в том, что при его редактировании сбрасываются значения колонки в null для всех строк таблицы, а то и вовсе получим ошибку `Data truncated for column 'genre' at row 3`. То есть, чтобы корректно изменить enum поле, нужно куда-то сохранить данные. План действий будет таков:

  1. Создать новую enum колонку с правильным набором данных;

  2. Скопировать значение из старой колонки в новую и сразу применить исправление значения;

  3. Удалить старую enum колонку;

  4. Переименовать новую enum колонку.

При использовании фреймворка Laravel это будет выглядеть следующим образом:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        // Создаём новую колонку
        Schema::table('games', function (Blueprint $table) {
            $table->enum('tmp_genre', ['action', 'adventure', 'shooter', 'racing']);
        });

        // Копируем значения из enum колонки в новую с корректировкой значения
        DB::statement('UPDATE games SET tmp_genre = (IF genre = \'rasing\' THEN \'racing\' ELSE genre END IF)');

        // Удаляем старую колонку
        Schema::table('games', function (Blueprint $table) {
            $table->dropColumn('genre');
        });

        // Переименовываем колонку
        Schema::table('games', function (Blueprint $table) {
            $table->renameColumn('tmp_genre', 'genre');
        });
    }
};

На языке SQL эти действия будут выглядеть следующим образом:

ALTER TABLE `games`
  ADD COLUMN `tmp_genre` ENUM('action','adventure','shooter','racing') NOT NULL AFTER `genre`;

UPDATE `games` SET `tmp_genre` = (
    IF `genre` = 'rasing'
    THEN 'racing'
    ELSE `genre` END IF
);

ALTER TABLE `games`
  DROP COLUMN `genre`;

ALTER TABLE `games`
  CHANGE `tmp_genre` `genre` ENUM('action','adventure','shooter','racing') CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci NULL;

И, кроме этого, всё-равно нужно будет на стороне приложения обрабатывать значение либо как константу класса, либо как enum класс. Например:

class Game extends Model
{
    protected $casts = [
        'genre' => GameGenreEnum::class,
    ];
}

enum GameGenreEnum: string
{
    case Action    = 'action';
    case Adventure = 'adventure';
    case Shooter   = 'shooter';
    case Racing    = 'racing';
}

А теперь представьте, что как только эта задача выполнена, пришла новая - добавить новую категорию - `Fighting`...

Альтернатива этой боли - полный отказ от полей типа enum в пользу integer. В этом случае как при изменении названия, так и при добавлении нового всё что нужно будет сделать - это изменить содержимое самого enum класса. Например:

// До
enum GameGenreEnum: int
{
    case Action    = 1;
    case Adventure = 2;
    case Shooter   = 3;
    case Rasing    = 4;
}

// После
enum GameGenreEnum: int
{
    case Action    = 1;
    case Adventure = 2;
    case Shooter   = 3;
    case Racing    = 4;
    case Fighting  = 5;
}

И всё. Никакой боли и задача закрывается буквально за несколько секунд.

Именно поэтому многие разработчики отказываются от использования колонок типа enum в базе данных сохраняя не только время, потраченное на задачу, но и нервы и, в качестве приятного бонуса, шанс потерять данные сводится к нулю.

Также можно ответить на вопрос "почему integer, а не string": дело в том, что поля типа string подвержены грамматическим ошибкам и всё что они дают - лишь удобство чтения таких данных в одной конкретной таблице без использования SQL запросов. Но если в слове будет допущена ошибка или слово нужно будет заменить на другое - придётся отправлять запрос в базу, чего не нужно делать при использовании целочисленных значений.

Таким образом, тип полей enum в базе - это лютое зло.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Какой тип колонок Вы используете чаще всего?
12.5% enum 5
67.5% integer 27
20% string 8
Проголосовали 40 пользователей. Воздержались 7 пользователей.
Источник: https://habr.com/ru/articles/757212/


Интересные статьи

Интересные статьи

В HTML4 не было способа семантически разметить HTML, чтобы сохранить связь между изображением и его подписью. В HTML5 нам дали такую возможность с помощью элементов <figure> и <figcaption>...
Вряд ли хоть кто-то в интернете не слышал о теории поколений. Как справедливо заметила Шульман, она уже стала чем-то вроде новой версии гороскопов. Кто-то искренне убеждён в её научности и работоспо...
Существует масса вариантов удаленного взаимодействия. Принято выделять дистанционную работу и фриланс, но чаще мы находимся в каком-то из промежуточных состояний. И состояние состоянию рознь. Одн...
Должен признаться: я читаю ACM Magazine. Это делает меня «ботаником» даже по меркам программистов. Среди прочего, я узнал из этого журнала о «метаморфическом тестировании». Раньше я никогда о н...
Адаптировано из обсуждения 2015 года. Здесь Common Lisp служит лишь одним из многих наглядных примеров Будущее JavaScript? Я с 2007 года работаю в комитете по стандартам JavaScript (TC3...