Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру 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
поле, нужно куда-то сохранить данные. План действий будет таков:
Создать новую
enum
колонку с правильным набором данных;Скопировать значение из старой колонки в новую и сразу применить исправление значения;
Удалить старую
enum
колонку;Переименовать новую
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
в базе - это лютое зло.