将多个值存储在一行的一个字段中而不是单独存储的可能的好处


11

在我们上一次的每周会议上,一个没有数据库管理经验的人提出了以下问题:

“是否有一种场景可以证明以行(字符串)而不是几行的形式存储数据?”

让我们假设有一个表countryStates,我们要在其中存储一个国家的州。在本示例中,我将使用USA,并且为了懒惰,不会列出所有州。

在那里,我们将有两列;一个被称为Country,另一个被称为States。作为讨论在这里,并通过@ srutzky提出的答案时,PK会通过定义的代码ISO 3166-1阿尔法-3

我们的表如下所示:

+---------+-----------------------+-------------------------------------------------------+
| Country | States                | StateName                                             |
+---------+-----------------------+-------------------------------------------------------+
| USA     | AL, CA, FL,OH, NY, WY | Alabama, California, Florida, Ohio, New York, Wyoming |
+---------+-----------------------+-------------------------------------------------------+

当向朋友开发人员询问相同的问题时,他说,从数据流量大小的角度来看,这可能很有用,但是如果我们需要操纵这些数据则没有用。在这种情况下,必须在应用程序代码上具有智能,该智能可以转换列表中的此字符串(假设可以访问此表的软件需要创建一个组合框)。

我们得出的结论是,该模型不是很有用,但是我怀疑可能有一种使之有用的方法。

我想问的是,你们中是否有人已经以一种切实有效的方式看到,听到或做过类似的事情。


现在,假设您有第二张表“ sales”,其中包含发生的每笔销售的数据以及发生销售的州代码。您将如何编写查询以生成带有列(StateName,TotalSalesAmount)的报告?很难吧?
zgguy

究竟。我也不同意这种模式。我们会在需要恢复任何类型的数据(或者如果有用的话,有用的数据)的任何时候陷入困境。
Human_AfterAll

一种可能的情况是存储变量。商店a;b;c,使用前端解析你的字符串,你再拿到abc在执行和实施做与他们的东西,也许?感觉它可能会以这种方式满足某种特定需求。您始终可以存储ID,联接表并创建串联字符串,然后才能将内容发送到FE ...
Nelz 2016年

为了公平起见(至少对我而言;-),我建议在其他答案中使用2个字符的国家/地区代码 :-)。
所罗门·鲁茨基

2
请注意,没有人会担心将值“ Alabama”存储在列中,而不是拥有一个单独的带有STATE,N和C列的表,其中“ STATE STATE的名称具有第N个字符C”。因为1.我们不打算查询名称的字符,或者2.我们不介意调用函数NTH_CHAR(N,S)在每行带有名称的返回“字符串S的第N个字符”的情况下进行操作。(与JOIN和其他关系运算符通过额外的表消除了一些这样的行。)同上表示整数和NTH_DIGIT(N,I)。对于特定数据库中的什么是关系原子的,始终是一种判断。
philipxy

Answers:


13

首先,当前“问题”标题涉及“将数据存储为字符串而不是列”,这有点令人困惑。说到将数据存储为字符串而不是其他内容时,通常是指将所有内容序列化为字符串格式,而不是适当/强的数据类型(例如INTDATETIME)。但是,如果要询问将数据作为多个值存储在单个字段中而不是单独的行中,那就有些不同了。公平地讲,虽然连接值最容易使用字符串完成,但也可以使用INTBINARY类型完成,通过位掩码或类似地保留某些位置以具有不同的含义。由于第二种解释是实际要问的问题,因此基于问题的文本,让我们解决这个问题。

一言以蔽之:否。如果要存储实际的数据点,那么这只会带来痛苦(就代码和性能而言),因为这是不必要的复杂性。如果该值只能存储为一个单位,不能更新为一个单位,并且永远不会在数据库中反汇编,那么就可以了,因为它大致类似于存储图像或PDF。否则,任何试图解析数据也将无效使用任何索引(例如,使用LIKE '%something%',或CHARINDEX,或PATINDEX,或SUBSTRING等)。

如果您需要将单独的值存储在一行的单个字段中,那么可以使用更合适的方法:XML或JSON。这些是可解析的格式(XML / JSON),甚至可以索引 XML 。但理想情况下,此数据将存储在正确键入的字段中,以便真正有用。

并且请不要忘记,RDBMS的目的是存储数据,以便可以在符合ACID要求的限制内尽可能高效地检索处理数据。由于需要首先解析值,因此检索级联的值已经很糟糕了,并且这是不可索引的。但是操纵通常意味着替换整个Blob只是为了更新它的一部分(假设不存在可与函数一起使用的模式)。XML数据类型至少允许XML DML进行简单更新,尽管这些更新仍不如对正确建模的数据进行简单更新那样快。REPLACE

同样,在上述问题所示的情况下,通过将所有StateCode串联在一起,您将无法(朝任一方向)外键这些值。

而且,如果业务需求随时间变化,并且您需要跟踪这些项目的其他属性,该怎么办?就“州”而言,首都,人口,排序顺序或其他什么呢?正确存储为行,您可以添加更多列以获取其他属性。当然,您可以具有多个可分析的数据级别,例如,|StateCode,Capital,Population |StateCode,Capital,Populate|...但希望任何人都可以看到该问题呈指数级增长而不受控制。当然,使用XML和JSON格式可以很轻松地解决此特定问题,这就是如上所述的价值。但你还是需要一个非常使用或者作为那些既不造型的最初手段将永远被视为有效作为单独的行使用离散场很好的理由。


9

实际上,我实际上只是出于有限的目的使用了类似的东西。我们为输出文件创建了一个标题表。它们是专门构造的,大部分只是列标题,但不完全是列标题。所以数据看起来像

OutputType   OutputHeader
PersonalData Name|Address|City|State|Zip
JobInfo      Name|JobName|JobTitle

从本质上讲,它看起来像是定界列表。一方面是这样。但是出于我们的目的,它是一个长字符串。

这就是窍门。如果您从未计划解析列表,那么值得保存列表。但是,如果您将甚至需要解析列表,则值得将其拆分并保存在单独的行中的额外空间和时间。


1

我曾经在一个很小的表中使用过它,例如:

CREATE TABLE t1 (
  ID number,
  some_feature   varchar2(100),
  valid_channels  varchar2(100));

CREATE TABLE channel_def (
  channel varchar2(100));

然后将值存储CRM,SMS,SELF-CARE到中valid_channel

整个表有大约10条记录。 valid_channel包含实际上应该在描述多对多关系的链接表中的值。Table t1不会被大量使用,因此我们决定走这条路。不过,这项决定涉及一些政治因素(请参阅下文)。

但总的来说,我避免使用3NF。

我现在工作的地方到处都是这样的专栏。他们的理由是它使查询更容易:与其使用链接表联接三个表,不如直接使用定义表LIKE。例如

SELECT * 
  FROM t1 
 INNER JOIN channel_def cd
    ON ','||t1.valid_channels||',' LIKE '%,'||cd.channel||',%';

在Oracle上+ +是可怕的,因为启动它禁用了索引的使用'%,'


哪一个比较慢:LIKE还是简单的连接?
Human_AfterAll

最好在已索引或至少具有引用约束(FK)的列上具有联接。此外,联接通常是在另一个表的PK上完成的,默认情况下,该表已被索引(至少在Oracle上已建立索引)。如果您要询问手头的特殊情况(请参见上文),则执行计划很可能会说它是相同的,因为这是一张小桌子。
Robotron

@Human_AfterAll LIKE会比较慢,尤其是如果数据已正确建模为使用中的TINYINTPK字段channel_def。然后,只需要在两个表之间比较一个字节即可。在这里,它必须逐个字符地解析字符串(至少直到满足条件为止),并且它在进行不区分大小写的搜索(基于给定的表def未显示_BIN2正在使用的排序规则)。这也会使SQL Server上的索引无效。我在回答中说了解析不能使用索引的方法。我刚刚更新了我的答案,使其更加清晰。
所罗门·鲁兹基

1
@Human_AfterAll我要说的是,这种建模决策是由于缺乏经验和知识(有时是懒惰)而做出的。另一种JOIN可以保存所有内容,但是牺牲了的是外键功能,这将阻止完全伪造的数据进入(即使它不匹配该LIKE子句并产生奇怪的结果,它仍然可能导致其他问题或至少会使调试更加困难/更长)。这也使更新valid_channels字段变得更加复杂。这并不是说这行不通,只是没有充分的理由这样做。
所罗门·鲁兹基

“缺乏经验”-最糟糕的是,这个特殊的设计决定是由一名资深工作人员强加的……
Robotron

1

这是在SE上完成的。正如马克Gravell

...经过一番思考和考虑,我们确定了以自然线表示的管道(条形),并带有前导/尾随管道,因此“ .net c#”简单地变成了“ | .net | c#|”。这具有优点:

  • 解析非常简单
  • 批量更新和删除标签可以通过简单的替换(包括管道,以避免替换中间标签匹配项)来完成
  • ...

此“新格式”是与“旧格式”的下一步,后者有所不同,因此选择使用SQL Server全文搜索功能,因此,如果从头开始,则某些好处并不重要。

可能由于工作量和性能原因,他们可能没有完全标准化该事物。


0

好了,使用字符串和其他数据类型的一个可能的主要好处是,当可能需要纯粹的性能时,可以使用SQLCLR将它们从SQL Server发送到C#,C,C ++(等)。您甚至可以创建一个视图或存储过程来非关系地表示关系数据-就像您在上面的示例中所描述的那样。

请参阅以下示例:

http://aboutsqlserver.com/2013/07/22/clr-vs-t-sql-performance-considerations/

每个维基百科:SQL CLR或SQLCLR(SQL公共语言运行时)是一种用于在SQL Server中托管Microsoft .NET公共语言运行时引擎的技术。SQLCLR允许托管代码由Microsoft SQL Server环境托管并在其中运行。


2
嗨,您好。请您在这里提供更多详细信息。我不确定以非传统方式存储数据有何好处。如果有的话,SQLCLR的好处是能够更好地处理必须存在的替代数据格式。但这不是选择备用数据格式的原因。因此,我真的不认为这可以回答问题。
所罗门·鲁兹基

本文链接说明了优缺点的好处。另外,我提到了关系存储数据,并出于CLR的目的将其与视图或存储过程转换为非关系数据。您的问题是“是否有一种场景可以证明以行(字符串)而不是几行的形式存储数据?” 我的回答是肯定的,尽管我更喜欢使用视图或存储过程来与CLR进行交互。
2013年

0

我认为答案是否定的。我没有使用这种方法,并且会避免使用它-我想不出为什么要走那条路。您倾向于使用数组实现JSON / NoSQL的世界。

在上一个职位中,我们曾有过类似的设计选择,即架构师团队希望拥有一个“ Data”字段,该字段已定界然后转换为二进制。由于某些原因,我们最终没有沿那条路线走。

如果您必须加入此类数据,那将是一种丑陋的经历。更新字符串的单个元素也是不愉快的。

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.