Это плохая практика, чтобы разрешить пользовательские поля?

17

Вообще говоря, считается ли плохой практикой разрешать пользовательские поля в базе данных для веб-приложения?

Например, я делаю веб-приложение домашнего инвентаря для своей жены, и она захочет определить свои собственные поля для различных предметов. Я планировал позволить ей создавать категории элементов и добавлять «функции» в эти категории. Объектами будут просто ключ / значение, хранящиеся в виде строк. Таким образом, если бы у нее была категория «Аудио CD», например, она могла бы добавить функции для таких вещей, как «исполнитель», «треки» и т. Д. Но в другой категории, например «мебель», она могла бы добавить функции для таких вещей, как «материал». "(дерево, пластик и т. д.). Тогда любой элемент может принадлежать к одной (или нескольким) категориям, добавляя эти функции к элементу.

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

Вообще говоря, как люди справляются с чем-то подобным в «реальной жизни»?

zako42
источник
4
Рассматривали ли вы использование ориентированной на документы базы данных, такой как MongoDB? Вы можете сохранить документ для каждого типа, который действует как схема, которая также может быть отредактирована (возможно, вручную, учитывая небольшой масштаб проекта).
Энди Хант
@AndyBursh Одним из «забавных» битов с текущими postgres является тип данных «json» ( ссылка ). Такой подход позволил бы хранить определенные пользователем поля в этих данных, т.е. документ, что угодно, и затем использовать оставшиеся поля для вещей, которые вы правильно индексируете и тому подобное. Хотя все зависит от использования и трудно сказать, будет ли это работать хорошо для конкретного приложения или нет. Но это то, что нужно знать.
все: отличная дискуссия, спасибо за понимание! @AndyBursh Я слышал о MongoDB, но никогда не читал об этом. Похоже, еще один домашний проект, чтобы поэкспериментировать с ...
zako42

Ответы:

19

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

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

Рассмотрим следующую таблицу:

  + -------------- +
  | вещь |
  | -------------- |
  | id |
  | тип |
  | desc |
  | attr1 |
  | attr2 |
  | attr3 |
  | attr4 |
  | attr5 |
  + -------------- +

Это после того, как вы добавили несколько атрибутов. Вместо того, чтобы attr1притворяться, что он читает artistили tracksили genreили какие-либо атрибуты, которые имеет вещь. И вместо 5, что, если это было 50. Очевидно, что это неуправляемо. Это также требует обновления модели и повторного развертывания приложения для обработки нового поля. Не идеально.

Теперь рассмотрим следующую структуру таблицы:

  + -------------- + + --------------- + + ------------- +
  | вещь | | thing_attr | | attr |
  | -------------- | | --------------- | | ------------- |
  | id | <--- + | thing_id (FK) | +> | id |
  | тип | | attr_id (fk) | + - + | имя |
  | desc | | значение | | |
  + -------------- + + --------------- + + ------------- +

Вы получили свою вещь с ее основными полями. У вас есть еще две таблицы. Один с атрибутами. Каждое поле представляет собой строку в attrтаблице. И затем есть thing_attrпара внешних ключей, относящихся обратно к thingстолу и attrстолу. И тогда у него есть поле значения, в котором вы сохраняете любое значение поля для этой сущности.

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

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

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


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

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

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

  • Вы занимаетесь программированием базы метаданных. Вместо того, чтобы просто сопоставить этот столбец с этим полем с помощью хорошего ORM, вы, вероятно, делаете что-то вроде, select *а затем делаете некоторый сложный код, чтобы выяснить, каковы данные на самом деле (см . ResultSetMetaData в Java ), и затем сохраните их в карте или какой-либо другой тип данных - но не хорошие поля в коде). Это затем отбрасывает значительную часть безопасности типов и опечаток, которые вы имеете с традиционным подходом.
  • Вы, вероятно, отказались от ORM. Это означает, что вы пишете raw sql для всего кода, а не позволяете системе делать всю работу за вас.
  • Вы перестали делать чистые обновления. Что происходит, когда клиент добавляет поле с одним именем, которое также использует ваша следующая версия? На сайте создания матчей обновление, которое хочет добавить hasdateполе для хранения метки времени, уже определено как hasdateлогическое значение для успешного совпадения ... и ваше обновление прерывается.
  • Вы верите, что клиент не нарушает систему, используя какое-то зарезервированное слово, которое тоже нарушает ваши запросы ... где-то.
  • Вы привязали себя к одному бренду базы данных. DDL различных баз данных отличаются. Типы баз данных являются самым простым примером этого. varchar2против textи тому подобное. Ваш код для добавления столбца будет работать на MySQL, но не на Postgres, Oracle или SQL Server.
  • Вы доверяете клиенту на самом деле, чтобы добавить данные хорошо ? Конечно, EAV далек от идеала, но теперь у вас есть несколько ужасных неясных имен таблиц, которые вы, разработчик, не добавили, с неверным типом индекса (если есть), без каких-либо ограничений, добавленных в код, где необходимо быть и так далее.
  • Вы предоставили права на изменение схемы пользователю, запустившему приложение. Маленькие Бобби Брошенные Таблицы не возможны, когда вы ограничены SQL, а не DDL (конечно, вы можете сделать delete * from studentsвместо этого, но вы не можете действительно испортить базу данных плохими способами). Количество вещей, которые могут пойти не так с доступом к схеме из-за несчастного случая или стремительного роста злонамеренных действий.

Это действительно сводится к «не делай этого». Если вы действительно этого хотите, воспользуйтесь известным шаблоном структуры таблицы EAV или базой данных, полностью посвященной этой структуре. Не позволяйте людям создавать произвольные поля в таблице. Головные боли просто не стоят этого.

DougM
источник
4
Вы также заново изобрели базу данных.
user253751
1
@immibis добавлен слой, в котором пользователь может администрировать, не искажая остальную часть базы данных и не требуя повторного развертывания для обновления модели.
1
@immibis EAV уже много лет горячо обсуждается в кругах реляционных баз данных. Теоретически, это не нужно, но на практике вы не можете делать определенные вещи без него.
Росс Паттерсон
1
@ShivanDragon, который идет к подходу NoSQL. Хранилище документов просто хранит документы и не навязывает схему. Таким образом, добавление и удаление полей и анализ документов полностью выходят за рамки самой базы данных (и вы написали свою модель для этого). Это совершенно другой набор компромиссов, чем компрометации реляционной базы данных для структуры EAV.
5

Делать это хорошо сложно.

Для одноразового приложения, такого как то, что вы планируете, вы, конечно, можете просто добавить столбец для каждого поля и предоставить пользовательский интерфейс, который делает определение поля неподготовленным пользователем более безопасным, чем предоставление им командной строки SQL. Или вы можете следовать страшному шаблону Entity-Attribute-Value , который является классическим, хотя и несколько пугающим ответом на такого рода проблемы. Создание пользовательского интерфейса для определения полей EAV обычно намного сложнее, чем для столбцов базы данных, и запросы могут быть довольно сложными, но для большого количества полей ( т. Е. Схем с очень разреженной матрицей) это может быть единственный способ получить работа сделана

Росс Паттерсон
источник
В итоге: маленький проект == KISS. Проворный на землю.
Encaitar
Проблема с обновлениями таблиц базы данных заключается в том, что в зависимости от объема данных и требуемых индексов (настраиваемые поля часто требуют функций поиска), запрос на изменение таблицы может занять огромное количество времени. Короче говоря, MySQL и другие реляционные базы данных просто не являются хорошим средством для такого рода требований.
Оддман
0

Я наткнулся на нечто похожее недавно.

Я сделал 2 таблицы.

1: table Objects 
    Id , name, type

Он все ваши объекты. U установить имя этого.

И тип этого объекта: - для меня доступными типами были - инвентарь, инвентарь, пункт, офис.

И обычная настройка состояла из n элементов: child или инвентарь, который также является child of office, и я использовал таблицу соединения, чтобы соединять объекты друг с другом.

2 table settings 
     organization_Id , title, value , type

Таблица настроек содержит каждое имя поля для данного конкретного типа объекта и значение в значении.

Пример свойства офиса

Расположение, телефон, время работы

И для предметов

  • Количество
  • Цена
  • Штрих-код

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

Поэтому, когда я хочу офис, я загружаю его легко со всеми его связями и настройками, в которых находятся настройки object_I'd (запрошенные объекты)

После этого я поворачиваю все строки из настроек и все.

И в случае, если я хотел, чтобы настройка была специфичной для предмета в инвентаре (не глобального), я устанавливаю object_I'd = я из таблицы отношений object_objects и устанавливаю settings.type = ratio_setting

Надеюсь, вы понимаете, о чем я. Я попытаюсь переформатировать ответ, когда доберусь до ноутбука.

Zalaboza
источник
2
Совет от профессионалов - не публикуйте сообщения на этом форуме со своего телефона. Автокоррекция делает части вашего сообщения нечитаемыми.
BobDalgleish
Хаха, хорошее наблюдение :)
Zalaboza
0

Это плохая практика, чтобы разрешить пользовательские поля?

Нет, это не плохая практика. Это довольно часто. В терминах ОО это называется наследованием. У вас есть базовый класс inventoryItem и два унаследованных класса AudioCD и мебель.

Вообще говоря, как люди справляются с чем-то подобным в «реальной жизни»?

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

Если easy-query наиболее важен для вас и db-space / normalization не имеет значения, вы бы реализовали схему «таблица на иерархию».

Если пространство / нормализация наиболее важны для вас, и более сложные запросы не являются проблемой, вы должны реализовать схему «таблица для типа».

Для получения дополнительной информации см. Наследование таблицы-типа-против-таблицы-иерархии-дотнет или спящий режим Java .

k3b
источник
Я не знаю, если это решает вопрос. Пользователь не изменяет код для создания новых классов
Colin D