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

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

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

Допустим у нас есть таблица со списком платежей, содержащая колонку status со следующими значениями:

CREATE TABLE `payments`(  
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `status` ENUM('new', 'progress', 'done', 'fauled') NOT NULL,
   KEY(`id`)
) CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

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

Нюанс изменения enum поля в том, что при его редактировании сбрасываются значения колонки в null для всех строк таблицы, а то и вовсе получим ошибку Data truncated for column 'status' 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('payments', function (Blueprint $table) {
            $table->enum('tmp_status', ['new', 'progress', 'done', 'failed']);
        });

        // Копируем значения из enum колонки в новую с корректировкой значения
        DB::statement('UPDATE payments SET tmp_status = (IF status = \'fauled\' THEN \'failed\' ELSE status END IF)');

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

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

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

ALTER TABLE `payments`
  ADD COLUMN `tmp_status` ENUM('new', 'progress', 'done', 'failed') NOT NULL AFTER `status`;

UPDATE `payments` SET `tmp_status` = (
    IF `status` = 'fauled'
    THEN 'failed'
    ELSE `status` END IF
);

ALTER TABLE `payments`
  DROP COLUMN `status`;

ALTER TABLE `payments`
  CHANGE `tmp_status` `status` ENUM('new', 'progress', 'done', 'failed') CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci NULL;

При использовании фреймворка Laravel нельзя просто взять и применить метод "change" на поле типа enum.Это вызовет ошибку:

Unknown column type "enum" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType()...

Для решения этой проблемы приходится применять следующий костыль перед применением изменений:

protected function fixEnum(): void
{
    $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform();
    $platform->registerDoctrineTypeMapping('enum', 'string');
}

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

class Payment extends Model
{
    protected $casts = [
        'status' => StatusEnum::class,
    ];
}

enum StatusEnum: string
{
    case New      = 'new';
    case Progress = 'progress';
    case Done     = 'done';
    case Failed   = 'failed';
}

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

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

// До
enum StatusEnum: int
{
    case New      = 0;
    case Progress = 1;
    case Done     = 2;
    case Fauled   = 3;
}

// После
enum StatusEnum: int
{
    case New      = 0;
    case Progress = 1;
    case Done     = 2;
    case Failed   = 3;
    case Refund   = 4;
}

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

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

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

Таким образом, тип полей enum в базе является иррациональным и бессмысленным действием за исключением крайне редких и специфичных кейсов.

Заключение

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

В том же Laravel правило валидации реализуется очень просто:

public function rules(): array
{
    return [
        'status' => [Rule::enum(StatusEnum::class)],
    ];
}

И на выходе, в случае обнаружения проблем, получим корректный код четырёхсотой серии с расшифровкой вместо `500 Whoops! Something wrong`.

Это крайне удобно и максимально практично.

Комментарии (166)


  1. z250227725
    28.08.2023 08:10
    +1

    Зачем названия таблиц и полей в SQL запросе даны в кавычках? Ещё и константное значение взято в такие же кавычки…
    И второй вопрос — при использовании int не придётся ли конструкторы обмазывать проверками передаваемых значений?


    1. Helldar Автор
      28.08.2023 08:10

      SQLYog такой формат предложил, я не стал отказываться т.к. выглядит, на мой взгляд, красиво.

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


    1. Ivan22
      28.08.2023 08:10
      +3

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


    1. zuek
      28.08.2023 08:10

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


    1. klopp_spb
      28.08.2023 08:10
      -2

      Зачем названия таблиц и полей в SQL запросе даны в кавычках?

      Ты пошутил?


  1. nightlord189
    28.08.2023 08:10
    +6

    Зачем создавать временную колонку, если можно поменять тип поля с enum на varchar, проапдейтить таблицу (если нужно), поправить enum и обратно поменять тип поля с varchar на enum?


    1. Suvitruf
      28.08.2023 08:10

      Что будет с сервисами, которые будут стучаться в базу в промежуточный момент, когда колонка внезапно стала не enum'ом, а varchar'ом?


      1. nightlord189
        28.08.2023 08:10
        +2

        Ничего, скастятся в enum на бэке точно так же.


      1. Akina
        28.08.2023 08:10

        Что будет с сервисами, которые будут стучаться в базу в промежуточный момент, когда колонка внезапно стала не enum'ом, а varchar'ом?

        Если сервисы работают со строковым представлением значения - они даже не заметят подмены. А если с числовым - то да, могут и обалдеть, и даже напоросячить..


      1. KReal
        28.08.2023 08:10

        Промежуточный вариант? Это как, у нас транзакций нету?


        1. 96467840
          28.08.2023 08:10

          в мускуле транзакции с alter table плохо дружат


          1. KReal
            28.08.2023 08:10

            Да я сначала написал, потом вспомнил, что транзакции с DML мало где дружат.


    1. BlenderRU
      28.08.2023 08:10

      Вполне себе рабочий и безболезненный вариант.


  1. SadOcean
    28.08.2023 08:10
    +19

    А нормализация не требует, чтобы жанр был отдельной таблицей, а в таблице игр хранился ее id ?


    1. Helldar Автор
      28.08.2023 08:10
      +1

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

      Описанным в статье способом обычно статусы хранят, например, но для примера и жанры подойдут.


    1. Ivan22
      28.08.2023 08:10
      +13

      так это и есть статья о пользе нормализации без использования слова "нормализация"


      1. FlyingDutchman2
        28.08.2023 08:10
        -1

        так это и есть статья о пользе нормализации

        Статья не имеет никакого отношения к нормализации.


        1. Boilerplate
          28.08.2023 08:10
          +6

          Имеет самое прямое. В статье можно наблюдать все проблемы, отсутствие которых считается достоинством нормализованных бд. Собственно, я по заголовку и при постановки проблемы сразу ожидал решения "мы вынесли жанр в отдельную таблицу". Но почему-то вместо этого один костыль заменили другим...


          1. FlyingDutchman2
            28.08.2023 08:10
            +3

            Имеет самое прямое.

            Замена ENUM на внешний ключ не влияет на нормализацию аж никак. Иными словами, в какой нормальной форме была таблица, в той и осталась.


            1. Cryvage
              28.08.2023 08:10
              +2

              А разве это не является одним из условий доменно ключевой нормальной формы? Там, конечно, не только значения ENUM'ов, надо выносить в отдельные таблицы, а вообще допустимые значения любых пользовательских типов. Но сказать что оно никак не влияет уже нельзя.


              1. Ivan22
                28.08.2023 08:10
                -1

                да это нарушение аж ПЕРВОЙ нф. Одно поле - одно значение.


    1. FlyingDutchman2
      28.08.2023 08:10

      А нормализация не требует, чтобы жанр был отдельной таблицей ?

      Не требует. Но удобнее завести отдельную таблицу.


  1. fonclub
    28.08.2023 08:10
    +4

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

    Слишком категорично. В целом верно, но в некоторых случаях это поле вполне приемлемо. Например для колонки Пол (мужской, женский, не выбран). Enum можно использовать только в случаях, когда есть уверенность, что значения ВСЕГДА будут неизменными, тогда и проблем, описанных выше, не будет.


    1. Helldar Автор
      28.08.2023 08:10
      +10

      В моей практике такой уверенности никогда не было. Недавно ломали колонку гендера...

      -- before
      gender ENUM('man', 'woman')
      
      -- after
      gender ENUM('man', 'woman', 'other')


      1. dopusteam
        28.08.2023 08:10
        -7

        А кто такие other?


        1. trinxery
          28.08.2023 08:10
          +9

          Либо "не указан", либо выполнение требований современного общества...


        1. Tiriet
          28.08.2023 08:10
          -14

          озербайджанцы наверное.


        1. Zenitchik
          28.08.2023 08:10
          +4

          attack_helicopter


        1. ClayRing
          28.08.2023 08:10
          +1

          Другие


        1. Helldar Автор
          28.08.2023 08:10
          +2

          В нашем случае те, кто не хочет сообщать свой пол, записываются как null (поле в базе nullable), а те кто хочет сообщить, но не относит себя к мужчинам или женщинам - выбирает other.


          1. javalin
            28.08.2023 08:10
            +1

            Регистрировал недавно почту у гугла, там можно написать свой. Исходя из этого, скорее всего там String(varchar), что как по мне наверное идеально.


        1. drath
          28.08.2023 08:10
          +1

          Да вы безнадежно отстали от жизни! Вот вам, для референса, последняя ревизия политически одобренной формы для формы выбора пола


      1. usrsse2
        28.08.2023 08:10
        +1

        Повезло, что он был не BOOLEAN


      1. FlyingDutchman2
        28.08.2023 08:10
        +7

        В моей практике такой уверенности никогда не было. Недавно ломали колонку гендера...

        Точно, с полом не все так просто. Вот, например, как определены коды для пола в стандарте ISO-5218 Representation of human sexes:

        +-----+---------------+
        | Код | Пол           |
        +-----+---------------+
        | 0   | Неизвестен    |
        | 1   | Мужской       |
        | 2   | Женский       |
        | 9   | Неприменим    |
        +-----+---------------+
        

        А в базах данных американского ФБР (Федеральное бюро расследований) используются следующие коды:

        +-----+----------------------------------+
        | Код | Пол                              |
        +-----+----------------------------------+
        | 0   | Неизвестен                       |
        | 1   | Мужской                          |
        | 2   | Женский                          |
        | 3   | Мужской, бывший женский          |
        | 4   | Женский, бывший мужской          |
        | 5   | Мужской, изменяющийся на женский |
        | 6   | Женский, изменяющийся на мужской |
        | 7   | Невозможно определить            |
        | 9   | Неприменим                       |
        +-----+----------------------------------+
        

        Информация взята из книги Ханс Ладани "SQL. Энциклопедия пользователя", 1998 год, издательство "Диасофт".


    1. EvilShadow
      28.08.2023 08:10
      -9

      мужской, женский, не выбран

      true, false, NULL. Или false, true, NULL, кому как больше нравится.


      1. ivankudryavtsev
        28.08.2023 08:10
        +4

        Не надо так… СУБД не для этого развивались в то, чем они стали. У Вас даже семантика исчезла при данном представлении. Даже две представленные Вами альтернативы об этом свидетельствуют…


        1. vadimr
          28.08.2023 08:10
          -8

          Для сохранения семантики надо назвать поле HAS_PENIS. Или завуалированно – IS_MAN. Правда, будет неполиткорректно.


          1. Kirill-112
            28.08.2023 08:10
            +5

            А если пользователь мужчина, но по каким-то причинам лишился пениса? В таком случае нельзя будет поставить ему HAS_PENIS = true.


            1. koreychenko
              28.08.2023 08:10
              +12

              Ну, тогда HAD_PENIS


              1. trinxery
                28.08.2023 08:10
                +1

                has_penis = false;

                had_penis = true;


                1. Ritan
                  28.08.2023 08:10
                  +1

                  has_penis = true;

                  had_penis = false;

                  Нужно ещё учесть


                  1. Helldar Автор
                    28.08.2023 08:10

                    И has_penis = true не факт что мужчина. Может женщина вдруг решила себе пришить... Или мужик сделал переход в женщину, но оставил себе причиндал.


              1. iRedds
                28.08.2023 08:10

                Не был, а бывал. Это женский скелет(с)


          1. saboteur_kiev
            28.08.2023 08:10

            А если пол касается NPC, который может быть гермафродитом, или мало ли что в выдуманном мире?
            Не нужно нести бред, лишь бы пошутить. Не забывайте девиз правильной лурки - "факты > лулзы"


            1. vadimr
              28.08.2023 08:10
              -2

              А причём здесь выдуманный мир?

              Может быть, вы объясните, с какой целью вообще отслеживать в таблице пол NPC в выдуманном мире, отличный от грамматической категории рода?


              1. Zenitchik
                28.08.2023 08:10
                +6

                Грамматическая категория рода в русском языке принимает три значения, плюс особые случаи типа "сирота" и "сани". Т.е. если надо хранить род NPC для согласования слов в диалогах, то "родов" у него будет четыре.

                А если нужно поддерживать несколько локалей - то чёрт ногу сломит, во что превратится дерево согласовательных классов.

                Короче, рассчитывать, что какой-то список будет вечным и неизменным - не стоит. Лучше обрабатывать всё единообразно, через дополнительную таблицу.


                1. EvilShadow
                  28.08.2023 08:10

                  Только тред начался с колонки "пол". Не социальный "гендер". Не грамматический "род". Обычный биологический "пол". Их у человеков всего два. И NULL для "неизвестно".


              1. saboteur_kiev
                28.08.2023 08:10

                Для отыгрыша ролевой составляющей?


                1. Zenitchik
                  28.08.2023 08:10
                  +1

                  Для правильного употребления родов в диалогах.


        1. EvilShadow
          28.08.2023 08:10
          -3

          Конечно, не надо. Я бы и сам использовал енам. Но мой комментарий был не о поле, а о значении "не выбрано". Которое как раз и означает отсутствие значение, т.е. то, для чего в соседнем треде сделан костыль:

          -- before
          gender ENUM('man', 'woman')
          
          -- after
          gender ENUM('man', 'woman', 'other')

          Вот этот other избыточен, если мы следуем формулировке "мужской/женский/не выбран". Причём "не выбран" - это вовсе не то же самое, что "предпочитаю не говорить" или "другой", ибо первое - это отсутствие значения, а второе - сознательный выбор пользователя.


          1. mayorovp
            28.08.2023 08:10

            Причём "не выбран" — это вовсе не то же самое, что "предпочитаю не говорить" или "другой", ибо первое — это отсутствие значения, а второе — сознательный выбор пользователя.

            Вот вы и сами это признали. А теперь посмотрите на тот "костыль" ещё раз, и осознайте, что other и NULL — это разные значения, а вы их зачем-то решили объединить.


            1. ainoneko
              28.08.2023 08:10

              Мне кажется, проблема с NULL в том, что оно означает одно из "другой", "не скажу", "невозможно определить", "ещё не спрашивали", ..., а какое из них -- не очевидно (надо смотреть в документациии, которая (как обычно) уже устарела).


            1. EvilShadow
              28.08.2023 08:10

              Следите за контекстом. Тред начался с формулировки

              Например для колонки Пол (мужской, женский, не выбран). 

              И указанный код опубликован в этом треде. В этом контексте нет "другого", а в этом коде не указано, что поле с этим енамом может быть NULL. Из чего очевидный вывод: в этом коде other используется ВМЕСТО NULL.


  1. Akina
    28.08.2023 08:10
    +38

    План действий будет таков:

    Если ничего не понимать в сути данного типа, можно и так. А если понимать, то можно и проще. Вот варианты (написаны для MySQL):

    Вариант 1. Добавить нужное значение, обновить, убрать ошибочное значение.

    ALTER TABLE games 
      MODIFY COLUMN `genre` ENUM('action','adventure','shooter','rasing','racing') NOT NULL;
    UPDATE games SET genre = 'racing' WHERE genre = 'rasing';
    ALTER TABLE games 
      MODIFY COLUMN `genre` ENUM('action','adventure','shooter','racing') NOT NULL;

    Вариант 2. Описан выше в комментарии от @nightlord189 - через VARCHAR.

    ALTER TABLE games 
      MODIFY COLUMN `genre` VARCHAR(10) NOT NULL;
    UPDATE games SET genre = 'racing' WHERE genre = 'rasing';
    ALTER TABLE games 
      MODIFY COLUMN `genre` ENUM('action','adventure','shooter','racing') NOT NULL;

    Вариант 3. Конвертация в TINYINT (если список значений содержит более 63 значений - в SMALLINT) с последующей обратной конвертацией в ENUM, но с правильным описанием. Оптимальный вариант, ибо не требует UPDATE. И очень быстрый, потому что не затрагивает данных, корректируя исключительно метаданные.

    ALTER TABLE `games` 
      MODIFY COLUMN `genre` TINYINT NOT NULL;
    ALTER TABLE `games` 
      MODIFY COLUMN `genre` ENUM('action','adventure','shooter','racing') NOT NULL;

    Полигон для испытаний: https://dbfiddle.uk/2d7_al1e

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

    Безответственное и ничем не обоснованное утверждение.

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

    То есть в переводе на русский: если ошибка допущена в значении в БД - это зло вселенское. А если она будет допущена в клиентской части - так это нормально...

    Альтернатива этой боли - полный отказ от полей типа enum в пользу integer.

    Вообще-то это называется "нормализация".


    1. theIggs
      28.08.2023 08:10

      если список значений содержит более 63 значений - в SMALLINT

      Выходит, в некоторых случаях можно сэкономить половину памяти, просто вспомнив, что числа могут быть UNSIGNED (каковыми и полагается быть почти всем этим перечислениям). ???? Да и значение 63 кажется заниженным.


      1. ptr128
        28.08.2023 08:10

        Если в БД реализован механизм сжатия строк, аналогичный row compression в MS SQL, то знаковые числа до 127 (без знаковые до 255) и так будут занимать 1 байт (точнее - полтора), даже если объявлены 64-битными.

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


      1. Akina
        28.08.2023 08:10

        Упс... это самый обычный ляпсус. Должно быть если список значений содержит более 255 значений - в SMALLINT.


  1. kalapanga
    28.08.2023 08:10
    +22

    А так нельзя?

    CREATE TYPE gamegenre AS ENUM ('action','adventure','shooter','rasing');

    Использовать для колонки этот тип. А для правки:

    ALTER TYPE gamegenre RENAME VALUE 'rasing' TO 'racing';

    Вроде не теряется ничего (PostgreSQL). И добавлять значения можно.


    1. Helldar Автор
      28.08.2023 08:10

      Я с MySQL работаю. В ней так нельзя. Но выглядит адекватно.


  1. spirit1984
    28.08.2023 08:10
    +1

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


    1. nightlord189
      28.08.2023 08:10
      +3

      Зато если энам на уровне базы, то это гарантирует, что никакое "левое" (пустое или то, которого нет в перечислении) значение в таблицу никогда не сохранится. Мне ближе такой вариант, потому что с ним часто можно намного меньше смотреть в код, а сразу смотреть в базу, чтобы понять, что вообще делает приложение и какие данные у нас могут быть.

      А так это шкала между гибкостью и гарантиями целостности, тут каждый сам выбирает, что удобнее для проекта.

      Если энам меняется нечасто, то почему бы его и не прописать в базу?
      А если новые значения добавляются каждый месяц - лучше оставить строкой/интом и хранить это в приложении.


      1. Ivan22
        28.08.2023 08:10
        +8

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


        1. xSVPx
          28.08.2023 08:10
          +1

          Ну так енум это почти тоже самое по функциональности. Некий сахар для удобства. И в ряде случаев вполне оправданный.


          1. spaceatmoon
            28.08.2023 08:10

            Вот только сахар этот с горчинкой. Отказался от enum по похожим причинам указанным в статье.


            1. ptr128
              28.08.2023 08:10

              А можно подробней? Не знаю, как в MySQL, но в PostgreSQL enum - это, по сути, просто автоматический JOIN с таблицей pg_enum, фильтрацией по enumtypeid и по равенству enumsortorder.

              А при необходимость изменить enum, модифицировать метаданные таблиц не требуется. Модифицируется только тип.


        1. nightlord189
          28.08.2023 08:10
          +3

          Можно и в отдельной хранить. Но тогда придется джойнить таблички, чтобы получить полную информацию о сущности. Опять же зайдя в таблицу, удобнее сразу видеть "order 1, status created, type juridical", а не "order 1, status 15, type 84".

          В общем, тут кому какой фломастер нравится.


          1. Ivan22
            28.08.2023 08:10
            +3

            "любишь субд использовать - люби и таблички джойнить" (с) народная поговорка


            1. atygaev
              28.08.2023 08:10
              +1

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


              1. Ivan22
                28.08.2023 08:10
                -1

                ну вот как раз от людей механизм зависить и не должен.


          1. ptr128
            28.08.2023 08:10

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

            Удобней конечно видеть

            "Номер ПУ": 123456, "Регистр": "Накопленная реактивная энергия второй фазы", "Значение": 1234.5

            чем

            "Номер ПУ": 123456, "Регистр": 22, "Значение": 1234.5

            Но передавать по каналам связи и хранить в БД второе намного эффективней.


            1. nightlord189
              28.08.2023 08:10

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


        1. Xexa
          28.08.2023 08:10
          -1

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


          1. michael_v89
            28.08.2023 08:10

            Более того, относительно нормальных форм часто не учитывают один нюанс. Если вы вынесли данные в отдельную таблицу с первичным ключом, то у вас статичные данные превратились в объект с состоянием и поведением. Теперь можно обновить значение свойства, и оно обновится для всех строк в других таблицах с этим внешним ключом. А до вынесения можно было поменять значение для любой отдельной строки, не меняя другие, где указано то же значение. Это не эквивалентные структуры данных. Они эквивалентны только для данных, которые никогда не меняются.


            1. 0xd34df00d
              28.08.2023 08:10

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


              1. michael_v89
                28.08.2023 08:10

                Ну в том и нюанс, что сначала можно было сделать
                UPDATE table SET status = 'new_status' WHERE id = 123
                а после вынесения этого поля в отдельную таблицу сделать
                UPDATE status_table SET status_name = 'new_status' WHERE id = 1
                уже нельзя. Значение поменяется не только для строки table:123, а везде, где status_table_id = 1.


                Раньше мы изменяли данные, и они становились 'другие', теперь изменение тех же данных это изменение свойства объекта с сохранением его identity для всех остальных, то есть объект остался 'тот же самый'. До вынесения такого эффекта не было, в этом и есть отличие.


                Поэтому да, чтобы было эквивалентно, надо не просто выносить в отдельную таблицу, а еще и запрещать для нее UPDATE, и заменять UPDATE который был раньше на INSERT в новую таблицу и UPDATE основной таблицы.


                Или другой пример. Сначала у нас была таблица заказов с полями ('client_first_name', 'client_last_name'), потом мы решили вынести их в таблицу 'clients'. А потом клиент Мария Иванова вышла замуж и стала Мария Петрова, и ее фамилия поменялась во всех предыдущих заказах, хотя правильно указывать новые данные только в новых.


                1. 0xd34df00d
                  28.08.2023 08:10
                  +1

                  Я тут как-то случайно вспомнил, что в известных мне системах, где подобная консистентность с реальным миром действительно важна, UPDATE запрещён вообще как класс. Любое изменение — новая запись. Изменилась фамилия клиента перед новой покупкой? Новая запись (хоть нормализованная с ссылкой на новую запись о конкретной фамилии, хоть напрочь денормализованная со встроенным жсончиком о клиенте). Изменился статус? Событие о новом статусе.


                  И вот когда я это вспомнил, то понял, что всё обсуждаемое — проявление этой проблемы. А конкретная степень (де)нормализации тут вторична.


                  1. michael_v89
                    28.08.2023 08:10

                    То что вы описали это и есть то о чем я говорю. Мне непонятно, на что вы возражаете. "UPDATE запрещён вообще как класс" именно потому что данные, вынесенные в отдельную таблицу, превращаются в объект.


                    хоть нормализованная с ссылкой на новую запись о конкретной фамилии

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


                    Теория баз данных не делает различия, история там или нет, она работает с кортежами данных. И многие ее изложения обычно говорят только "Нормализация предназначена для приведения структуры БД к виду, обеспечивающему минимальную логическую избыточность", не упоминая, что логически результаты до и после вынесения колонок не эквивалентны, и такое вынесение не всегда подходит.


                    1. 0xd34df00d
                      28.08.2023 08:10
                      +1

                      Мне непонятно, на что вы возражаете.

                      Что вообще есть такая проблема, как «обновить часть записей». Записи не обновляют.


                      "UPDATE запрещён вообще как класс" именно потому что данные, вынесенные в отдельную таблицу, превращаются в объект.

                      Они запрещены даже в том случае, если ничего никуда не выносят — просто потому, что у вас тогда есть иммутабельность/цепочка для аудита/етц.


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

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


                      1. michael_v89
                        28.08.2023 08:10

                        Что вообще есть такая проблема, как «обновить часть записей»

                        Я не говорил, что есть такая проблема. UPDATE нескольких записей вроде нормально работает во всех базах данных.


                        Я говорил, что результат до и после вынесения группы полей в таблицу и замену их на значение первичного ключа не эквивалентный, и проявляется это при изменениях данных. Раньше UPDATE "prev_status" на "new_status" делалась для одной строки, после замены их на первичный ключ UPDATE тех же данных "prev_status" на "new_status" повлияет на все связанные строки. Это даже не проблема, а эффект. А нюанс в том, что про него не говорят и часто не учитывают.


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

                        Я говорю "'Сделать X до изменения' не эквивалентно 'Сделать X после изменения". Вы говорите "Сделать X+Y после изменения эквивалентно". Ну так я с этим и не спорил. Напротив, я именно на это и указывал изначально с первого комментария. Даже хотел написать, что для эквивалентности надо делать INSERT в новую таблицу, но подумал, что тут все специалисты, и про это и так знают.


                        просто потому, что у вас тогда есть иммутабельность/цепочка для аудита/етц

                        Бизнес-требования иммутабельности это другой вопрос. Эффект, про который я говорю, не зависит от их наличия. Обновление фамилии клиента в предыдущих заказах вполне может никак не влиять на бизнес, и обновление таблицы клиентов может быть разрешено, но разница между тем, когда имя клиента записано в таблице заказов, и когда оно связано через внешний ключ, все равно есть. Иногда она важна, иногда нет, а иногда сначала неважна, а потом становится важна.


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


  1. Ivan22
    28.08.2023 08:10
    +1

    Може имхо - это того же уровня модель данных, что и JSON со множеством значений в поле таблицы


  1. EvilShadow
    28.08.2023 08:10
    +11

    Статья называется "Почему тип поля enum на уровне базы — зло", а на самом деле она о "Почему тип поля enum на уровне базы (*в Mysql* при использовании *PHP*) — зло"

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

    Это инструмент. Как и любым инструментом, им нужно уметь пользоваться.

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

    Вообще непонятно. Как можно читать данные в таблице *без использования SQL запросов*? SELECT - это не SQL?

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

    Удачи в изменении порядка значений.


    1. Ivan22
      28.08.2023 08:10

      про "Удачи в изменении порядка значений." - целочисленный PK не стоит использовать для сортировки. Для сортировки стоит использовать отдельное поле в справочнике с условным названием "ordering"


      1. EvilShadow
        28.08.2023 08:10
        +2

        целочисленный PK не стоит использовать для сортировки

        Сортировка по первичному ключу - занятие бессмысленное, абсолютно согласен, но какое это имеет отношение к обсуждаемым enum'ам?

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

        В реальном мире проблемы сквозной нумерации могут решаться увеличением порядка, пример: аудитории в учебных заведениях, типа 1xx на первом этаже, 2xx на втором и т.д. Т.е. сознательное создание дырок. Здесь можно применить тот же подход, но зачем, если можно просто не использовать инты?

        Может возникнуть "для чего вставлять новое значение именно в середину". Ответ: потому что это требуется для решения задачи.


        1. Ivan22
          28.08.2023 08:10

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

          p.s. И вообще сейчас люди на UUID переходят, и правильно делают


          1. EvilShadow
            28.08.2023 08:10
            +1

            Но откуда вообще взялся PK? О нём в посте речи не шло.


            1. Ivan22
              28.08.2023 08:10

              ну если речь зашла про "использование целочисленных значений" вместо строк, а мы все еще не подумали про PK, то где-то модель данных свернула не туда


              1. EvilShadow
                28.08.2023 08:10

                Не улавливаю связи. Речь шла об использовании уникальных значений. Автору не нравятся строки, он предлагает использовать инты. Также, с разной эффективностью, можно было бы использовать float, decimal, uuid, да хоть битовые маски. То, что инты также могут использоваться в качестве первичных ключей - просто совпадение.


                1. Ivan22
                  28.08.2023 08:10

                  ну да, ну да, крякает как утка, плавает как утка, уникально идентифицирует сущность в таблице, что же это???


                  1. EvilShadow
                    28.08.2023 08:10
                    -2

                    уникально идентифицирует сущность в таблице

                    enum? С какой стати? enum - это тип данных, допускающий установку одного из нескольких предопределённых значений. Например, [active, inactive]. Или [cart, address, deliver, payment]. Или [monday, tuesday, ..., sunday]. А записей в таблице может быть столько, сколько поддерживает СУБД. И у вас будут сотни миллионов active и миллиарды inactive. Ничто тут ничего уникально не идентифицирует.


    1. sergeaunt
      28.08.2023 08:10
      +2

      Статья называется "Почему тип поля enum на уровне базы — зло", а на самом деле она о "Почему тип поля enum на уровне базы (в Mysql при использовании PHP) — зло"

      А вот и нет. Постгря, например, имеет транзакционный DDL, но добавить значение в enum в транзакции она вам не даст. Мелочь, а неприятно и заставляет задуматься, что, видимо, всё непросто с этими енамами, раз не осилили транзакционность.


      1. Akina
        28.08.2023 08:10

        Целочисленный индекс значения - это данные, а вот его строковое представление - это метаданные. И изменение описания типа потенциально затрагивает как то, так и другое. А такое изменение для параллельной транзакции - это безусловный пердимонокль. Так что постгресс совершенно логично посылает в даль туманную с подобным намерением.


    1. Helldar Автор
      28.08.2023 08:10

      Вообще непонятно. Как можно читать данные в таблице *без использования SQL запросов*? SELECT - это не SQL?

      Скорректировал статью. Имелось ввиду без использования джойнов. Да и при просмотре таблицы через любой инструмент запрос руками не пишется. Например:


    1. Helldar Автор
      28.08.2023 08:10

      Удачи в изменении порядка значений.

      enum StatusEnum: int
      {
          case New = 1;
          case Progress = 2;
          case Done = 3;
      }
      
      enum StatusEnum: int
      {
          case Done = 3;
          case New = 1;
          case Progress = 2;
      }

      И ничего не изменится на уровне базы ¯\_(ツ)_/¯


  1. pantsarny
    28.08.2023 08:10
    +2

    Enum ограничивает список допустимых значений, а ваш integer - допускает огромный список значений. Что если кто-то залезет в базу ручками и установит значение 100? Как будет вести себя ваше приложение? Именно для этого и придуман enum. Да, в MySQL изменение структуры таблицы подразумевает создание новой таблицы. Но вы ни слова не сказали о ключевой функциональности enum - ограничения списком допустимых значений, а тупо проигнорировали это и перенесли решение на уровень приложения.


    1. Ivan22
      28.08.2023 08:10
      +4

      вы что-нибудь слышали про constraints в целом и Foreign Key в частности?


      1. pantsarny
        28.08.2023 08:10
        +1

        да, но причем тут это?


        1. vadimr
          28.08.2023 08:10
          +1

          Содержать список допустимых значений в виде таблицы-словаря с foreign key.


          1. pantsarny
            28.08.2023 08:10

            зачем тут таблица и foreign key?


            1. vadimr
              28.08.2023 08:10
              +1

              Именно чтобы не хардкодить константы в операторах sql.


              1. pantsarny
                28.08.2023 08:10

                приведите пример, непонятно ничего


                1. vadimr
                  28.08.2023 08:10

                  Да вот выше человек захардкодил в базу enum ('man', 'woman'), а потом стал локализовывать код в страну, где 46 половых принадлежностей*, и всё сломалось. А на следующем этапе ещё получит за новый enum по голове от какой-нибудь полиции нравов в стране, где два пола.

                  *не является пропагандой нетрадиционных отношений.


                  1. pantsarny
                    28.08.2023 08:10

                    а зачем для полов отдельная таблица?


                    1. vadimr
                      28.08.2023 08:10
                      +1

                      А зачем вообще константы выносить из кода программы? Именно для того, чтобы пользователь мог настроить значения в соответствии со своими требованиями.

                      Я понимаю, зачем enum был нужен в языке Си, где из структур данных имелись только массивы, которые индексируются целыми числами. Но в SQL это ненужная вещь.


                      1. pantsarny
                        28.08.2023 08:10
                        +1

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


                      1. vadimr
                        28.08.2023 08:10
                        +2

                        Эта фигня называется шестой нормальной формой.


                      1. Ivan22
                        28.08.2023 08:10

                        ну нет, этот случай - нарушение аж ПЕРВОЙ нф: один кортеж - одно значение


                      1. semmaxim
                        28.08.2023 08:10
                        +2

                        Это сейчас Вам нужно только два значения - 1 ("man") и 2 ("woman"). А потом Вы открываете для себя другие страны. И в одной стране за количество полов меньше 40 Вас отменят, а в другой за больше 2 Вас посадят. Вот и понадобится переделать в табличку, которая бы хранила пол и данные о том, кому и когда его можно показывать.

                        Хотя, конечно, данный конкретный пример довольно специфичен....


                1. Ivan22
                  28.08.2023 08:10
                  +1

                  1. pantsarny
                    28.08.2023 08:10

                    ну вот, сдулись. нету у вас ответа


                  1. aleksandy
                    28.08.2023 08:10

                    Не удалось найти ни одного результата для нормализация для чайников
                    Проверьте правильность написания или используйте другие ключевые слова

                    А можно мне другую ссылку :)


      1. anone8729347
        28.08.2023 08:10

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

        Это узкий кейс, но он опровергает тезис- "Таким образом, тип полей enum в базе - это лютое зло.".


    1. BioHazzardt
      28.08.2023 08:10
      +1

      ограничения списком допустимых значений

      так check constraint же


    1. michael_v89
      28.08.2023 08:10
      +1

      Что если кто-то залезет в базу ручками и установит значение 100? Как будет вести себя ваше приложение?

      Совершенно спокойно упадет с исключением при конвертации из int в enum приложения. Ничем не отличается от ситуации, когда из-за сбоя оперативной памяти вместо 'racing' появилось 'raaing'.


      1. Helldar Автор
        28.08.2023 08:10

        Либо будет восприниматься как null если при конвертации используется вызов метода tryFrom вместо from. Это зависит от реализации в приложении.


    1. Helldar Автор
      28.08.2023 08:10

      Но вы ни слова не сказали о ключевой функциональности enum - ограничения списком допустимых значений, а тупо проигнорировали это

      Во втором абзаце это написано:

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


  1. spacediver
    28.08.2023 08:10
    +14

    Шёл 2023 год, программисты продолжали не доверять системам управления базами данных в части управления базами данных...


  1. Abobcum
    28.08.2023 08:10
    +6

    Разве enum не хранится в базе как int + таблица имён ? Мне казалось, все современные субд используют эту обёртку автоматически. Тем более, это даёт огромный выигрыш по памяти.


    1. pantsarny
      28.08.2023 08:10

      enum хранится как целочисленное значение, даже арифметические операции доступны


  1. koreychenko
    28.08.2023 08:10
    +8

    Enum это не зло. Это просто еще один способ обеспечения целостности базы данных. История про то, что "enum не позволяет мне легко косячить" - ну, он как бы для этого и сделан. А то, что enum использовался для живого постоянно изменяющегося списка - дык это просто неправильно база спроектирована.


    1. Aquahawk
      28.08.2023 08:10
      +2

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


      1. koreychenko
        28.08.2023 08:10

        В целом да. Но есть же lean development, который рекомендует не делать лишнюю работу, если на горизонте нет ничего, что бы предвещало возможность изменений в будущем. Ну, т.е. список жанров - понятно что должен быть динамическим. Но ведь бывают какие-то чисто бизнесовые деления, например, статусы воркфлоу.
        Т.е. ты на этапе разработки бизнес-процесса закладываешь допустимые статусы. И это может быть enum, потому что если потребовалось изменить значение в базе, это значит, что и процесс уже другой.
        Короче, архитектор он на то и архитектор, чтобы голову включать. А то можно докатиться до того, что число Pi переменной делать, мало ли что :-)


        1. Aquahawk
          28.08.2023 08:10
          +1

           lean development, который рекомендует не делать лишнюю работу, если на горизонте нет ничего, что бы предвещало возможность изменений в будущем.

          Я считаю что это в корне неверное понимание принципов lean. Я очень часто сталкивался что люди оправдывают принципами lean или kiss закладывание сложно расширяемых принципов проектирования, когда заложить расширяемость на старте особо много не стоит, иногда эта стоимость пренебрежимо мала. Если строим небоскрёб, то фундамент, стройплощадку и краны сразу под небоскрёб надо подвозить, а не так что давайте одноэтажный домик построим, потом научимся достраивать на него этаж, и повторим 100 раз. Нет, это так не работает.


          1. 0xd34df00d
            28.08.2023 08:10

            ИМХО самый хороший принцип — не делать почти наверняка ненужную работу сейчас (часто вам эти небоскребы нужны?), и строить систему так, чтобы БД/тайпчекер/етц кричали на вас, если ваши ожидания не оправдались, и работа оказалась нужной.


            1. Aquahawk
              28.08.2023 08:10

              Вы уверены, что заведение отдельной таблицы и ссылка на неё это большое количество работы, которое не стоит делать, и, что вы этим что-то сэкономите. Это ничтожное количество работы за большое количество простоты расширения в будущем. Имхо lean говорит, что это нужно сделать. Не стоит делать сложного, но не вся избыточность сложная. И вообще, никакая человеческая деятельность сделанная без избыточности не стабильна. Надо закладывать избыточную прочность в здание, в машину, надо чтобы на званом ужине было больше еды, чем съедят, шведский стол в отеле не должен пустеть. Мы не боги, и не можем предсказать точные потребности в будущем, поэтому дёшево купить избыточность очень даже выгодно. И, на мой взгляд, это не противоречит lean. Если пытаться интерпретировать lean радикально и фанатично, то нужно не делать всё, что можно не делать, то давайте отступы в коде писать не будем, сэкономим байты на пробелах. Давайте гит не будем юзать, нам ведь пока не требуется откатывать изменения, вот когда потребуется, тогда гит и применим. Lean не про это.


              1. 0xd34df00d
                28.08.2023 08:10

                В этом случае — нет, не уверен. У меня недостаточно опыта с подобными проектами, чтобы иметь разумные prior'ы о том, как себя ведут enum'ы в БД.


        1. Helldar Автор
          28.08.2023 08:10

          Статусы тоже меняются. Реже чем жанры игр, но меняются. На прошлой работе, например, ввели новые статусы "ожидание доставки" и "ожидание возврата".


          1. Aquahawk
            28.08.2023 08:10
            +1

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


        1. aleksandy
          28.08.2023 08:10

          можно докатиться до того, что число Pi переменной делать

          Даже это имеет некий смысл.


          1. ptr128
            28.08.2023 08:10

            Шутки шутками, но в случае, когда из-за необходимости очень высокой точности используется PostgreSQL decimal, Pi приходится вычислять, как переменную, рядом Эйлера, в зависимости от заданной точности.


      1. EvilShadow
        28.08.2023 08:10

        Есть списки неизменные, например, биологический пол: есть всего два варианта. А если изменяющиеся, например, гендер. И enum'ы, конечно, нужно использовать только для неизменных.


        1. piton_nsk
          28.08.2023 08:10
          +6

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

          Кроме того часто бывает что все эти статусы обрастают дополнительными атрибутами типа SortOrder, FullName, ShortName, etc.


          1. EvilShadow
            28.08.2023 08:10
            +1

             Проблем с отдельной табличкой нет никаких.

            Да, в atlassian тоже так думают. А потом смотришь на список таблиц jira, а там

            (860 rows)


            1. piton_nsk
              28.08.2023 08:10

              800 табличек это имхо меньшая проблема, чем 800 энумов. Особенно если придерживаться соглашения об именовании.


              1. EvilShadow
                28.08.2023 08:10

                Трудно представить ситуацию, в которой у вас найдётся 800 неизменяемых списков.


          1. qrKot
            28.08.2023 08:10
            -1

            Проблем с отдельной табличкой нет никаких.

            Точно нет? Ну, там, по производительности ничего не теряем? Память не жрем?


  1. guryanov
    28.08.2023 08:10

    Можно вместо enum использовать constraint check в PostgreSQL

    alter table games drop constraint games_genre_check;
    update games set genre = 'racing' where genre = 'rasing';
    alter table games add constraint games_genre_check check (genre in ('action','adventure','shooter','racing'));


  1. a1ez
    28.08.2023 08:10

    Предпочитаю varchar и constraint(по необходимости).

    Не люблю инты в силу того, что через некоторое время хрен разберёшь что за status=4 и для интерпретации данных из таблицы, всё время приходится обращаться к энамам из приложения.


    1. DarkMike
      28.08.2023 08:10
      +1

      Поэтому предпочитаю справочники, в которых прописаны enum-ы приложения. Всегда запросом можно увидеть что есть status=4. Ну и расширить список статусов просто. Сложно только обеспечивать синхронность справочников и приложения. Но тут один раз сделал и работает :)


      1. avost
        28.08.2023 08:10
        +1

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


        1. vlivyur
          28.08.2023 08:10

          DBeaver в помощь


          1. avost
            28.08.2023 08:10

            Визуальный тул для построения запросов? Да, в запросы я и сам могу. Если позволяют. Но иногда не позволяют.


            1. drath
              28.08.2023 08:10

              SQLYog в дата-вьювере на дабл-клик по полю с FK выдает поисковик по референсной таблице. Все жду когда это уже в PHPStorm завезут


              1. michael_v89
                28.08.2023 08:10

                Там можно нажать Ctrl+B в ячейке, он открывает другую таблицу с фильтром по этому значению.


                1. drath
                  28.08.2023 08:10

                  Это которая функция jump to source (пкм -> go to -> all related rows) ?


                  1. michael_v89
                    28.08.2023 08:10

                    go to -> referenced rows, но all related тоже подходит.


            1. vlivyur
              28.08.2023 08:10

              Нет. Он при клике в ячейку, ссылающуюся на другую таблицу, может показывать значение из этой таблицы и вид настраивается. И не надо переписывать запрос, добавляя join'ы, выполнять его, а потом заново искать нужную строчку
              Но да, всё равно в ответе видишь только Id'шники и надо везде кликать, чтоб узнать что же там скрывается за ним


  1. BackDoorMan
    28.08.2023 08:10

    Для таких случае придумали LowCardinality. Вероятно, не в той СУБД, которую вы используете, правда.


  1. vagon333
    28.08.2023 08:10
    +1

    Возможно повторюсь, но:
    1. нормализация прежде всего: все списки в отдельной таблице и ссылки по id
    С ростом размера базы (кол-во таблиц, колонок, записей), приложение вам скажет спасибо.
    2. если угораздило получить enum, незачем создавать доп. temp колонку: расширьте список на новое значение (+racing), замените в таблице значение колонки на новое (rasing->racing), удалите старый enum.


  1. binakot
    28.08.2023 08:10
    +3

    PostgreSQL прекрасно умеет делать модификацию значений перечислений без необходимости промежуточных "махинаций", и уже довольно давно https://www.postgresql.org/docs/current/sql-altertype.html. Неужели в MySQL это не так, что-то сомневаюсь. Честно говоря, статья притянута за уши.

    Первое же предложение "Часто разработчики интересуются почему не рекомендуется использовать тип поля enum в базе данных" намекает на какую-то надуманность, т.к. за 15 лет ни разу не слышал подобной постановки вопроса о ENUM в БД.

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


    1. n0isy
      28.08.2023 08:10

      Да. Но нет. Не умеет "в транзакции". А миграции в нормальной туле идут в транзакциях.


    1. breninsul
      28.08.2023 08:10

      ну бывает, что изменилась логика и прежнее название перестало отображать суть.

      но сама проблема надумана, да.


  1. RolexStrider
    28.08.2023 08:10
    +1

    Осознанно не читал ни основной текст ни комменты (тут этого не любят, знаю), чтобы выразить свое мнение:
    "Потому что дает ложную уверенность в постоянстве"


  1. ptr128
    28.08.2023 08:10
    +1

    Нюанс изменения enum поля в том, что при его редактировании сбрасываются значения колонки в null для всех строк таблицы

    Прочитал, очень удивился, пошел проверять.

    CREATE TYPE dev.tmp_test_enum AS ENUM (
      'male',
      'female'
    );
    CREATE TABLE dev.tmp_test_table (
      id   serial PRIMARY KEY,
      name varchar NOT NULL,
      sex  dev.tmp_test_enum NULL
    );
    INSERT INTO dev.tmp_test_table (name, sex) VALUES ('men', 'male'), ('woman', 'female');

    Добавим элемент в enum

    ALTER TYPE dev.tmp_test_enum ADD VALUE IF NOT EXISTS 'unknow';
    INSERT INTO dev.tmp_test_table (name, sex) VALUES ('men', 'male'), ('woman', 'female'),  ('trans', 'unknow');
    SELECT * FROM dev.tmp_test_table;

    Никаких проблем:

    id|name |sex   |
    --+-----+------+
     1|men  |male  |
     2|woman|female|

    Вставим строку новым элементом enum

    INSERT INTO dev.tmp_test_table (name, sex) VALUES ('men', 'male'), ('woman', 'female'),  ('trans', 'unknow');
    SELECT * FROM dev.tmp_test_table;

    Никаких проблем:

    id|name |sex   |
    --+-----+------+
     1|men  |male  |
     2|woman|female|
     3|men  |male  |
     4|woman|female|
     5|trans|unknow|

    Переименуем добавленный элемент:

    ALTER TYPE dev.tmp_test_enum RENAME VALUE 'unknow' TO 'other';
    SELECT * FROM dev.tmp_test_table;

    И снова все хорошо:

    id|name |sex   |
    --+-----+------+
     1|men  |male  |
     2|woman|female|
     3|men  |male  |
     4|woman|female|
     5|trans|other |

    Что я делаю не так?


    1. EvilShadow
      28.08.2023 08:10
      +1

      Используете postgres, а у автора mysql.


      1. ptr128
        28.08.2023 08:10
        +4

        Я должен был догадаться? И что в тегах статьи тогда делают "postgresql, postgres"?


        1. EvilShadow
          28.08.2023 08:10

          Это очень хороший вопрос, но лучше задать его автору. Лично я догадался по обратным кавычкам в примерах запросов.


    1. Helldar Автор
      28.08.2023 08:10

      Понял в чём причина.

      Да, если напрямую отправлять в базу запрос на добавление элементов, например:

      ALTER TABLE `payments`   
        CHANGE `status` `status` ENUM('new','progres','done','refund') CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
      

      то ошибок нет и он отработает.

      Но я работаю с базой через механику фреймворка Laravel и doctrine/dbal, которую он использует, по-умолчанию не умеет работать с полями типа enumв плане изменений. Создать - может, а изменить - нет. Поэтому приходилось прибегать к костылям вида:

      DB::getDoctrineConnection()
          ->getDatabasePlatform()
          ->registerDoctrineTypeMapping('enum', 'string');

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

      Unknown column type "enum" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType()

      Это значит, что костыль, который я применял несколько лет назад, перестал корректно работать по причине изменений в проекте Doctrine.

      Код, на котором я тестирую
      <?php
      
      declare(strict_types=1);
      
      use Illuminate\Database\Migrations\Migration;
      use Illuminate\Database\Schema\Blueprint;
      use Illuminate\Support\Facades\Schema;
      
      return new class extends Migration
      {
          public function up(): void
          {
              DB::getDoctrineConnection()
                  ->getDatabasePlatform()
                  ->registerDoctrineTypeMapping('enum', 'string');
      
              Schema::dropIfExists('payments');
      
              Schema::create('payments', function (Blueprint $table) {
                  $table->enum('status', ['new', 'progres', 'done']);
              });
      
              DB::table('payments')->insert([
                  ['status' => 'new'],
                  ['status' => 'progres'],
                  ['status' => 'done'],
              ]);
      
              Schema::table('payments', function (Blueprint $table) {
                  $table->enum('status', ['new', 'progres', 'done', 'refund', 'progress'])->change();
              });
      
              DB::table('payments')->insert([
                  ['status' => 'refund'],
              ]);
      
              DB::table('payments')
                  ->where('status', 'progres')
                  ->update(['status' => 'progress']);
      
              Schema::table('payments', function (Blueprint $table) {
                  $table->enum('status', ['new', 'progress', 'done', 'refund'])->change();
              });
          }
      };
      


      1. ptr128
        28.08.2023 08:10
        +5

        Но тогда главный тезис статьи все же не верен и должен относиться не к базе, а к конкретному фреймворку.


        1. Helldar Автор
          28.08.2023 08:10
          -1

          Со стороны примеров кода да, а вот со стороны enum - какую бы БД с поддержкой данного типа ни выбрали, будет больно её обновлять если запросы кидаются не в чистом виде, а в наше время в этом нет необходимости когда достаточно пару-тройку методов вызвать.


          1. BlenderRU
            28.08.2023 08:10

            В Постгре никаких проблем закастить в текст и изменить/отказаться от enum'ов без чистки данных столбца.


  1. middle
    28.08.2023 08:10
    +2

    Кривая база + кривые фреймворки, а виновато поле типа enum.


    1. breninsul
      28.08.2023 08:10
      -1

      база ????


  1. Gapon65
    28.08.2023 08:10

    Автор статьи упустил тот факт, что тип ENUM позволяет 1-based indexing. Пример двух эквивалентных запросов:

    SELECT * FROM `payments` WHERE status='fauled';
    SELECT * FROM `payments` WHERE status=4;

    Более того, в MySQL тип ENUM реализован (в имплементации структур данных) используя целочисленный тип (16-битовое представление). Подробности в https://dev.mysql.com/doc/refman/8.0/en/enum.html


    1. Akina
      28.08.2023 08:10

      в MySQL тип ENUM реализован (в имплементации структур данных) используя целочисленный тип (16-битовое представление).

      Либо 8-битное, либо 16-битное - зависит от количества значений в перечислении. См. Data Type Storage Requirements


  1. Arm79
    28.08.2023 08:10

    Мы следуем следующей логике.

    1) делаем справочник вместо перечислений, если изменение списка допустимых значений не приводит к необходимости доработки существующего кода

    2) используем enum, если каждое изменение перечня значений - это изменение функциональности в коде, и выводится на пром релизом, в котором учтены скрипты миграции


  1. AzatJ
    28.08.2023 08:10
    -2

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

    Не стоит недооценивать удобство чтения.

    У int есть один недостаток, через n лет красивый int enum превратится в:
    enum StatusEnum: int
    {
    case New = 0;
    case Legacy = 1;
    case Done = 2;
    case Failed = 3;
    case Deleted = 4;

    case NewStatus = 376;

    case AnotherNewStatus = 378;
    }