Часто разработчики интересуются почему не рекомендуется использовать тип поля 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
поле, нужно куда-то сохранить данные. План действий будет таков:
Создать новую
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('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)
nightlord189
28.08.2023 08:10+6Зачем создавать временную колонку, если можно поменять тип поля с enum на varchar, проапдейтить таблицу (если нужно), поправить enum и обратно поменять тип поля с varchar на enum?
Suvitruf
28.08.2023 08:10Что будет с сервисами, которые будут стучаться в базу в промежуточный момент, когда колонка внезапно стала не enum'ом, а varchar'ом?
Akina
28.08.2023 08:10Что будет с сервисами, которые будут стучаться в базу в промежуточный момент, когда колонка внезапно стала не enum'ом, а varchar'ом?
Если сервисы работают со строковым представлением значения - они даже не заметят подмены. А если с числовым - то да, могут и обалдеть, и даже напоросячить..
SadOcean
28.08.2023 08:10+19А нормализация не требует, чтобы жанр был отдельной таблицей, а в таблице игр хранился ее id ?
Helldar Автор
28.08.2023 08:10+1В данном конкретном случае да, лучше выносить жанры в отдельную таблицу и связывать их через foreign связь.
Описанным в статье способом обычно статусы хранят, например, но для примера и жанры подойдут.
Ivan22
28.08.2023 08:10+13так это и есть статья о пользе нормализации без использования слова "нормализация"
FlyingDutchman2
28.08.2023 08:10-1так это и есть статья о пользе нормализации
Статья не имеет никакого отношения к нормализации.
Boilerplate
28.08.2023 08:10+6Имеет самое прямое. В статье можно наблюдать все проблемы, отсутствие которых считается достоинством нормализованных бд. Собственно, я по заголовку и при постановки проблемы сразу ожидал решения "мы вынесли жанр в отдельную таблицу". Но почему-то вместо этого один костыль заменили другим...
FlyingDutchman2
28.08.2023 08:10+3Имеет самое прямое.
Замена ENUM на внешний ключ не влияет на нормализацию аж никак. Иными словами, в какой нормальной форме была таблица, в той и осталась.
Cryvage
28.08.2023 08:10+2А разве это не является одним из условий доменно ключевой нормальной формы? Там, конечно, не только значения ENUM'ов, надо выносить в отдельные таблицы, а вообще допустимые значения любых пользовательских типов. Но сказать что оно никак не влияет уже нельзя.
FlyingDutchman2
28.08.2023 08:10А нормализация не требует, чтобы жанр был отдельной таблицей ?
Не требует. Но удобнее завести отдельную таблицу.
fonclub
28.08.2023 08:10+4Таким образом, тип полей
enum
в базе - это лютое зло.Слишком категорично. В целом верно, но в некоторых случаях это поле вполне приемлемо. Например для колонки Пол (мужской, женский, не выбран). Enum можно использовать только в случаях, когда есть уверенность, что значения ВСЕГДА будут неизменными, тогда и проблем, описанных выше, не будет.
Helldar Автор
28.08.2023 08:10+10В моей практике такой уверенности никогда не было. Недавно ломали колонку гендера...
-- before gender ENUM('man', 'woman') -- after gender ENUM('man', 'woman', 'other')
dopusteam
28.08.2023 08:10-7А кто такие other?
Helldar Автор
28.08.2023 08:10+2В нашем случае те, кто не хочет сообщать свой пол, записываются как
null
(поле в базе nullable), а те кто хочет сообщить, но не относит себя к мужчинам или женщинам - выбираетother
.javalin
28.08.2023 08:10+1Регистрировал недавно почту у гугла, там можно написать свой. Исходя из этого, скорее всего там String(varchar), что как по мне наверное идеально.
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 год, издательство "Диасофт".
EvilShadow
28.08.2023 08:10-9мужской, женский, не выбран
true, false, NULL. Или false, true, NULL, кому как больше нравится.
ivankudryavtsev
28.08.2023 08:10+4Не надо так… СУБД не для этого развивались в то, чем они стали. У Вас даже семантика исчезла при данном представлении. Даже две представленные Вами альтернативы об этом свидетельствуют…
vadimr
28.08.2023 08:10-8Для сохранения семантики надо назвать поле HAS_PENIS. Или завуалированно – IS_MAN. Правда, будет неполиткорректно.
Kirill-112
28.08.2023 08:10+5А если пользователь мужчина, но по каким-то причинам лишился пениса? В таком случае нельзя будет поставить ему HAS_PENIS = true.
koreychenko
28.08.2023 08:10+12Ну, тогда HAD_PENIS
saboteur_kiev
28.08.2023 08:10А если пол касается NPC, который может быть гермафродитом, или мало ли что в выдуманном мире?
Не нужно нести бред, лишь бы пошутить. Не забывайте девиз правильной лурки - "факты > лулзы"vadimr
28.08.2023 08:10-2А причём здесь выдуманный мир?
Может быть, вы объясните, с какой целью вообще отслеживать в таблице пол NPC в выдуманном мире, отличный от грамматической категории рода?
Zenitchik
28.08.2023 08:10+6Грамматическая категория рода в русском языке принимает три значения, плюс особые случаи типа "сирота" и "сани". Т.е. если надо хранить род NPC для согласования слов в диалогах, то "родов" у него будет четыре.
А если нужно поддерживать несколько локалей - то чёрт ногу сломит, во что превратится дерево согласовательных классов.
Короче, рассчитывать, что какой-то список будет вечным и неизменным - не стоит. Лучше обрабатывать всё единообразно, через дополнительную таблицу.
EvilShadow
28.08.2023 08:10Только тред начался с колонки "пол". Не социальный "гендер". Не грамматический "род". Обычный биологический "пол". Их у человеков всего два. И NULL для "неизвестно".
EvilShadow
28.08.2023 08:10-3Конечно, не надо. Я бы и сам использовал енам. Но мой комментарий был не о поле, а о значении "не выбрано". Которое как раз и означает отсутствие значение, т.е. то, для чего в соседнем треде сделан костыль:
-- before gender ENUM('man', 'woman') -- after gender ENUM('man', 'woman', 'other')
Вот этот
other
избыточен, если мы следуем формулировке "мужской/женский/не выбран". Причём "не выбран" - это вовсе не то же самое, что "предпочитаю не говорить" или "другой", ибо первое - это отсутствие значения, а второе - сознательный выбор пользователя.mayorovp
28.08.2023 08:10Причём "не выбран" — это вовсе не то же самое, что "предпочитаю не говорить" или "другой", ибо первое — это отсутствие значения, а второе — сознательный выбор пользователя.
Вот вы и сами это признали. А теперь посмотрите на тот "костыль" ещё раз, и осознайте, что other и NULL — это разные значения, а вы их зачем-то решили объединить.
ainoneko
28.08.2023 08:10Мне кажется, проблема с NULL в том, что оно означает одно из "другой", "не скажу", "невозможно определить", "ещё не спрашивали", ..., а какое из них -- не очевидно (надо смотреть в документациии, которая (как обычно) уже устарела).
EvilShadow
28.08.2023 08:10Следите за контекстом. Тред начался с формулировки
Например для колонки Пол (мужской, женский, не выбран).
И указанный код опубликован в этом треде. В этом контексте нет "другого", а в этом коде не указано, что поле с этим енамом может быть NULL. Из чего очевидный вывод: в этом коде other используется ВМЕСТО NULL.
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
.Вообще-то это называется "нормализация".
theIggs
28.08.2023 08:10если список значений содержит более 63 значений - в SMALLINT
Выходит, в некоторых случаях можно сэкономить половину памяти, просто вспомнив, что числа могут быть UNSIGNED (каковыми и полагается быть почти всем этим перечислениям). ???? Да и значение 63 кажется заниженным.
ptr128
28.08.2023 08:10Если в БД реализован механизм сжатия строк, аналогичный row compression в MS SQL, то знаковые числа до 127 (без знаковые до 255) и так будут занимать 1 байт (точнее - полтора), даже если объявлены 64-битными.
Но мне больше импонирует реализация enum в PostgreSQL, когда enumsortorder float4, что позволяет добавлять новые значения в enum до или после указанного существующего значения. Все же порядок сортировки порой имеет значение.
Akina
28.08.2023 08:10Упс... это самый обычный ляпсус. Должно быть если список значений содержит более 255 значений - в SMALLINT.
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). И добавлять значения можно.
spirit1984
28.08.2023 08:10+1Минус енама в том еще, что Вы не можете в этом случае ограничиться чисто изменением в одном месте. Вам придется поправить его значения на уровне базы (если Вы хотите добавить новую опцию), а еще и в приложении, которое долждно этот новый вариант обрабатывать.
nightlord189
28.08.2023 08:10+3Зато если энам на уровне базы, то это гарантирует, что никакое "левое" (пустое или то, которого нет в перечислении) значение в таблицу никогда не сохранится. Мне ближе такой вариант, потому что с ним часто можно намного меньше смотреть в код, а сразу смотреть в базу, чтобы понять, что вообще делает приложение и какие данные у нас могут быть.
А так это шкала между гибкостью и гарантиями целостности, тут каждый сам выбирает, что удобнее для проекта.
Если энам меняется нечасто, то почему бы его и не прописать в базу?
А если новые значения добавляются каждый месяц - лучше оставить строкой/интом и хранить это в приложении.Ivan22
28.08.2023 08:10+8чтобы полчить гарантии целостности как раз и нужна отдельная таблица справочник в которой список допустимых значений, и FK на нее из основной таблицы который и не даст вставить ничего чего нет в справочнике.
xSVPx
28.08.2023 08:10+1Ну так енум это почти тоже самое по функциональности. Некий сахар для удобства. И в ряде случаев вполне оправданный.
spaceatmoon
28.08.2023 08:10Вот только сахар этот с горчинкой. Отказался от enum по похожим причинам указанным в статье.
ptr128
28.08.2023 08:10А можно подробней? Не знаю, как в MySQL, но в PostgreSQL enum - это, по сути, просто автоматический JOIN с таблицей pg_enum, фильтрацией по enumtypeid и по равенству enumsortorder.
А при необходимость изменить enum, модифицировать метаданные таблиц не требуется. Модифицируется только тип.
nightlord189
28.08.2023 08:10+3Можно и в отдельной хранить. Но тогда придется джойнить таблички, чтобы получить полную информацию о сущности. Опять же зайдя в таблицу, удобнее сразу видеть "order 1, status created, type juridical", а не "order 1, status 15, type 84".
В общем, тут кому какой фломастер нравится.Ivan22
28.08.2023 08:10+3"любишь субд использовать - люби и таблички джойнить" (с) народная поговорка
atygaev
28.08.2023 08:10+1звучит как догма. Есть удобный механизм для определенных сценариев и людей. На мой взгляд это как разговор про языки программирования. Мы все выбрали свои собственные (которыми пользуемся большую часть времени) а не одни и те же.
ptr128
28.08.2023 08:10Тут вопрос не в том, что нравится, а насколько пропускная способность соединения критична в данной задаче. В том же protobuf разница размеров записей в Вашем примере - в четыре раза.
Удобней конечно видеть
"Номер ПУ": 123456, "Регистр": "Накопленная реактивная энергия второй фазы", "Значение": 1234.5
чем
"Номер ПУ": 123456, "Регистр": 22, "Значение": 1234.5
Но передавать по каналам связи и хранить в БД второе намного эффективней.
nightlord189
28.08.2023 08:10Если смотрим на пропускную способность - то надо смотреть еще и в каком виде мы запись передаем чаще всего. Может мы в 99% случаев отдаем по апи полную запись со всеми значениями полей, и тогда мы просто каждый раз будем делать бессмысленные джойны, когда их можно было не делать.
Xexa
28.08.2023 08:10-1"Отдельная таблица" и "нормализация" - имели смысл во времена дорогих носителей. И то уже тогда разработчики шли на компромисс между "так канонично и требуется" и "куча джойнов замедляют выборки". В своей жизни разработок и работы с бд, дай бог 2-ю нормальную форму видел, но никогда не первую.
michael_v89
28.08.2023 08:10Более того, относительно нормальных форм часто не учитывают один нюанс. Если вы вынесли данные в отдельную таблицу с первичным ключом, то у вас статичные данные превратились в объект с состоянием и поведением. Теперь можно обновить значение свойства, и оно обновится для всех строк в других таблицах с этим внешним ключом. А до вынесения можно было поменять значение для любой отдельной строки, не меняя другие, где указано то же значение. Это не эквивалентные структуры данных. Они эквивалентны только для данных, которые никогда не меняются.
0xd34df00d
28.08.2023 08:10Непонятно. Точно так же можно обновить в одной транзакции все значения этого свойства во все строках в денормализованном представлении, и добавить новое значение во внешнюю таблицу и поменять ключ при нормализованном представлении. Там полный изоморфизм структур, так что в чем нюанс?
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'. А потом клиент Мария Иванова вышла замуж и стала Мария Петрова, и ее фамилия поменялась во всех предыдущих заказах, хотя правильно указывать новые данные только в новых.
0xd34df00d
28.08.2023 08:10+1Я тут как-то случайно вспомнил, что в известных мне системах, где подобная консистентность с реальным миром действительно важна,
UPDATE
запрещён вообще как класс. Любое изменение — новая запись. Изменилась фамилия клиента перед новой покупкой? Новая запись (хоть нормализованная с ссылкой на новую запись о конкретной фамилии, хоть напрочь денормализованная со встроенным жсончиком о клиенте). Изменился статус? Событие о новом статусе.И вот когда я это вспомнил, то понял, что всё обсуждаемое — проявление этой проблемы. А конкретная степень (де)нормализации тут вторична.
michael_v89
28.08.2023 08:10То что вы описали это и есть то о чем я говорю. Мне непонятно, на что вы возражаете. "UPDATE запрещён вообще как класс" именно потому что данные, вынесенные в отдельную таблицу, превращаются в объект.
хоть нормализованная с ссылкой на новую запись о конкретной фамилии
Так у такой нормализации та же проблема, данные превращаются в объект с поведением и первичным ключом. Теперь нельзя поменять фамилию в одном конкретном заказе, а раньше было можно. Неважно, хранится ли в новой таблице история изменений клиента или актуальное состояние клиента.
Теория баз данных не делает различия, история там или нет, она работает с кортежами данных. И многие ее изложения обычно говорят только "Нормализация предназначена для приведения структуры БД к виду, обеспечивающему минимальную логическую избыточность", не упоминая, что логически результаты до и после вынесения колонок не эквивалентны, и такое вынесение не всегда подходит.
0xd34df00d
28.08.2023 08:10+1Мне непонятно, на что вы возражаете.
Что вообще есть такая проблема, как «обновить часть записей». Записи не обновляют.
"UPDATE запрещён вообще как класс" именно потому что данные, вынесенные в отдельную таблицу, превращаются в объект.
Они запрещены даже в том случае, если ничего никуда не выносят — просто потому, что у вас тогда есть иммутабельность/цепочка для аудита/етц.
не упоминая, что логически результаты до и после вынесения колонок не эквивалентны, и такое вынесение не всегда подходит.
Эквивалентность есть, просто для получения того же результата необходимо сделать немного другую последовательность действий, но это как раз нормально и лично меня не смущает.
michael_v89
28.08.2023 08:10Что вообще есть такая проблема, как «обновить часть записей»
Я не говорил, что есть такая проблема. UPDATE нескольких записей вроде нормально работает во всех базах данных.
Я говорил, что результат до и после вынесения группы полей в таблицу и замену их на значение первичного ключа не эквивалентный, и проявляется это при изменениях данных. Раньше UPDATE "prev_status" на "new_status" делалась для одной строки, после замены их на первичный ключ UPDATE тех же данных "prev_status" на "new_status" повлияет на все связанные строки. Это даже не проблема, а эффект. А нюанс в том, что про него не говорят и часто не учитывают.
Эквивалентность есть, просто для получения того же результата необходимо сделать немного другую последовательность действий
Я говорю "'Сделать X до изменения' не эквивалентно 'Сделать X после изменения". Вы говорите "Сделать X+Y после изменения эквивалентно". Ну так я с этим и не спорил. Напротив, я именно на это и указывал изначально с первого комментария. Даже хотел написать, что для эквивалентности надо делать INSERT в новую таблицу, но подумал, что тут все специалисты, и про это и так знают.
просто потому, что у вас тогда есть иммутабельность/цепочка для аудита/етц
Бизнес-требования иммутабельности это другой вопрос. Эффект, про который я говорю, не зависит от их наличия. Обновление фамилии клиента в предыдущих заказах вполне может никак не влиять на бизнес, и обновление таблицы клиентов может быть разрешено, но разница между тем, когда имя клиента записано в таблице заказов, и когда оно связано через внешний ключ, все равно есть. Иногда она важна, иногда нет, а иногда сначала неважна, а потом становится важна.
Поэтому при вынесении имени клиента в отдельную таблицу надо подумать, на что это повлияет, нет ли у вас документов, где важно исходное имя, а не просто руководствоваться тем, что 'Это же повышение нормальной формы и устранение избыточности, теория рекомендует так делать'. Я об этом говорю.
Ivan22
28.08.2023 08:10+1Може имхо - это того же уровня модель данных, что и JSON со множеством значений в поле таблицы
EvilShadow
28.08.2023 08:10+11Статья называется "Почему тип поля enum на уровне базы — зло", а на самом деле она о "Почему тип поля enum на уровне базы (*в Mysql* при использовании *PHP*) — зло"
Таким образом, тип полей
enum
в базе - это лютое зло.Это инструмент. Как и любым инструментом, им нужно уметь пользоваться.
Также можно ответить на вопрос "почему integer, а не string": дело в том, что поля типа
string
подвержены грамматическим ошибкам и всё что они дают - лишь удобство чтения таких данных в одной конкретной таблице без использования SQL запросов. Но если в слове будет допущена ошибка илиВообще непонятно. Как можно читать данные в таблице *без использования SQL запросов*? SELECT - это не SQL?
слово нужно будет заменить на другое - придётся отправлять запрос в базу, чего не нужно делать при использовании целочисленных значений.
Удачи в изменении порядка значений.
Ivan22
28.08.2023 08:10про "Удачи в изменении порядка значений." - целочисленный PK не стоит использовать для сортировки. Для сортировки стоит использовать отдельное поле в справочнике с условным названием "ordering"
EvilShadow
28.08.2023 08:10+2целочисленный PK не стоит использовать для сортировки
Сортировка по первичному ключу - занятие бессмысленное, абсолютно согласен, но какое это имеет отношение к обсуждаемым enum'ам?
Даже если отставить PK в сторону, то дело не в сортировке. В предложенном варианте с последовательными интами в базе невозможно добавить новое значение в середину списка, ибо оно должно будет переиспользовать существующий индекс, а значит, нужна миграция, которая сдвинет все индексы ниже. Это бесполезная, никому не нужная работа.
В реальном мире проблемы сквозной нумерации могут решаться увеличением порядка, пример: аудитории в учебных заведениях, типа 1xx на первом этаже, 2xx на втором и т.д. Т.е. сознательное создание дырок. Здесь можно применить тот же подход, но зачем, если можно просто не использовать инты?
Может возникнуть "для чего вставлять новое значение именно в середину". Ответ: потому что это требуется для решения задачи.
Ivan22
28.08.2023 08:10если PK используется правильно, и не несет на себе всяких ненужных функций, а-ля сортировка, нумерация документов и т.п. - то на практике никогда и не возникает необходимости ничего вставлять в середину.
p.s. И вообще сейчас люди на UUID переходят, и правильно делают
EvilShadow
28.08.2023 08:10+1Но откуда вообще взялся PK? О нём в посте речи не шло.
Ivan22
28.08.2023 08:10ну если речь зашла про "использование целочисленных значений" вместо строк, а мы все еще не подумали про PK, то где-то модель данных свернула не туда
EvilShadow
28.08.2023 08:10Не улавливаю связи. Речь шла об использовании уникальных значений. Автору не нравятся строки, он предлагает использовать инты. Также, с разной эффективностью, можно было бы использовать float, decimal, uuid, да хоть битовые маски. То, что инты также могут использоваться в качестве первичных ключей - просто совпадение.
Ivan22
28.08.2023 08:10ну да, ну да, крякает как утка, плавает как утка, уникально идентифицирует сущность в таблице, что же это???
EvilShadow
28.08.2023 08:10-2уникально идентифицирует сущность в таблице
enum? С какой стати? enum - это тип данных, допускающий установку одного из нескольких предопределённых значений. Например, [active, inactive]. Или [cart, address, deliver, payment]. Или [monday, tuesday, ..., sunday]. А записей в таблице может быть столько, сколько поддерживает СУБД. И у вас будут сотни миллионов active и миллиарды inactive. Ничто тут ничего уникально не идентифицирует.
sergeaunt
28.08.2023 08:10+2Статья называется "Почему тип поля enum на уровне базы — зло", а на самом деле она о "Почему тип поля enum на уровне базы (в Mysql при использовании PHP) — зло"
А вот и нет. Постгря, например, имеет транзакционный DDL, но добавить значение в enum в транзакции она вам не даст. Мелочь, а неприятно и заставляет задуматься, что, видимо, всё непросто с этими енамами, раз не осилили транзакционность.
Akina
28.08.2023 08:10Целочисленный индекс значения - это данные, а вот его строковое представление - это метаданные. И изменение описания типа потенциально затрагивает как то, так и другое. А такое изменение для параллельной транзакции - это безусловный пердимонокль. Так что постгресс совершенно логично посылает в даль туманную с подобным намерением.
Helldar Автор
28.08.2023 08:10Вообще непонятно. Как можно читать данные в таблице *без использования SQL запросов*? SELECT - это не SQL?
Скорректировал статью. Имелось ввиду без использования джойнов. Да и при просмотре таблицы через любой инструмент запрос руками не пишется. Например:
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; }
И ничего не изменится на уровне базы ¯\_(ツ)_/¯
pantsarny
28.08.2023 08:10+2Enum ограничивает список допустимых значений, а ваш integer - допускает огромный список значений. Что если кто-то залезет в базу ручками и установит значение 100? Как будет вести себя ваше приложение? Именно для этого и придуман enum. Да, в MySQL изменение структуры таблицы подразумевает создание новой таблицы. Но вы ни слова не сказали о ключевой функциональности enum - ограничения списком допустимых значений, а тупо проигнорировали это и перенесли решение на уровень приложения.
Ivan22
28.08.2023 08:10+4вы что-нибудь слышали про constraints в целом и Foreign Key в частности?
pantsarny
28.08.2023 08:10+1да, но причем тут это?
vadimr
28.08.2023 08:10+1Содержать список допустимых значений в виде таблицы-словаря с foreign key.
pantsarny
28.08.2023 08:10зачем тут таблица и foreign key?
vadimr
28.08.2023 08:10+1Именно чтобы не хардкодить константы в операторах sql.
pantsarny
28.08.2023 08:10приведите пример, непонятно ничего
vadimr
28.08.2023 08:10Да вот выше человек захардкодил в базу enum ('man', 'woman'), а потом стал локализовывать код в страну, где 46 половых принадлежностей*, и всё сломалось. А на следующем этапе ещё получит за новый enum по голове от какой-нибудь полиции нравов в стране, где два пола.
*не является пропагандой нетрадиционных отношений.
pantsarny
28.08.2023 08:10а зачем для полов отдельная таблица?
vadimr
28.08.2023 08:10+1А зачем вообще константы выносить из кода программы? Именно для того, чтобы пользователь мог настроить значения в соответствии со своими требованиями.
Я понимаю, зачем enum был нужен в языке Си, где из структур данных имелись только массивы, которые индексируются целыми числами. Но в SQL это ненужная вещь.
pantsarny
28.08.2023 08:10+1enum нужен ровно для того - чтобы ограничивать список допустимых значений. я не понимаю что вы вынесете в отдельную таблицу, тут же только значение айди интересует и не более. вы будете хранить в исходной таблице идентификатор, и в таблице пола идентификатор? ради fk только? это какая то фигня
semmaxim
28.08.2023 08:10+2Это сейчас Вам нужно только два значения - 1 ("man") и 2 ("woman"). А потом Вы открываете для себя другие страны. И в одной стране за количество полов меньше 40 Вас отменят, а в другой за больше 2 Вас посадят. Вот и понадобится переделать в табличку, которая бы хранила пол и данные о том, кому и когда его можно показывать.
Хотя, конечно, данный конкретный пример довольно специфичен....
anone8729347
28.08.2023 08:10Это не поможет запретить создать новую запись и добавить её ключ. Т.е. любой, у кого есть доступ на инсерт, может создать левое значение, и сломать логику приложения.
Это узкий кейс, но он опровергает тезис- "Таким образом, тип полей
enum
в базе - это лютое зло.".
michael_v89
28.08.2023 08:10+1Что если кто-то залезет в базу ручками и установит значение 100? Как будет вести себя ваше приложение?
Совершенно спокойно упадет с исключением при конвертации из int в enum приложения. Ничем не отличается от ситуации, когда из-за сбоя оперативной памяти вместо 'racing' появилось 'raaing'.
Helldar Автор
28.08.2023 08:10Либо будет восприниматься как
null
если при конвертации используется вызов методаtryFrom
вместоfrom
. Это зависит от реализации в приложении.
Helldar Автор
28.08.2023 08:10Но вы ни слова не сказали о ключевой функциональности enum - ограничения списком допустимых значений, а тупо проигнорировали это
Во втором абзаце это написано:
Он обеспечивает ограничение значений, которые может принимать колонка, и позволяет более строго контролировать данные. Это может быть полезно для хранения статусов, категорий, типов или любых других значений, которые могут быть заданы только из ограниченного набора вариантов.
spacediver
28.08.2023 08:10+14Шёл 2023 год, программисты продолжали не доверять системам управления базами данных в части управления базами данных...
Abobcum
28.08.2023 08:10+6Разве enum не хранится в базе как int + таблица имён ? Мне казалось, все современные субд используют эту обёртку автоматически. Тем более, это даёт огромный выигрыш по памяти.
pantsarny
28.08.2023 08:10enum хранится как целочисленное значение, даже арифметические операции доступны
koreychenko
28.08.2023 08:10+8Enum это не зло. Это просто еще один способ обеспечения целостности базы данных. История про то, что "enum не позволяет мне легко косячить" - ну, он как бы для этого и сделан. А то, что enum использовался для живого постоянно изменяющегося списка - дык это просто неправильно база спроектирована.
Aquahawk
28.08.2023 08:10+2Скорее в посте есть архитектурная правда, заключающая в том что в живо продукте любой список или множество живые и изменяющиеся.
koreychenko
28.08.2023 08:10В целом да. Но есть же lean development, который рекомендует не делать лишнюю работу, если на горизонте нет ничего, что бы предвещало возможность изменений в будущем. Ну, т.е. список жанров - понятно что должен быть динамическим. Но ведь бывают какие-то чисто бизнесовые деления, например, статусы воркфлоу.
Т.е. ты на этапе разработки бизнес-процесса закладываешь допустимые статусы. И это может быть enum, потому что если потребовалось изменить значение в базе, это значит, что и процесс уже другой.
Короче, архитектор он на то и архитектор, чтобы голову включать. А то можно докатиться до того, что число Pi переменной делать, мало ли что :-)Aquahawk
28.08.2023 08:10+1lean development, который рекомендует не делать лишнюю работу, если на горизонте нет ничего, что бы предвещало возможность изменений в будущем.
Я считаю что это в корне неверное понимание принципов lean. Я очень часто сталкивался что люди оправдывают принципами lean или kiss закладывание сложно расширяемых принципов проектирования, когда заложить расширяемость на старте особо много не стоит, иногда эта стоимость пренебрежимо мала. Если строим небоскрёб, то фундамент, стройплощадку и краны сразу под небоскрёб надо подвозить, а не так что давайте одноэтажный домик построим, потом научимся достраивать на него этаж, и повторим 100 раз. Нет, это так не работает.
0xd34df00d
28.08.2023 08:10ИМХО самый хороший принцип — не делать почти наверняка ненужную работу сейчас (часто вам эти небоскребы нужны?), и строить систему так, чтобы БД/тайпчекер/етц кричали на вас, если ваши ожидания не оправдались, и работа оказалась нужной.
Aquahawk
28.08.2023 08:10Вы уверены, что заведение отдельной таблицы и ссылка на неё это большое количество работы, которое не стоит делать, и, что вы этим что-то сэкономите. Это ничтожное количество работы за большое количество простоты расширения в будущем. Имхо lean говорит, что это нужно сделать. Не стоит делать сложного, но не вся избыточность сложная. И вообще, никакая человеческая деятельность сделанная без избыточности не стабильна. Надо закладывать избыточную прочность в здание, в машину, надо чтобы на званом ужине было больше еды, чем съедят, шведский стол в отеле не должен пустеть. Мы не боги, и не можем предсказать точные потребности в будущем, поэтому дёшево купить избыточность очень даже выгодно. И, на мой взгляд, это не противоречит lean. Если пытаться интерпретировать lean радикально и фанатично, то нужно не делать всё, что можно не делать, то давайте отступы в коде писать не будем, сэкономим байты на пробелах. Давайте гит не будем юзать, нам ведь пока не требуется откатывать изменения, вот когда потребуется, тогда гит и применим. Lean не про это.
0xd34df00d
28.08.2023 08:10В этом случае — нет, не уверен. У меня недостаточно опыта с подобными проектами, чтобы иметь разумные prior'ы о том, как себя ведут enum'ы в БД.
Helldar Автор
28.08.2023 08:10Статусы тоже меняются. Реже чем жанры игр, но меняются. На прошлой работе, например, ввели новые статусы "ожидание доставки" и "ожидание возврата".
Aquahawk
28.08.2023 08:10+1Вообще говоря в каждом проекте где я настраивал процессы, я всегда создавал новые статусы, потому что процессы всегда разные.
aleksandy
28.08.2023 08:10можно докатиться до того, что число Pi переменной делать
Даже это имеет некий смысл.
ptr128
28.08.2023 08:10Шутки шутками, но в случае, когда из-за необходимости очень высокой точности используется PostgreSQL decimal, Pi приходится вычислять, как переменную, рядом Эйлера, в зависимости от заданной точности.
EvilShadow
28.08.2023 08:10Есть списки неизменные, например, биологический пол: есть всего два варианта. А если изменяющиеся, например, гендер. И enum'ы, конечно, нужно использовать только для неизменных.
piton_nsk
28.08.2023 08:10+6Проще использовать единообразный подход - отдельная табличка со списком значений и внешний ключ. Не надо думать что неизменно, а что может поменяться. Проблем с отдельной табличкой нет никаких.
Кроме того часто бывает что все эти статусы обрастают дополнительными атрибутами типа SortOrder, FullName, ShortName, etc.
EvilShadow
28.08.2023 08:10+1Проблем с отдельной табличкой нет никаких.
Да, в atlassian тоже так думают. А потом смотришь на список таблиц jira, а там
(860 rows)
piton_nsk
28.08.2023 08:10800 табличек это имхо меньшая проблема, чем 800 энумов. Особенно если придерживаться соглашения об именовании.
EvilShadow
28.08.2023 08:10Трудно представить ситуацию, в которой у вас найдётся 800 неизменяемых списков.
qrKot
28.08.2023 08:10-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'));
a1ez
28.08.2023 08:10Предпочитаю varchar и constraint(по необходимости).
Не люблю инты в силу того, что через некоторое время хрен разберёшь что за status=4 и для интерпретации данных из таблицы, всё время приходится обращаться к энамам из приложения.
DarkMike
28.08.2023 08:10+1Поэтому предпочитаю справочники, в которых прописаны enum-ы приложения. Всегда запросом можно увидеть что есть status=4. Ну и расширить список статусов просто. Сложно только обеспечивать синхронность справочников и приложения. Но тут один раз сделал и работает :)
avost
28.08.2023 08:10+1Запросом-то можно... Но часто лазишь по табличкам в каком-нибудь визуальном туле и на кажлый новый статус лазить в справочник задалбливает. А может вообще не быть лосиупа к запросам, а только какая-нибудь ограниченная визуализация необходимых полей. И, да, синхронизация может отставать или вообще не соответствовать.
vlivyur
28.08.2023 08:10DBeaver в помощь
avost
28.08.2023 08:10Визуальный тул для построения запросов? Да, в запросы я и сам могу. Если позволяют. Но иногда не позволяют.
drath
28.08.2023 08:10SQLYog в дата-вьювере на дабл-клик по полю с FK выдает поисковик по референсной таблице. Все жду когда это уже в PHPStorm завезут
michael_v89
28.08.2023 08:10Там можно нажать Ctrl+B в ячейке, он открывает другую таблицу с фильтром по этому значению.
vlivyur
28.08.2023 08:10Нет. Он при клике в ячейку, ссылающуюся на другую таблицу, может показывать значение из этой таблицы и вид настраивается. И не надо переписывать запрос, добавляя join'ы, выполнять его, а потом заново искать нужную строчку
Но да, всё равно в ответе видишь только Id'шники и надо везде кликать, чтоб узнать что же там скрывается за ним
BackDoorMan
28.08.2023 08:10Для таких случае придумали LowCardinality. Вероятно, не в той СУБД, которую вы используете, правда.
vagon333
28.08.2023 08:10+1Возможно повторюсь, но:
1. нормализация прежде всего: все списки в отдельной таблице и ссылки по id
С ростом размера базы (кол-во таблиц, колонок, записей), приложение вам скажет спасибо.
2. если угораздило получить enum, незачем создавать доп. temp колонку: расширьте список на новое значение (+racing), замените в таблице значение колонки на новое (rasing->racing), удалите старый enum.
binakot
28.08.2023 08:10+3PostgreSQL прекрасно умеет делать модификацию значений перечислений без необходимости промежуточных "махинаций", и уже довольно давно https://www.postgresql.org/docs/current/sql-altertype.html. Неужели в MySQL это не так, что-то сомневаюсь. Честно говоря, статья притянута за уши.
Первое же предложение "Часто разработчики интересуются почему не рекомендуется использовать тип поля
enum
в базе данных" намекает на какую-то надуманность, т.к. за 15 лет ни разу не слышал подобной постановки вопроса о ENUM в БД.А вся статья себя по итогу оправдывает тем, что разработчики выкатили на прод в базу кривое значение в enum: "Через какое-то время была замечена грамматическая ошибка в слове "racing" и принято решение её исправить."
n0isy
28.08.2023 08:10Да. Но нет. Не умеет "в транзакции". А миграции в нормальной туле идут в транзакциях.
breninsul
28.08.2023 08:10ну бывает, что изменилась логика и прежнее название перестало отображать суть.
но сама проблема надумана, да.
RolexStrider
28.08.2023 08:10+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 |
Что я делаю не так?
EvilShadow
28.08.2023 08:10+1Используете postgres, а у автора mysql.
ptr128
28.08.2023 08:10+4Я должен был догадаться? И что в тегах статьи тогда делают "postgresql, postgres"?
EvilShadow
28.08.2023 08:10Это очень хороший вопрос, но лучше задать его автору. Лично я догадался по обратным кавычкам в примерах запросов.
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(); }); } };
ptr128
28.08.2023 08:10+5Но тогда главный тезис статьи все же не верен и должен относиться не к базе, а к конкретному фреймворку.
Helldar Автор
28.08.2023 08:10-1Со стороны примеров кода да, а вот со стороны
enum
- какую бы БД с поддержкой данного типа ни выбрали, будет больно её обновлять если запросы кидаются не в чистом виде, а в наше время в этом нет необходимости когда достаточно пару-тройку методов вызвать.BlenderRU
28.08.2023 08:10В Постгре никаких проблем закастить в текст и изменить/отказаться от enum'ов без чистки данных столбца.
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
Akina
28.08.2023 08:10в MySQL тип ENUM реализован (в имплементации структур данных) используя целочисленный тип (16-битовое представление).
Либо 8-битное, либо 16-битное - зависит от количества значений в перечислении. См. Data Type Storage Requirements
Arm79
28.08.2023 08:10Мы следуем следующей логике.
1) делаем справочник вместо перечислений, если изменение списка допустимых значений не приводит к необходимости доработки существующего кода
2) используем enum, если каждое изменение перечня значений - это изменение функциональности в коде, и выводится на пром релизом, в котором учтены скрипты миграции
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;
}
z250227725
Зачем названия таблиц и полей в SQL запросе даны в кавычках? Ещё и константное значение взято в такие же кавычки…
И второй вопрос — при использовании int не придётся ли конструкторы обмазывать проверками передаваемых значений?
Helldar Автор
SQLYog такой формат предложил, я не стал отказываться т.к. выглядит, на мой взгляд, красиво.
Перед записью в базу валидацию лучше всегда прикручивать т.к. будь то строка, число или сам enum, всегда нужно убеждаться что в базу улетят правильные значения.
Ivan22
некоторые субд поддерживают case-sensitive (и наличие специмволов) для названий объектов таких как поля например. В итоге чтобы корректно их указывать применяют кавычки.
zuek
Там "не совсем кавычки" - это символ, который нам называли "мягкий апостроф" - многие конструкторы запросов его подставляют, да и точки с дефисами иногда в названиях полей допускаются - тут без ограничителей обойтись будет трудно...
klopp_spb
Зачем названия таблиц и полей в SQL запросе даны в кавычках?
Ты пошутил?