Зачем вам хранить перечисление в БД?


69

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

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



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

4
@JBKing ... просто посмотри на пол в Facebook.


3
Если ваши клиенты «обманутые Tumblrites», то вы чертовски хорошо создаете схему базы данных, которая позволяет вам создавать то, что соответствует их потребностям, по крайней мере, если вы намерены оставаться в бизнесе.
Gort the Robot

Ответы:


74

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

Какую ценность вы храните в базе данных?

Таким образом, я мог бы хранить 'C', 'H', 'M'и 'L'в базе данных. Или 'HIGH'и так далее. Это имеет проблему со строковыми данными. Существует известный набор допустимых значений, и если вы не сохраните этот набор в базе данных, с ним может быть сложно работать.

Почему вы храните данные в коде?

У вас есть List<String> priorities = {'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'};или что-то на этот счет в коде. Это означает, что у вас есть различные сопоставления этих данных в правильном формате (вы вставляете все заглавные буквы в базу данных, но отображаете их как Critical). Ваш код теперь также трудно локализовать. Вы связали представление идеи в базе данных со строкой, которая хранится в коде.

Везде, где вам нужен доступ к этому списку, вам нужно иметь дублирование кода или класс с кучей констант. Ни один из которых не является хорошим вариантом. Не следует также забывать, что существуют другие приложения, которые могут использовать эти данные (которые могут быть написаны на других языках - в веб-приложении Java используется система отчетов Crystal Reports и пакетное задание Perl, передающее данные в него). Механизму создания отчетов потребуется знать действительный список данных (что произойдет, если в 'LOW'приоритете ничего не отмечено и вам нужно знать, что это допустимый приоритет для отчета?), А пакетное задание будет содержать информацию о том, что является действительным значения есть.

Гипотетически, вы можете сказать: «Мы - магазин с одним языком - все написано на Java», и у вас есть один файл .jar, содержащий эту информацию, - но теперь это означает, что ваши приложения тесно связаны друг с другом, а этот файл .jar содержит данные. Вам нужно будет выпускать часть отчетов и пакетного обновления вместе с веб-приложением каждый раз, когда происходят изменения, и надеяться, что этот выпуск пройдет гладко для всех частей.

Что происходит, когда ваш начальник хочет другого приоритета?

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

При использовании подхода enum-in-the-table вы обновляете список enum, чтобы иметь новый приоритет. Весь код, который получает список, извлекает его из базы данных.

Данные редко стоят отдельно

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

Возвращаясь к полу, как упомянуто в вопросе, немного: у Пола есть ссылка на используемые местоимения: he/his/himи she/hers/her... и вы хотите избежать жесткого кодирования этого в самом коде. А потом приходит ваш босс, и вам нужно добавить, что у вас есть 'OTHER'пол (чтобы все было просто), и вам нужно соотнести этот пол с they/their/them... и ваш босс видит, что есть у Facebook и ... ну, да.

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

А как насчет других хранилищ данных?

Независимо от того, где вы храните это, тот же принцип существует.

  • Вы можете иметь файл, priorities.propкоторый имеет список приоритетов. Вы читаете этот список из файла свойств.
  • У вас может быть база данных хранилища документов (например, CouchDB ), в которой есть запись для enums(а затем написать функцию проверки в JavaScript ):

    {
       "_id": "c18b0756c3c08d8fceb5bcddd60006f4",
       "_rev": "1-c89f76e36b740e9b899a4bffab44e1c2",
       "priorities": [ "critical", "high", "medium", "low" ],
       "severities": [ "blocker", "bad", "annoying", "cosmetic" ]
    }
    
  • Вы можете получить XML-файл с небольшой схемой:

    <xs:element name="priority" type="priorityType"/>
    
    <xs:simpleType name="priorityType">
      <xs:restriction base="xs:string">
        <xs:enumeration value="critical"/>
        <xs:enumeration value="high"/>
        <xs:enumeration value="medium"/>
        <xs:enumeration value="low"/>
      </xs:restriction>
    </xs:simpleType>
    

Основная идея та же самая. В самом хранилище данных должен храниться и применяться список допустимых значений. Размещая это здесь, легче рассуждать о коде и данных. Вам не нужно беспокоиться о проверке того, что у вас есть каждый раз (верхний регистр или нижний? Почему chriticalв этом столбце есть тип? И т. Д.), Потому что вы знаете, что вы получаете из хранилища данных точно то, что хранилище данных ожидает от вас отправлять в противном случае - и вы можете запросить хранилище данных для получения списка допустимых значений.

Еда на вынос

Набор допустимых значений - это данные , а не код. Вам действительно нужно стремиться к СУХОМУ коду - но проблема дублирования заключается в том, что вы дублируете данные в коде, а не уважаете их место в качестве данных и сохраняете их в базе данных.

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

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

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


6
Если вы можете добавить значение enum в ваш код без необходимости изменения какой-либо логики (и чтобы это не было его локализованным отображением), я сомневаюсь в необходимости дополнительного значения enum. И хотя я достаточно взрослый, чтобы ценить способность легко запрашивать резервные копии базы данных с помощью простых запросов SQL для анализа проблемы, с ORM в наши дни вы можете делать это очень хорошо, даже не обращая внимания на базовую базу данных. Я не понимаю смысла локализации (местоимений) здесь - такого материала, конечно, не должно быть в базе данных, но есть файлы ресурсов, которые я бы сказал.
Voo

1
@ Voo местоимения является примером других данных, связанных с этим enumeque значением. Без данных, находящихся в таблице, должны присутствовать строково-типизированные значения без надлежащих ограничений FK. Если у вас есть местоимения (как это) в файле ресурсов, у вас есть связь между базой данных и файлом (обновите базу данных и повторно разверните файл). Рассмотрим перечисления redmine, которые можно изменить через интерфейс администратора на лету без необходимости повторного развертывания .

1
... помните также, что базы данных являются хранилищем данных полиглота. Если вам требуется, чтобы проверка выполнялась как часть ORM на одном языке, вы сделали необходимость дублировать эту проверку на любом другом используемом вами языке (недавно я работал с внешним интерфейсом Java, в котором Python помещал данные в базу данных - системы Java ORM и Python должны договориться о вещах - и это соглашение (допустимые типы) легче всего было реализовать, если база данных обеспечит его использование таблицы enum.).

2
@Voo. Использование enum в Redmine такое же, как и в bugzilla: «самая важная таблица содержит все ошибки системы. Она состоит из различных свойств ошибок, включая все значения enum, такие как серьезность и приоритет». - Это не текстовое поле произвольной формы, это значение, которое является одним из этого известного и перечислимого набора. Это не перечисление времени компиляции , но все же перечисление. Смотрите также богомол .

1
Итак, чтобы подтвердить - ваша точка зрения состоит в том, что люди никогда не должны использовать Enums? Не было понятно
niico

18

Как вы думаете, какие из них с большей вероятностью приведут к ошибкам при чтении запроса?

select * 
from Person 
where Gender = 1

Или же

select * 
from Person join Gender on Person.Gender = Gender.GenderId
where Gender.Label = "Female" 

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

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


5
Но тогда мы присоединяемся к столам. Если у моей сущности есть два перечисления, я присоединюсь к трем таблицам только для простого запроса.
user3748908

11
@ user3748908 - так? Объединения - это то, в чем БД хороши, а альтернативы хуже - по крайней мере, в глазах людей, которые выбрали этот маршрут.
Теластин

8
@ user3748908: Базы данных не только хорошо справляются с объединениями, но и обеспечивают согласованность. Обеспечение согласованности работает очень хорошо, когда вы можете указать столбец в одной таблице на строку идентификации другой и сказать, что «значение для этого столбца должно быть одним из идентификаторов в этой таблице».
Blrfl

2
Это все верно, но во многих случаях нужно жертвовать объединениями по соображениям производительности. Не поймите меня неправильно. Я все об этом типе дизайна и объединении, но я полагаю, что мир не закончится, если вы обнаружите, что иногда вам не нужны объединения из-за производительности.
Джон

3
Если вам нужно отказаться от присоединения к ссылочным таблицам по соображениям производительности @JonH, вам нужно купить больший сервер или прекратить попытки проталкивать предикаты через большое количество подзапросов (я предполагаю, что вы знаете, что делаете). Таблицы ссылок - это то, что должно быть в вашем кеше в течение нескольких секунд после запуска БД.
Бен

10

Я не могу поверить, что люди еще не упоминали об этом.

Иностранные ключи

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


Вопрос состоит всего из 5 строк и четко гласит: «Помимо очевидной причины обеспечения правильности». Так что никто не упомянул об этом, потому что ОП заявляет, что это очевидно, и он ищет другие оправдания - PS: Я согласен с вами, это достаточно веская причина.
user1007074

6

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

Затем вам нужно будет передать значения перечисления в ваши хранимые процедуры следующим образом:

create stored procedure InsertPerson @name varchar, @gender int
    insert into tblPeople (name, gender)
    values (@name, @gender)

Но подумайте, как бы вы это сделали, если бы хранили эти значения в таблице базы данных:

create stored procedure InsertPerson @name varchar, @genderName varchar
    insert into tblPeople (name, gender)
    select @name, fkGender
    from tblGender
    where genderName = @genderName --I hope these are the same

Конечно, реляционные базы данных создаются с учетом объединений, но какой запрос легче читать?


Вот еще один пример запроса:

create stored procedure SpGetGenderCounts
    select count(*) as count, gender
    from tblPeople
    group by gender

Сравните это с этим:

create stored procedure SpGetGenderCounts
    select count(*) as count, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender
    group by genderName --assuming no two genders have the same name

Вот еще один пример запроса:

create stored procedure GetAllPeople
    select name, gender
    from tblPeople

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

create stored procedure GetAllPeople
    select name, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender

Все эти запросы меньше по размеру и более удобны для сопровождения, если вы хотите сохранить определения enum вне базы данных.


1
Что если это не пол? Я думаю, что мы слишком зациклены на том, что пол - это поле. Что если бы ОП сказал: «Итак, допустим, у меня есть ошибка сущности с полем Приоритет» - ваш ответ изменится?

4
@MichaelT Список возможных значений «приоритета» является частью кода как минимум в той же степени, что и часть данных. Вы видите графические значки для различных приоритетов? Вы не ожидаете, что они сняты с базы данных? И подобные вещи могут быть тематическими и стилизованными и все равно представлять тот же диапазон значений, хранящихся в БД. Вы не можете просто изменить его в базе данных; у вас есть код презентации для синхронизации.
Евгений Рябцев

1

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


1

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

Однако, как только вы захотите рассмотреть возможность доступа к базе данных нескольких приложений, тогда таблица для перечисления имеет большой смысл (другие ответы более подробно описаны в разделе «Почему»). Другая вещь, которую стоит рассмотреть, захочет ли вы или другой разработчик взглянуть на необработанные данные базы данных. Если это так, это может рассматриваться как другое применение приложения (только в том случае, когда лабораторный индикатор является сырым SQL).

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


1

Если у вас есть перечисление кода, которое используется для управления бизнес-логикой в ​​коде, вы все равно должны создать таблицу для представления данных в БД по многим причинам, подробно описанным выше / ниже. Вот несколько советов, чтобы убедиться, что значения вашей БД синхронизированы со значениями кода:

  1. Не делайте поле идентификатора в таблице столбцом идентификации. Включить идентификатор и описание в качестве полей.

  2. Сделайте что-то другое в таблице, чтобы разработчики знали, что значения являются полустатическими / привязаны к перечислению кода. Во всех других справочных таблицах (обычно там, где значения могут быть добавлены пользователями) у меня обычно есть LastChangedDateTime и LastChangedBy, но отсутствие их в таблицах, связанных с перечислением, помогает мне помнить, что они могут изменяться только разработчиками. Документируйте это.

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

  4. Создайте производственные сценарии SQL, которые делают то же самое, но внутри БД. Если они созданы правильно, они также помогут с миграцией среды.


0

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

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

Class GenderList

   Public Shared Property UnfilteredList
   Public Shared Property Male = GetItem("M")
   Public Shared Property Female = GetItem("F")

End Class

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.