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

18

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


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

  • Пользователь может принадлежать только одной компании
  • Компания может иметь много пользователей

Дизайн стола «Фирменный» довольно прост. Компания будет иметь следующие атрибуты / столбцы: (давайте будем проще)

ID, COMPANY_NAME, CREATED_ON

Первый сценарий

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

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CREATED_ON

Второй сценарий

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

Например:

  • Компания A хочет сохранить: LIKE_MOVIE (логическое значение), LIKE_MUSIC (логическое значение)
  • Компания B хочет хранить: FAV_CUISINE (String)
  • Компания C хочет сохранить: OWN_DOG (логическое), DOG_COUNT (int)

Подход 1

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

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, LIKE_MOVIE, LIKE_MUSIC, FAV_CUISINE, OWN_DOG, DOG_COUNT, CREATED_ON

Что довольно неприятно, потому что у вас будет множество NULLS и пользовательских строк, у которых есть столбцы, которые для них не имеют значения (т. Е. Все пользователи, принадлежащие компании A, имеют значения NULL для FAV_CUISINE, OWN_DOG, DOG_COUNT)

Подход 2

Второй подход заключается в том, чтобы иметь «поле свободной формы»:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_1, CUSTOM_2, CUSTOM_3, CREATED_ON

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

Подход 3

Я посмотрел в поле PostgreSQL JSON, в этом случае у вас будет:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_PROFILE_JSON, CREATED_ON

В таком случае, как вы сможете применять различные схемы для пользователя? У пользователя с компанией A будет схема, которая выглядит как

 {"LIKE_MOVIE":"boolean", "LIKE_MUSIC": "boolean"}

Хотя пользователь с компанией C будет иметь другую схему:

 {"OWN_DOG ":"boolean", "DOG_COUNT": "int"}

Как мне решить эту проблему? Как правильно спроектировать базу данных, чтобы использовать эту гибкую схему для одного «объекта» (пользователя) на основе их отношения (компании)?

реляционное решение? Nosql решение?


Изменить: я также думал о таблице "CUSTOM_PROFILE", которая будет по существу хранить пользовательские атрибуты в строках, а не столбцах.

Есть 2 проблемы с этим подходом:

1) Данные растут в расчете на пользователя в виде строк, а не столбцов - и это означает, что для получения полной картины пользователя необходимо выполнить много соединений, несколько соединений с таблицей «пользовательский профиль» с различными пользовательскими атрибутами

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

noobcser
источник
3
Если разные компании имеют разные, многозначные наборы данных по каждому клиенту, то вам абсолютно необходима таблица ссылок COMPANY_CUSTOMER. Все остальное очень скоро причинит вам сильную боль.
Килиан Фот
Как таблица ссылок может помочь с пользовательскими данными? столбцы все равно должны быть разными
noobcser
1
Вы должны представить факт «пароль Килиана для IKEA -« котенок »» с помощью кортежа, такого как «КОМПАНИЯ: IKEA, ЗАКАЗЧИК: Kilian, АТРИБУТ: пароль, ЗНАЧЕНИЕ: котенок». Ничего более простого не сделает работу.
Килиан Фот
3
Схема - это фиксированная вещь по определению; Вы не можете настроить его, если не знаете, какие поля вам нужны. Взгляните на Entity-Attribute-Value для одностороннего решения подобных проблем в реляционной базе данных.
Мейсон Уилер

Ответы:

13

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

Сначала давайте воспользуемся схемой вашей компании.

[Companies] ComnpanyId, COMPANY_NAME, CREATED_ON

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

[Users] UserId, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CREATED_ON

Затем мы создадим таблицу, в которой мы определим наши динамические атрибуты, которые являются специфическими для пользовательских компаний. Итак, вот пример значения столбца Attribute будет «LikeMusic»:

[UserAttributeDefinition] UserAttributeDefinitionId, CompanyId, Attribute

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

[UserAttributes] UserAttributeDefinitionId, UserId, Value

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

Вы также можете переместить CompanyId из таблицы UserAttributeDefiniton в таблицу перекрестных ссылок для проверки в будущем.

П. Роу
источник
спасибо - я думаю о таком подходе - см. редактирование. 2 проблемы: 1) Данные растут в виде строк, что означает, что для получения полной картины пользователя вам придется выполнять много объединений. 2) «значение» всегда будет храниться как VARCHAR, чтобы быть универсальным, даже если это значение на самом деле int или boolean и т. Д.
noobcser
1
Если вы используете int / bigint для идентификаторов таблиц и присоединяетесь к тем, у вас не будет проблем с производительностью, пока вы не наберете огромное количество строк. Теперь, если вы начнете поиск по значениям атрибутов, это может стать проблемой, если вы начнете получать огромное количество записей. В этом случае я бы работал с администратором базы данных, чтобы определить, существуют ли индексы, которые можно создать, или, возможно, индексированное представление, которое может ускорить поиск такого рода. Я использовал подобную схему, и она потребляет 100 миллионов записей в год без каких-либо проблем с производительностью, поэтому базовый дизайн работает довольно хорошо. IMO
P. Roe
Если необходимы отчеты, фильтрация, запросы и разные атрибуты могут принадлежать разным наборам данных. Будет ли этот подход лучше, чем NoSQL? Я пытаюсь понять разницу в производительности. Подобная ситуация только пользователь может определить отчеты, которые содержат пользовательские поля.
Кос
В вышеупомянутом подходе, как мы реализуем вещь поиска, как diff. компании хотят искать по своим полям, в том числе по пользователям. Что такое правильный подход , чтобы обеспечить масштабируемое поиск на вершине этого
techagrammer
Вы можете искать это обычно с большим количеством соединений. Вы можете использовать скрипт ETL для извлечения данных, которые вы хотите найти, и поместить их в более денормализованную структуру. Наконец, вы можете попытаться использовать индексированные представления в качестве метода поиска. Лично я рекомендую метод ETL для создания денормализованных структур, которые легко найти.
П. Роу
7

Используйте базу данных NoSQL. Там будут документы компании и пользователя. У пользователей будет часть их схемы, динамически создаваемая на основе шаблона пользователя (текст, указывающий поля / типы для этой компании.

\Company\<uniqueidentifier>
    - Name: <Name>
    - CreatedOn: <datetime>
    - UserTemplate: <Text>

\User\<uniqueidentifier>
    - COMPANY_ID: <ID>
    - FIRST_NAME: <Text>
    - LAST_NAME: <Text>
    - EMAIL: <Text>
    - CREATED_ON: <datetime>
    - * Dynamically created fields per company

Вот как это может выглядеть в Firebase.com. Вам нужно будет научиться делать это на любом другом языке.

JeffO
источник
это то, о чем я думаю, или, может быть, столбцы JSON. Какова производительность при запросах, фильтрации отчетов по сравнению с решением, предложенным PRoe.
Кос
1
Каждый раз, когда вы сжимаете данные в json или xml и затем помещаете их в столбец, поиск будет ужасно медленным. Если вам нужно найти данные, представленные в моем ответе выше, я бы посоветовал использовать индексированные представления для извлечения данных. Если это решение не является идеальным, я бы рекомендовал использовать ETL для копирования данных в структуру, которую можно легко найти и отчитаться.
П. Роу
В вышеупомянутом подходе, как мы реализуем вещь поиска, как diff. компании хотят искать по своим полям, в том числе по пользователям. Что такое правильный подход , чтобы обеспечить масштабируемое поиск на вершине этого
techagrammer
В базах данных nosql у вас могут быть избыточные данные, но они структурированы таким образом, чтобы их можно было искать. Показанный выше по уникальному идентификатору. Другой может быть \ Company \ Name. Это похоже на наличие нескольких индексов.
Джефф
3

Если вы часто сталкиваетесь с настраиваемыми полевыми запросами, я бы фактически смоделировал их аналогично базе данных. Создайте таблицу, содержащую метаданные о каждом настраиваемом поле, CompanyCustomField (кому оно принадлежит, тип данных и т. Д.) И другую таблицу CompanyCustomFieldValues, которая содержит CustomerId, FieldId и значение. Если вы используете что-то вроде Microsoft Sql Server, у меня будет столбец значения типа sql_variant.

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

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

Энди
источник
спасибо - я думаю о таком подходе - см. редактирование. 2 проблемы: 1) Данные растут в виде строк, что означает, что для получения полной картины пользователя вам придется выполнять много объединений. 2) «значение» всегда будет храниться как VARCHAR, чтобы быть универсальным, даже если это значение на самом деле int или boolean и т. Д.
noobcser
1
@noobcser Данные, растущие в виде строк, на самом деле не имеют значения, ведь базы данных строятся вокруг строк и объединений. В любом случае, вы, скорее всего, используете для этого Common Table Expressions, которые довольно хороши в этом. Я не уверен, что если вы пропустили ту часть, в которой, как я сказал, вы можете использовать sql_variant в качестве типа данных для столбца значений, в котором значение хранится как любой тип, который вы в него вставляете. Хотя я и называю имена функций MS SQL-сервера, я бы ожидал, что другие зрелые СУБД будут иметь аналогичные функции.
Энди
1
@noobcser FYI В своей карьере я довольно часто сталкивался с этими требованиями, и у меня есть опыт работы с каждым из предложенных решений, поэтому я предлагаю тот, который работал лучше всего в моем опыте. Использование типов данных xml для такого рода вещей - отчасти то, почему я ненавижу то, что MS добавляет xml как собственный тип данных.
Энди
1

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

По мере добавления пользовательских атрибутов ALTER TABLE profile_attrib ADD COLUMN like_movie TINYINT(1)вы можете запретить их удаление. Это сведет к минимуму ваше присоединение, но при этом обеспечит гибкость.

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

Крис Сойферт
источник
Регулярное выражение [^\w-]+должно довольно хорошо это делать, не допуская ничего, что не 0-9A-Za-z_-является - но да, санитарная обработка необходима для защиты от злонамеренности или глупости.
Обычный Джо
0

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

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

Чтобы получить больше информации:

https://msdn.microsoft.com/en-us/library/hh403385.aspx

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

Джон Рейнор
источник
Это может также работать, но я чувствую, что становится ограниченным, когда вам действительно нужно что-то делать с данными, хранящимися в поле XML / JSON.
Энди
@Анди - правда, есть еще один слой. Запрашивать БД и анализировать XML, а не просто запрашивать БД. Я не знаю, назову ли я это ограничивающим, просто более громоздким. Но было бы что-то рассмотреть, если бы пользовательские атрибуты широко использовались.
Джон Рейнор
В T-SQL можно определить содержимое в столбце XML / JSON для пространства имен и выполнить запрос к элементам пользовательских данных. Это не сложно
Стивен Йорк
-1

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

USER_ID, LIKE_MOVIE, LIKE_MUSIC

USER_ID, FAVORITE_CUISINE

USER_ID, OWN_DOG, DOG_COUNT

Этот подход предполагает, что вы будете знать форму информации, которую компания хочет хранить заранее, и что она не будет часто меняться. Если форма данных неизвестна во время разработки, вероятно, было бы лучше использовать это поле JSON или базу данных nosql.

mortalapeman
источник
-1

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

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

Поставщик базы данных не взимает с вас плату за таблицу, и вам не нужно удваивать дисковое пространство для удвоения количества таблиц (фактически, наличие двух таблиц более эффективно, поскольку вы не сохраняете атрибуты A для пользователей B. Даже хранение только NULL занимает место).

Конечно, если есть достаточно общих полей, вы можете выделить их в общую таблицу Users и иметь внешний ключ в каждой из пользовательских таблиц компании. Это настолько простая структура, что с ней не может справиться ни один оптимизатор запросов к базе данных. Любое необходимое СОЕДИНЕНИЕ тривиально.

MSalters
источник
3
И если у вас есть тысячи клиентов, таблица на каждого из них может быстро стать неуправляемой, не говоря уже о том, что вам потребуется специальный код для пользовательских полей каждого клиента.
Энди
@ Энди: Угадай что? Ситуация станет еще более неуправляемой, если вы объедините тысячу различных схем в одну таблицу! И да, вам, вероятно, нужен собственный код для пользовательских полей. Опять же, это проще, а не сложнее, если у каждого клиента есть чистый отдельный стол. Попытка выбрать поля компании X из тысячи других - кровавый беспорядок.
MSalters
Вы имеете в виду мой ответ или идею ОП о добавлении всех дополнительных столбцов в таблицу клиентов?
Энди
2
Цель здесь - найти поддерживаемое и масштабируемое решение. Создание таблицы для каждого клиента, безусловно, противоположно этому. Каждый раз, когда вы подключаетесь к новому клиенту, нереально: запустить сценарий создания таблицы, обновить код (объекты Entity) и выполнить повторное развертывание.
tsOverflow
Вся эта идея использования общих таблиц для всех клиентов сама по себе является отдельным обсуждением архитектуры SaaS, и есть несколько веских причин держать клиентов в разных таблицах (или даже в разных базах данных, что позволяет выполнять резервное копирование / восстановление и масштабирование для каждого клиента). В этом сценарии создание столбцов cusotm в главной таблице не представляет никакой сложности. Я проголосовал, и мне интересно, почему люди понижают это только потому, что им не нравится такой подход. Эффект внутренней платформы - это реальность: с помощью модели EVA ваши запросы будут сложнее,
экономнее, честнее
-1

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

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_VALUES

CUSTOM_VALUES будет иметь тип строки хранения ключ и пара значений. ключ будет именем столбца, а значение будет значением столбца, например

LIKE_MOVIE;yes;LIKE_MUSIC;no;FAV_CUISINE;rice

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

Я использовал эту логику, и она отлично работает, просто вам придется применять логику фильтрации в коде, а не в запросе.

techExplorer
источник