关系数据库中的键值对

时间:2008-09-24 09:50:19

标签: sql database

是否有人有将密钥值对存储在数据库中的经验?

我一直在使用这种类型的表:

CREATE TABLE key_value_pairs ( 
    itemid           varchar(32) NOT NULL,
    itemkey         varchar(32) NOT NULL,
    itemvalue       varchar(32) NOT NULL,
    CONSTRAINT ct_primarykey PRIMARY KEY(itemid,itemkey)
)

然后例如可以存在以下行:

 itemid            itemkey        itemvalue    
 ----------------  -------------  ------------ 
 123               Colour         Red            
 123               Size           Medium             
 123               Fabric         Cotton

此方案的问题在于提取数据所需的SQL语法非常复杂。 仅创建一系列键/值列会更好吗?

CREATE TABLE key_value_pairs ( 
    itemid            varchar(32) NOT NULL,
    itemkey1        varchar(32) NOT NULL,
    itemvalue1      varchar(32) NOT NULL,
    itemkey2        varchar(32) NOT NULL,
    itemvalue2      varchar(32) NOT NULL,
 . . .etc . . .
)

查询会更容易,更快,但缺乏第一种方法的可扩展性。 有什么建议吗?

18 个答案:

答案 0 :(得分:119)

在继续你的方法之前,我会谦虚地建议你退一步考虑是否真的想将这些数据存储在“键 - 值对”表中。我不知道你的应用程序,但我的经验表明,每次我做了你正在做的事情,后来我希望我创建了一个颜色表,一个织物表和一个尺寸表。

考虑引用完整性约束,如果采用键值对方法,数据库无法告诉您何时尝试在大小字段中存储颜色ID

考虑加入具有10个值的表的性能优势与可能在多个域中具有数千个值的通用值。关键值的索引真的有用吗?

通常,做你正在做的事情背后的原因是因为域名需要是“用户可定义的”。如果是这种情况,那么即使我不打算让你动态创建表格(尽管这是一种可行的方法)。

但是,如果您的推理是因为您认为它比多个表更容易管理,或者因为您正在设想一个适用于所有域的维护用户界面,那么在继续之前停下来思考一下。

答案 1 :(得分:16)

在大多数情况下,你会使用第一种方法,这是因为你没有真正坐下来思考你的模型。 “好吧,我们还不知道钥匙会是什么”。一般来说,这是非常糟糕的设计。它实际上比将键作为列更慢,这应该是它们。

我也会质疑为什么你的id是varchar。

在极少数情况下你真的必须实现一个键/值表,第一个解决方案没问题,但是,我通常希望将键放在一个单独的表中,这样你就不会将varchars存储为键。你的键/值表。

例如

CREATE TABLE valid_keys ( 
    id            NUMBER(10) NOT NULL,
    description   varchar(32) NOT NULL,
    CONSTRAINT pk_valid_keys PRIMARY KEY(id)
);

CREATE TABLE item_values ( 
    item_id NUMBER(10) NOT NULL,
    key_id  NUMBER(10) NOT NULL,
    item_value VARCHAR2(32) NOT NULL,
    CONSTRAINT pk_item_values PRIMARY KEY(item_id),
    CONSTRAINT fk_item_values_iv FOREIGN KEY (key_id) REFERENCES valid_keys (id)
);

然后你甚至可以坚持并在键上加一个“TYPE”,允许进行一些类型检查。

答案 2 :(得分:16)

还有另一种解决方案介于两者之间。您可以使用xml类型列作为键和值。所以你保留itemid字段,然后有一个xml字段,其中包含为某些键值对定义的xml,如<items> <item key="colour" value="red"/><item key="xxx" value="blah"/></items> 然后,当您从数据库中提取数据时,您可以通过多种不同方式处理xml。取决于您的使用情况。这是一个可扩展的解决方案。

答案 3 :(得分:13)

我曾经在数据库中使用键值对来创建电子表格(用于数据输入),其中出纳员将通过现金抽屉工作来总结他的活动。每个k / v对表示用户输入货币金额的命名单元格。这种方法的主要原因是电子表格很容易发生变化。常规添加新产品和服务(因此出现了新的细胞)。此外,在某些情况下不需要某些细胞,可能会被丢弃。

我写的应用程序是对应用程序的重写,该应用程序确实将出纳员表分成不同的部分,每个部分在不同的表中表示。这里的问题是,随着产品和服务的增加,需要进行架构修改。与所有设计选择一样,与另一个相比,采取某种方向是有利有弊的。我的重新设计肯定表现得更慢,更快地消耗了磁盘空间;但是,它非常灵活,允许在几分钟内添加新产品和服务。然而,唯一需要注意的问题是磁盘消耗;我记不起其他的头痛了。

如前所述,我通常考虑键值对方法的原因是用户 - 这可能是业务所有者 - 想要创建具有用户特定属性集的自己的类型。在这种情况下,我做出了以下决定。

如果不需要通过这些属性检索数据,或者一旦检索到一大块数据就可以将搜索推迟到应用程序,我建议将所有属性存储在单个文本字段中(使用JSON,YAML,XML等)。如果强烈需要通过这些属性检索数据,则会变得混乱。

您可以创建单个“属性”表(id,item_id,key,value,data_type,sort_value),其中sort列将实际值转换为字符串可排序表示。 (例如日期:“2010-12-25 12:00:00”,编号:“0000000001”)或者您可以按数据类型创建单独的属性表(例如string_attributes,date_attributes,number_attributes)。两种方法的众多优点和缺点:第一种更简单,第二种更快。两者都会让你写出丑陋复杂的查询。

答案 4 :(得分:6)

根据经验,我发现某些密钥将被更广泛地使用或更频繁地查询。然后,我们通常会稍微对设计进行非规范化,以在主“项目”表中包含特定字段。

例如。如果每个项目都有一个颜色,您可以将颜色列添加到项目表中。 Fabric和Size可以较少使用,并且可以在键值对表中保持独立。您甚至可以将颜色保留在键值对表中,但是复制项目表中的数据以获得性能优势。

显然,这取决于数据以及您需要键值对的灵活性。它还可能导致您的属性数据无法正确定位。但是,反规范化确实极大地简化了查询并提高了性能。

我通常只考虑在性能变得和问题时进行去规范化,而不仅仅是简化查询。

答案 5 :(得分:2)

PostgreSQL 8.4支持hstore数据类型,用于在单个PostgreSQL数据字段中存储(键,值)对的集合。 请参阅http://www.postgresql.org/docs/8.4/static/hstore.html了解其使用信息。虽然这是一个非常古老的问题,但想过传递这个信息,认为它可能对某人有帮助。

答案 6 :(得分:2)

我认为设计此类表格的最佳方法如下:

  • 将常用字段作为数据库中的列。
  • 提供一个Misc列,其中包含字典(以JSON / XML /其他字符串形式显示),其中包含字段作为键值对。

突出点:

  • 在大多数情况下,您可以编写正常的SQL查询来查询SQL。
  • 您可以对键值对执行FullTextSearch。 MySQL有一个全文搜索引擎,否则你可以使用稍微慢一点的“喜欢”查询。虽然全文搜索很糟糕,但我们认为此类查询较少,因此不应导致太多问题。
  • 如果您的键值对是简单的布尔标志,则此技术与为该键具有单独的列具有相同的功能。对键值对的任何更复杂的操作都应该在数据库外部完成。
  • 查看一段时间内查询的频率,可以告诉您需要在列中转换哪些键值对。
  • 此技术还可以轻松强制对数据库进行完整性约束。
  • 它为开发人员提供了一种更自然的路径,可以重新考虑他们的架构和代码。

答案 7 :(得分:2)

我不明白为什么提取数据的SQL应该对你的第一个设计来说很复杂。当然要获得项目的所有值,您只需执行此操作:

SELECT itemkey,itemvalue FROM key_value_pairs WHERE itemid='123';

或者如果您只想要该项目的一个特定键:

SELECT itemvalue FROM key_value_pairs WHERE itemid='123' AND itemkey='Fabric';

第一种设计还可以让您随时轻松添加新密钥。

答案 8 :(得分:1)

只要仍然可以满足业务要求,违反规范化规则就可以了。 key_1, value_1, key_2, value_2, ... key_n, value_n可以正常,直到您需要key_n+1, value_n+1

我的解决方案是用于共享属性的数据表和用于唯一属性的XML。这意味着我同时使用两者。如果所有(或大多数东西)都有大小,那么size是表中的一列。如果只有对象A具有属性Z,则Z被存储为XML,类似于Peter Marshall的回答。

答案 9 :(得分:1)

第一种方法在你提到的成本上更加灵活。

第二种方法永远不可行,如你所示。相反,你会做(根据你的第一个例子)

create table item_config (item_id int, colour varchar, size varchar, fabric varchar)

当然,只有在知道数据量并且不会发生很大变化时,这才会起作用。

作为一般规则,任何要求更改表格的DDL以进行正常工作的应用程序都应该给出第二和第三个想法。

答案 10 :(得分:1)

如果您的密钥很少,那么我只将它们存储为列。但是如果可能的密钥集很大,那么你的第一种方法是好的(第二种方法是不可能的)。

或者是这样每个项目只能拥有有限数量的密钥,但密钥可能来自大型密钥?

您还可以考虑使用对象关系映射器来简化查询。

答案 11 :(得分:1)

第一种方法很好。您可以创建一个提取所需数据的UDF,然后调用它。

答案 12 :(得分:0)

如果这些键是动态的,或者有很多键,那么请使用您拥有的映射表作为第一个示例。此外,这是最通用的解决方案,随着您添加更多密钥,将来最佳扩展,可以轻松编写SQL以获取数据,并且数据库将能够比您想象的更好地优化查询(即,我不会过早地优化这种情况,除非事后证明它是测试的瓶颈,在这种情况下你可以考虑下面的两个选项。)

如果密钥是已知集,并且其中没有多少(&lt; 10,可能&lt; 5),那么我没有看到将它们作为项目上的值列的问题。

如果有中等数量的已知固定密钥(10 - 30),则可能有另一个表来保存item_details。

但是我没有看到需要使用你的第二个示例结构,它看起来很麻烦。

答案 13 :(得分:0)

我认为你做的是正确的,只要给定类型的项目的键/值经常变化。
如果它们是静态的,那么简单地使项目表更宽更有意义。

我们使用类似(但更复杂)的方法,围绕键/值有很多逻辑,以及每个键允许的值类型的表。
这允许我们将项目定义为键的另一个实例,并且我们的中心表将任意键类型映射到其他任意键类型。它可以迅速将你的大脑系在一起,但是一旦你编写并封装了逻辑来处理它,你就会有很大的灵活性。

如果需要,我可以写下我们所做的更多细节。

答案 14 :(得分:0)

如果你走KVP表的路线,我不得不说我自己不喜欢这种技术,因为它确实难以查询,那么你应该考虑使用一个项目id聚集在一起的值适用于任何平台的适当技术。

RDBMS倾向于分散行以避免插入时出现块争用,如果要检索8行,则很容易发现自己正在访问表的8个块来读取它们。在Oracle上,您最好考虑使用哈希集群来存储这些内容,这将极大地提高访问给定项ID的值的性能。

答案 15 :(得分:0)

您的示例不是使用键值对的非常好的示例。一个更好的例子是在计费应用程序中使用诸如Fee表,Customer表和Customer_Fee表之类的东西。费用表包括以下字段:     fee_id,fee_name,fee_description Customer_Fee表将包含以下字段:     customer_id,fee_id,fee_value

答案 16 :(得分:0)

第二个表严重失调。我会坚持第一种方法。

答案 17 :(得分:0)

时代变了。现在您可以在关系数据库旁边使用其他数据库类型。 NOSQL选择现在包括,列存储,文档存储,图形和多模型(请参阅:http://en.wikipedia.org/wiki/NoSQL)。

对于键值数据库,您的选择包括(但不限于)CouchDb,Redis和MongoDB。