关系数据库中的键值对


74

有人在数据库中存储键值对有经验吗?

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

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 . . .
)

这将更容易,更快捷地进行查询,但是缺少第一种方法的可扩展性。有什么建议吗?


该示例不是最佳选择,因为每个项目可能只能具有一种颜色,大小和织物,在这种情况下,您可以使用列作为属性。
无线电控制

Answers:


128

在继续您的方法之前,我谨建议您退后一步,考虑您是否真的要将此数据存储在“键值对”表中。我不知道您的应用程序,但是我的经验表明,每次完成您的操作后,我希望以后创建一个颜色表,织物表和尺寸表。

考虑一下参照完整性约束,如果您采用键值对方法,则当您尝试在size字段中存储颜色id时,数据库将无法告诉您

考虑一下在具有10个值的表上联接的性能优势,而在多个域中可能有成千上万个值的普通值。关键值索引真的有用吗?

通常,做您正在做的事情的原因是因为域需要是“用户可定义的”。如果是这样,那么即使是我也不会敦促您快速创建表(尽管这是可行的方法)。

但是,如果您的推理是因为您认为它比多个表更易于管理,或者因为您正在设想一个对所有域通用的维护用户界面,那么请停下来并认真思考,然后再继续。


11
A ++,这是我想说的,但您清楚地指出了这一点。我要处理的数据库中有几个键/值对表,我每天对此感到遗憾。每次完成任务都是因为“我们现在需要解决方案”,而且每次我都知道这样做是错误的。
马修·沃森

除了SQL之外,是否还有任何其他数据存储系统可以更好地处理KVP?SQL是否做得不好,没有人做得好,还是做得不好?
quillbreaker

@quillbreaker NoSql解决方案经常围绕有效存储kv对。
mavnn 2010年

我可以看到这个答案有很多支持。@Daniel您对这篇关于reddit的“课程3:开放式架构”部分有何看法?似乎reddit正在使用键/值方法,这是他们随着reddit的发展而吸取的教训之一。谢谢。
Guido 2010年

3
@Guido对于某些系统,关系模型不是最佳方法。仅当您选择了关系模型来存储数据时,我的答案才适用。
Darrel Miller 2010年

17

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


那是一种可能的情况,我也更喜欢简单的KV概念。将数据(键,值)与其元数据分开(例如,在单独的“属性”列中的XML配置中)。灵活,可扩展且易于处理(例如JAXB)。扩展业务逻辑时,不必一直更改数据库架构。持久性逻辑(加载/保存)和与域的接口可以使用“ Convention over Configuration”进行开发,而无需为更改/扩展而进行改动。
Michael Marton

使用类似的JSON方法取得了很好的成功。最大的缺点是value字段中的数据在SQL层中无用。换句话说,告别对值字段中的数据进行索引/排序/联接/搜索/过滤。
rinogo

17

在大多数情况下,您将使用第一种方法,这是因为您还没有真正坐下来思考模型。“好吧,我们还不知道键是什么。” 通常,这是相当差的设计。这比实际将键作为列慢得多。

我还要质疑为什么您的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”,从而进行一些类型检查。


13

我曾经在数据库中使用键值对来创建电子表格(用于数据输入),其中出纳员可以通过操作现金抽屉来总结其活动。每个k / v对代表一个用户输入了金额的命名单元格。这种方法的主要原因是电子表格很容易更改。定期添加新产品和服务(因此出现了新单元)。另外,在某些情况下某些单元是不需要的,可以丢弃。

我编写的应用程序是对应用程序的重写,该应用程序确实将柜员表分为不同的部分,每个部分都在不同的表中表示。这里的问题是,随着产品和服务的添加,需要进行模式修改。与所有设计选择一样,与其他设计选择相比,朝着某个方向发展是有利有弊。我的重新设计无疑会降低磁盘速度并更快地消耗磁盘空间。但是,它非常灵活,可以在几分钟内添加新产品和服务。但是,唯一需要注意的问题是磁盘消耗。我没有其他头痛可以回想。

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

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

您可以创建一个“属性”表(id,item_id,键,值,data_type,sort_value),在此表中,排序列会将实际值覆盖为可按字符串排序的表示形式。(例如,日期:“ 2010-12-25 12:00:00”,数字:“ 0000000001”),也可以按数据类型(例如string_attributes,date_attributes,number_attributes)创建单独的属性表。在这两种方法的众多利弊之间:第一种更简单,第二种更快。两者都会使您编写难看的复杂查询。


7

根据经验,我发现某些键将得到更广泛的使用或查询。通常,我们通常会对设计进行稍微归一化,以在主“项目”表中包含特定字段。

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

显然,这取决于数据以及键值对的灵活性。这也可能导致属性数据的位置不一致。但是,反规范化确实可以极大地简化查询并提高其性能。

我通常只会考虑在性能出现问题时取消规范化,而不仅仅是为了简化查询。


6

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


3

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

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

重点:

  • 在大多数情况下,您可以编写普通的SQL查询来查询SQL。
  • 您可以对键值对执行FullTextSearch。MySQL具有全文本搜索引擎,否则您可以使用“点赞”查询,但查询速度稍慢。虽然全文搜索不好,但我们假设此类查询较少,因此不会引起太多问题。
  • 如果您的键值对是简单的布尔标志,则此技术的功能与为键创建单独的列的功能相同。对键值对的任何更复杂的操作都应在数据库外部进行。
  • 查看一段时间内查询的频率,可以告诉您哪些键值对需要按列进行转换。
  • 此技术还使对数据库强制完整性约束变得容易。
  • 它为开发人员提供了重构其架构和代码的更自然的途径。

2

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

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

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

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

第一种设计还使您可以灵活地随时随地轻松添加新密钥。


1
如果其中一个值是日期,并且您想在日期之间搜索某些键,则会变得很复杂。
亚瑟·托马斯

4
逆向考虑查询-找到一组键/值对的itemid,这需要一组级联的联接。由于需要避免选择一个超集,这使情况更加复杂。例如:find(Colour = Red,Size = Medium)不返回itemid 123,因为该集合包含另一行(Fabric = Cotton)
horace

1

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


1

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

还是每个项目只能具有有限数量的键,但是这些键可能来自较大的集合?

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


ORM使查询更加容易,但是并不能提高性能。手工编码的SQL查询可能会提供更好的性能。
mansu

它可能。但是可能不是,他问的不是速度。
汉尼斯·奥夫雷恩(HannesOvrén)2009年

1

第一种方法要灵活得多,但要付出您提到的代价。

正如您所展示的,第二种方法永远都不可行。相反,您会这样做(按照您的第一个示例)

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

当然,这仅在数据量已知且变化不大时才起作用。

作为一般规则,任何需要更改表的DDL以进行正常工作的应用程序都应具有第二和第三种想法。


1

只要仍然可以满足业务要求,就可以违反规范化规则。有key_1, value_1, key_2, value_2, ... key_n, value_n可能是好的,正确的,直到一点,你需要key_n+1, value_n+1

我的解决方案是为共享属性提供数据表,为唯一属性提供XML表。这意味着我要同时使用。如果所有内容(或大多数内容)都具有大小,则大小是表中的一列。如果只有对象A具有属性Z,则类似于Peter Marshall已经给出的答案,将Z存储为XML。


只要仍然可以满足业务需求,就不能违反规范化规则。只要规范化数据不具有性能,就可以违反规范化规则,即使如此,您仍然确实需要数据的规范化版本和规范化的物化视图。
马修·沃森

key_n,value_n解决方案使SQl变得非常困难。您将如何为“面料='棉布'和颜色='红色'的工资编码?您将最终得到:其中(key_1 =“ Fabric”和value_1 =“ Cotton”或key_2 =“ Fabric”和value_1 =“ Cotton “ .... and(...
James Anderson


0

我认为,只要给定类型的项的键/值频繁更改,您就在做正确的事情。
如果它们是静态的,则仅使项目表更宽更有意义。

我们使用类似(但更复杂)的方法,在键/值周围有很多逻辑,还有每个键允许的值类型的表格。
这使我们可以将项目定义为键的另一个实例,并且我们的中央表将任意键类型映射到其他任意键类型。它可以使您的大脑迅速陷入困境,但是一旦您编写并封装了处理所有问题的逻辑,便拥有了很大的灵活性。

如果需要,我可以写出我们要做的更多详细信息。


0

如果键是动态的,或者有很多键,则使用映射表作为第一个示例。此外,这是最通用的解决方案,它在将来添加更多密钥时可最佳扩展,对SQL进行编码以轻松提取数据非常容易,并且数据库将能够比您想象的更好地优化查询(也就是说,除非证明它是稍后测试的瓶颈,否则我不会过早优化这种情况,在这种情况下,您可以考虑下面的两个选项。

如果键是一个已知集合,并且键数目不多(<10,也许是<5),那么我认为将它们作为项的值列不会出现问题。

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

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


0

如果您走KVP表的路线,并且我不得不说我自己根本不喜欢该技术,因为它确实很难查询,那么您应该考虑使用适当的技术将单个项目ID的值聚类在一起无论您使用哪种平台

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


0

您的示例不是使用键值对的很好的示例。一个更好的例子是在计费应用程序中使用诸如费用表,客户表和客户_费用表之类的东西。费用表将由以下字段组成:fee_id,fee_name,fee_description Customer_Fee表将由以下字段组成:customer_id,fee_id,fee_value


0

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

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

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.