您将如何设计具有自定义字段的用户数据库


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(字符串)
  • 公司C要存储:OWN_DOG(布尔值),DOG_COUNT(整数)

方法1

暴力方式是为用户提供一个单一的架构,并在不属于公司的情况下让其为空:

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

这有点麻烦,因为您最终会得到很多NULL,并且用户行的列与它们不相关(即,属于公司A的所有用户的FAV_CUISINE,OWN_DOG,DOG_COUNT的值为NULL)

方法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”表,该表实际上将用户属性存储在行而不是列中。

这种方法有两个问题:

1)每位用户的数据随着行而不是列的增长而增长-这意味着要获得用户的全貌,需要完成许多联接,并且需要对不同的自定义属性进行多次联接到“自定义配置文件”表

2)即使我们知道数据应该是整数或布尔值,也总是将数据值存储为VARCHAR以便泛型


3
如果不同的公司在每个客户上都有不同的多值数据集,那么您绝对需要一个COMPANY_CUSTOMER链接表。其他一切都会很快给您带来巨大的痛苦。
Kilian Foth,2015年

链接表如何帮助定制数据?列仍将有所不同
noobcser 2015年

1
您必须使用诸如“ COMPANY:IKEA,CUSTOMER:Kilian,ATTRIBUTE:password,VALUE:kitten”之类的元组来表示“ IKEA的Kilian密码是'kitten'”这一事实。任何简单的事情都无法完成。
Kilian Foth,2015年

3
根据定义,模式是固定的东西。如果您不知道所需的字段是什么,则无法进行设置。看一下Entity-Attribute-Value的一种这样的问题通常会在关系数据库中得到解决。
梅森惠勒2015年

Answers:


13

请考虑将其作为替代方案。前两个示例都将要求您随着应用程序范围的扩大而对模式进行更改,此外,“ custom_column”解决方案很难扩展和维护。最终,您将得到Custom_510,然后想象一下该表将如何工作。

首先,让我们使用“公司”架构。

[Companies] ComnpanyId, COMPANY_NAME, CREATED_ON

接下来,我们还将对所有公司将使用/共享的顶级必填属性使用“用户”架构。

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

接下来,我们建立一个表,在此表中,我们将定义特定于每个公司自定义用户属性的动态属性。因此,“属性”列的示例值将为“ LikeMusic”:

[UserAttributeDefinition] UserAttributeDefinitionId, CompanyId, Attribute

接下来,我们定义一个UserAttributes表,该表将保存用户属性值

[UserAttributes] UserAttributeDefinitionId, UserId, Value

可以通过多种方式对此进行修改,以提高性能。您可以将多个表用于UserAttributes,使每个表都特定于存储在Value中的数据类型,或者仅将其保留为VarChar并用作键值存储。

您可能还希望将CompanyId从UserAttributeDefiniton表中移出,并移到交叉引用表中以供将来校对。


谢谢-我虽然对这种方法-请参阅编辑。2个问题:1)数据随着行的增长而增长,这意味着要获得用户的全貌,您必须进行大量的联接。2)即使值实际上是int或boolean
等值

1
如果对表标识使用int / bigint并加入这些标识,那么除非行数过多,否则不会有任何性能问题。现在,如果您开始基于属性值进行搜索,那么如果您开始获取大量记录,则可能会出现问题。在这种情况下,我将与DBA一起确定是否存在可以创建的索引,或者是否存在可以加快此类搜索速度的索引视图。我使用了类似的架构,因此每年可记录1亿条记录,而没有任何性能问题,因此基本设计非常有效。IMO
P. Roe 2015年

如果需要报告,过滤,查询,并且不同的属性可能属于不同的数据集。这种方法会比NoSQL更好吗?我试图了解性能差异。类似的情况,只有用户可以定义包含用户定义字段的报告。
kos

在上述方法中,我们如何实现搜索对象diff。公司希望搜索其字段,包括用户字段。在此基础上提供可扩展搜索的正确方法是什么
techagrammer'Aug

您可以使用很多联接来正常搜索它。您可以使用ETL脚本提取要搜索的数据,并将其放置在更规范化的结构中。最后,您可以尝试将索引视图用作搜索的方法。我个人建议使用ETL方法来生成易于搜索的非规范化结构。
P. Roe,

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这样的外观。无论您选择哪种方式,您都必须学习如何做。


这就是我在想的,或者也许是JSON列。与PRoe提出的解决方案相比,查询,过滤报告的性能如何。
kos

1
每当您将数据压缩到json或xml中,然后将其扔到一列中时,搜索起来的速度都会非常慢。如果您需要搜索上面我的答案中显示的数据,那么我建议使用索引视图来检索数据。如果该解决方案不理想,那么我建议使用ETL将数据复制到易于搜索和报告的结构中。
P. Roe '18

在上述方法中,我们如何实现搜索对象diff。公司希望搜索其字段,包括用户字段。在此基础上提供可扩展搜索的正确方法是什么
techagrammer'Aug

在nosql数据库中,您可能有冗余数据,但是其结构可搜索。上面显示的是唯一标识符。另一个可能是\ Company \ Name。这类似于具有多个索引。
JeffO

3

如果您经常遇到自定义字段请求,那么我实际上将其建模为数据库。创建一个表,其中包含有关每个自定义字段,CompanyCustomField(它属于谁,数据类型等)的元数据以及另一个包含CustomerId,FieldId和值的表CompanyCustomFieldValues。如果您使用的是Microsoft Sql Server之类的设备,则value列应为sql_variant数据类型。

当然,这并不容易,因为您需要一个界面,该界面可让管理员为每个客户定义自定义字段,而另一个界面则实际使用此元数据来构建UI来收集字段值。并且,如果您还有其他要求,例如将字段分组在一起或需要执行选择列表类型的字段,则需要将其与更多的元数据/其他表(例如CompanyCustomFieldPickListOptions)相匹配。

这是不平凡的,但是它的优点是不需要为每个新的自定义字段更改数据库/更改代码。自定义字段的任何其他功能也需要进行编码(例如,如果您要正则表达式验证字符串值,或者仅允许某些范围之间的日期,或者需要基于另一个自定义字段值启用一个自定义字段)。


谢谢-我虽然对这种方法-请参阅编辑。2个问题:1)数据随着行的增长而增长,这意味着要获得用户的全貌,您必须进行大量的联接。2)即使值实际上是int或boolean
等值

1
@noobcser在所有数据库都围绕行和联接进行设计之后,随着行增长的数据并不重要。无论如何,您更可能为此使用Common Table Expressions,这在这类事情上非常出色。我不确定是否错过了我说的可以使用sql_variant作为value列的数据类型的部分,该列将值存储为您坚持使用的任何类型。当我命名MS SQL Server功能名称时,我希望其他成熟的DBMS具有类似的功能。
安迪

1
@noobcser仅供参考,我实际上在我的职业生涯中经常遇到这些要求,并且对每种建议的解决方案都有经验,所以我建议一个在我的经验中效果最好的解决方案。对于此类事情使用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类型的数据库(SQL Server)。以文本形式存储会限制您的查询能力(例如在自定义属性上进行搜索),但是如果您只需要存储和检索,那么它是一个很好的解决方案。如果需要查询,那么将XML存储为XML类型将是更好的选择(尽管这是特定于供应商的)。

只需在客户表上添加一个添加列,就可以使客户存储任意数量的属性。可以将属性存储为哈希集或字典,因为所有内容都将以字符串开头,所以将失去类型安全性,但是如果对日期,数字和布尔值强制使用标准格式字符串,则可以正常工作。

了解更多信息:

https://msdn.microsoft.com/zh-CN/library/hh403385.aspx

@WalterMitty的答案也是有效的,尽管如果遵循许多继承模型,如果有许多具有不同属性的客户,那么最终可能会有很多表。这取决于在客户之间共享多少个自定义属性。


这也可以工作,但是一旦您真正需要对存储在XML / JSON字段中的数据进行某些操作,我就会感到局限。
安迪

@Andy-是的,还有另一层。查询数据库和解析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数据库。


-1

出于某种原因,数据库是内部平台效应最常出现的一个领域。这只是反模式弹出的另一种情况。

在这种情况下,您将尝试使用自然而正确的解决方案。公司A的用户不是公司B的用户,他们应该为自己的字段使用自己的表。

您的数据库供应商不会按表向您收费,并且不需要两倍于表的磁盘空间(实际上,拥有两个表会更有效,因为您不会为B用户存储A的属性。甚至只存储NULL。占用空间)。

当然,如果有足够的公共字段,则可以将它们分解到共享的Users表中,并在每个公司特定的用户表中都有一个外键。这种结构非常简单,因此没有数据库查询优化器会为此感到困扰。任何必要的JOIN都是微不足道的。


3
而且,如果您有成千上万的客户,那么每个表很快就会变得难以维护,更不用说您将需要为每个客户的自定义字段提供自定义代码。
安迪

@安迪:猜猜是什么?如果将一千种不同的方案混合到一个表中,情况将更加难以维持!是的,您可能确实需要用于自定义字段的自定义代码。同样,如果每个客户都有一张干净的独立桌子,那将更简单,而不是更难。试图从另外一千个其他公司中挑选X公司的领域真是一团糟。
MSalters 2015年

您是在指我的答案还是将所有多余的列添加到客户表上的OP想法?
安迪

2
这里的目标是找到一个可维护且可扩展的解决方案。为每个客户创建表绝对是相反的。每当您招募新客户时,执行以下操作都是不现实的:运行创建表脚本,更新代码(实体对象)并重新部署。
tsOverflow,2015年

为所有客户使用共享表的整个想法本身就是一个单独的SaaS体系结构讨论,并且有一些充分的理由将客户保留在不同的表中(或什至在不同的数据库中,从而允许按客户备份/还原和扩展)。在这种情况下,在主表中创建cusotm列很容易。我投票赞成,我想知道为什么人们仅因为他们不喜欢这种方法而投票赞成。内部平台的影响是现实的:通过使用EVA模型,您的查询将变得更难,更难保存,更难以完整性,等等
。– drizin

-1

我的解决方案假定您将从程序中调用此查询,并且您应该能够执行后处理。您可以具有以下列:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_VALUES

CUSTOM_VALUES将为字符串类型,存储键和值对。键将是列名,值将是列值,例如

LIKE_MOVIE;yes;LIKE_MUSIC;no;FAV_CUISINE;rice

在此CUSTOM_VALUES中,您将仅保存现有的那些信息。从程序查询时,可以拆分此字符串并使用它。

我一直在使用这种逻辑,并且运行良好,只是您必须在代码中而不是在查询中应用过滤逻辑。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.