地址簿表设计的SQL数据库问题

时间:2008-12-15 18:08:27

标签: sql data-structures entity-attribute-value database-relations

我正在为我的软件编写一个地址簿模块。到目前为止,我已经设置了数据库,它支持非常灵活的地址簿配置。

我可以为我想要的每种类型创建n条目。类型表示此处的数据,如“电子邮件”,“地址”,“电话”等

我有一个名为'contact_profiles'的表。

这只有两列:

id           Primary key
date_created DATETIME

然后有一个名为contact_attributes的表。这个有点复杂:

id       PK
#profile (Foreign key to contact_profiles.id)
type     VARCHAR describing the type of the entry (name, email, phone, fax, website, ...) I should probably change this to a SET later.
value    Text (containing the value for the attribute).

我现在可以链接到这些配置文件,例如从我的用户表中。但是从这里我遇到了问题。

目前我必须为我想要检索的每个值创建一个JOIN。 是否有可能以某种方式创建一个View,它给我一个类型为列的结果?

所以现在我会得到像

这样的东西
#profile type    value
1        email   name@domain.tld
1        name    Sebastian Hoitz
1        website domain.tld

但是得到这样的结果会很好:

#profile email           name            website
1        name@domain.tld Sebastian Hoitz domain.tld

我最初不想像这样创建表格布局的原因是,可能总是要添加的东西,我希望能够拥有相同类型的多个属性。

所以你知道是否有可能动态转换它?

如果您需要更好的描述,请告诉我。

6 个答案:

答案 0 :(得分:4)

您重新发明了名为Entity-Attribute-Value的数据库设计。这种设计有许多缺点,包括你发现的弱点:以常规格式重现查询结果非常困难,每个属性只有一列。

以下是您必须做的事情的示例:

SELECT c.id, c.date_created,
 c1.value AS name,
 c2.value AS email,
 c3.value AS phone,
 c4.value AS fax,
 c5.value AS website
FROM contact_profiles c
 LEFT OUTER JOIN contact_attributes c1
  ON (c.id = c1.profile AND c1.type = 'name')
 LEFT OUTER JOIN contact_attributes c1
  ON (c.id = c1.profile AND c1.type = 'email')
 LEFT OUTER JOIN contact_attributes c1
  ON (c.id = c1.profile AND c1.type = 'phone')
 LEFT OUTER JOIN contact_attributes c1
  ON (c.id = c1.profile AND c1.type = 'fax')
 LEFT OUTER JOIN contact_attributes c1
  ON (c.id = c1.profile AND c1.type = 'website');

您必须为每个属性添加另一个LEFT OUTER JOIN。您在编写查询时必须知道属性。您必须使用LEFT OUTER JOIN而不是INNER JOIN,因为无法强制使用属性(相当于简单地声明列NOT NULL)。

在存储属性时检索属性,然后编写应用程序代码以循环遍历结果集,构建具有每个属性的条目的对象或关联数组,效率要高得多。您不需要以这种方式了解所有属性,也不必执行n方式连接。

SELECT * FROM contact_profiles c
  LEFT OUTER JOIN contact_attributes ca ON (c.id = ca.profile);

如果您需要这种灵活性,如果不使用EAV设计,您在评论中询问该怎么办?如果您确实需要无限的元数据灵活性,SQL不是正确的解决方案。以下是一些替代方案:

  • 存储TEXT BLOB,其中包含以XML或YAML格式构建的所有属性。
  • 使用像Sesame这样的语义数据建模解决方案,其中任何实体都可以拥有动态属性。
  • 放弃数据库并使用平面文件。

EAV和任何这些替代解决方案都是很多工作。如果您真的需要在数据模型中具有这种程度的灵活性,那么您应该非常仔细地考虑,因为如果您可以将元数据结构视为相对不变的话,这将非常简单。

答案 1 :(得分:1)

如果您限制自己在此查询中为每个人显示单个电子邮件,姓名,网站等,我会使用子查询:

SELECT cp.ID profile
  ,cp.Name
  ,(SELECT value FROM contact_attributes WHERE type = 'email' and profile = cp.id) email
  ,(SELECT value FROM contact_attributes WHERE type = 'website' and profile = cp.id) website
  ,(SELECT value FROM contact_attributes WHERE type = 'phone' and profile = cp.id) phone
FROM contact_profiles cp

如果您使用的是SQL Server,还可以查看PIVOT

如果您想要显示多个电子邮件,电话等,请考虑每个配置文件必须具有相同数量的电子邮件,否则您将拥有空白。

我还要考虑类型列。创建一个名为contact_attribute_types的表,其中包含“email”,“website”等。然后,您将contact_attribute_types.id整数值存储在contact_attributes表中。

答案 2 :(得分:0)

您需要生成如下查询:

select #profile,
       max(case when type='email' then value end) as email,
       max(case when type='name' then value end) as name,
       max(case when type='website' then value end) as website
from mytable
group by #profile

但是,每个#profile只显示每种类型的一个值。您的DBMS可能有一个函数,您可以使用而不是MAX将所有值连接为逗号分隔的字符串,或者您可以编写一个。

出于您已经提到的原因,通常最好避免使用这种数据模型!

答案 3 :(得分:0)

您为每种联系类型

创建一个视图

如果您需要从整个表中提取的所有信息,当您需要特定联系人类型的子集时,可以从视图中提取。

我创建了一个存储过程,它将intent {all,phone,email,address}作为参数之一,然后派生数据。我的所有应用程序代码都会调用此存储过程来获取数据。此外,当添加新类型时(应该很少,您可以创建另一个视图并仅修改此sproc)。

我为多个小型/中型系统实施了类似的设计,没有任何问题。

我错过了什么吗?这看起来微不足道?

编辑:

我看到了我所缺少的东西......你正试图在同一时间进行规范化和非规范化。我不确定你的其他业务规则是什么用于提取记录。您可以为配置文件提供多个或空值的电话/电子邮件/地址等。我会保持您的数据格式相同,并再次使用sproc来创建您想要的特定视图。随着业务需求的变化,您可以单独保留数据,只需创建另一个sproc即可访问它。

答案 4 :(得分:0)

对于这个问题,没有一个正确的答案,因为您需要知道,对于您的特定组织或应用程序,业务想要收集多少联系方法,他们想要的当前状态信息,以及他们愿意投入多少灵活性。

当然,这里的很多人都可以对一般企业想做什么做出一些好的猜测,但真正的答案是找出你的项目,你的用户对什么感兴趣。

BTW,关于“最佳”性的所有架构问题都需要这种成本,收益和风险分析。

答案 5 :(得分:0)

现在面向文档的数据库越来越受欢迎,人们可以使用其中一种将所有这些信息存储在一个条目中 - 从而删除所有这些额外的连接和查询。