查找表有多重要?

23

我写的很多应用程序都使用查找表,因为那是我学习(规范化等方面)的方式。问题在于,由于这个原因,我进行的查询通常更加复杂。它们通常看起来像这样:

获取所有仍然开放的帖子

"SELECT * FROM posts WHERE status_id = (SELECT id FROM statuses WHERE name = 'open')"

通常情况下,查找表本身非常简短。例如,可能只有3个或更少的不同状态。在这种情况下,通过在应用程序中使用常量或类似方式搜索某种类型是否可行?例如:

获取所有仍处于打开状态的帖子

"SELECT * FROM posts WHERE status_id = ".Status::OPEN

或者,如果我将其设置为枚举并通过它进行查询,而不是使用外键ID呢?

谢谢。


1
不,这只是一个UDPATE查询来重命名状态。如果您没有单独的查找表,则添加状态仅意味着首次在行中使用该状态,就像插入年龄为53岁的第一个人一样。 - Dan Grossman
2
我总是更喜欢使用查找表而不是常量,因为为什么要在每一行中重复一个varchar(20),当你可以使用1字节的tinyint id呢。 - dotjoe
1
@Dan。那个例子很好。但是这里的问题是真正的查找表,这是规范化;数据完整性;引用完整性,而不是一些奇怪的非查找表。如果数据表中重复出现“打开”数据值,那么它就不是规范化的,也不是数据库。 - PerformanceDBA
1
仅仅因为某些东西没有被规范到第n个程度,并不意味着它不是一个数据库。你作为一个数据库管理员,这有点可怕。 - Matthew Whited
1
@MatthewWhited 1) 没有“n次方”,只有规范化或不规范化。2)好的,你必须处理更新异常(重复数据),这是未规范化或“反规范化”(因为他们一开始没有规范化)的结果,并且需要不断调整和更改你的“数据库”。在规范化的数据库中,我们没有这些问题,我们只需转到下一个项目。 - PerformanceDBA
显示剩余5条评论
7个回答

34
回答如下:

答案有点取决于你是否仅限于使用免费软件,例如PostGreSQL(不完全符合SQL标准),或者你正在考虑使用SQL(即符合SQL标准)和大型数据库。

对于符合SQL标准的开放式架构数据库,其中有许多应用程序使用一个数据库,并且许多用户使用不同的报告工具(不仅仅是应用程序)来访问数据,标准、规范化和开放式架构要求非常重要。

尽管有些人试图改变“规范化”等定义以适应其不断变化的目的,但规范化(科学)并没有改变。

  • if you have data values such as {Open; Closed; etc} repeated in data tables, that is data duplication, a simple Normalisation error: if you those values change, you may have to update millions of rows, which is very limited design.

    • Such values should be Normalised into a Reference or Lookup table, with a short CHAR(2) PK:

      O  Open
      C  Closed
      U  [NotKnown]
      
    • The data values {Open;Closed;etc} are no longer duplicated in the millions of rows. It also saves space.

    • the second point is ease of change, if Closed were changed to Expired, again, one row needs to be changed, and that is reflected in the entire database; whereas in the un-normalised files, millions of rows need to be changed.

    • Adding new data values, eg. (H,HalfOpen) is then simply a matter of inserting one row.

  • in Open Architecture terms, the Lookup table is an ordinary table. It exists in the [SQL compliant] catalogue; as long as the FOREIGN KEY relation has been defined, the report tool can find that as well.

  • ENUM is a Non-SQL, do not use it. In SQL the "enum" is a Lookup table.

  • The next point relates to the meaningfulness of the key.

    • If the Key is meaningless to the user, fine, use an {INT;BIGINT;GUID;etc} or whatever is suitable; do not number them incrementally; allow "gaps".
    • But if the Key is meaningful to the user, do not use a meaningless number, use a meaningful Relational Key.
  • Now some people will get in to tangents regarding the permanence of PKs. That is a separate point. Yes, of course, always use a stable value for a PK (not "immutable", because no such thing exists, and a system-generated key does not provide row uniqueness).

    • {M,F} are unlikely to change

    • if you have used {0,1,2,4,6}, well don't change it, why would you want to. Those values were supposed to be meaningless, remember, only a meaningful Key need to be changed.

    • if you do use meaningful keys, use short alphabetic codes, that developers can readily understand (and infer the long description from). You will appreciate this only when you code SELECT and realise you do not have to JOIN every Lookup table. Power users too, appreciate it.

  • Since PKs are stable, particularly in Lookup tables, you can safely code:

    WHERE status_code = 'O' -- Open

    You do not have to JOIN the Lookup table and obtain the data value Open, as a developer, you are supposed to know what the Lookup PKs mean.

最后,如果数据库很大并且除了支持 OLTP 外还支持 BI、DSS 或 OLAP 功能(因为规范化的数据库可以这样做),那么查找表实际上是一个维度或向量,在“维度-事实”分析中。如果没有它,则必须添加它以满足该软件的要求,然后才能进行此类分析。
如果你从一开始就对数据库进行这样的处理,就不必在以后升级它(和代码)。
你的示例
SQL 是一种低级语言,因此它很繁琐,特别是当涉及到 JOIN 时。这就是我们所拥有的,所以我们需要接受这种负担并处理它。你的示例代码很好。但更简单的形式也可以完成同样的事情。
报告工具将生成:
SELECT p.*,
       s.name
    FROM posts  p, 
         status s
    WHERE p.status_id = s.status_id 
    AND   p.status_id = 'O'

另一个例子

对于银行系统,我们使用有意义的短代码(因为它们是有意义的,所以我们不会随季节更改它们,我们只会添加它们),给定一个查找表,如下(精心选择,类似于ISO国家代码):

Eq   股票
EqCS 普通股
OTC  场外交易
OF   场外期货

这样的代码很常见:

WHERE InstrumentTypeCode LIKE "Eq%"

GUI的用户将从下拉列表中选择值,其中显示为
{普通股;场外交易},
而不是 {Eq;OTC;OF}, 也不是 {M;F;U}。
如果没有查找表,则无法在应用程序或报告工具中执行此操作。


1
这正是我想的,因为那是我学习的方式。我只是想看看是否有更简单的方法来处理这些查询。谢谢。 - BDuelz
2
谢谢。至少我们曾经有科学老师。如今,许多从事技术工作的人从未接受过教育;他们边做边学。他们没有更高的权威,对于任何与他们实现的不同的东西都感到害怕,并且会质疑科学。你必须小心询问问题的人。 - PerformanceDBA
1
@CatCall。好的,规范化是一个庞大的主题。这些评论格式有限,我不知道从哪里开始给你讲解。能否您开一个新问题,这样我就可以完整地回答您的问题了。 - PerformanceDBA
2
@PerformanceDBA - 或许你有这样热情的追随者,是因为我们中的一些人,缺乏你所倡导的计算机科学教育的好处,正在尽力获取我们能够得到的!对我来说,回到学校不是一个选择,这本质上是我的一项爱好。我总是在寻求像你这样的人提供的反馈。现在很容易获得错误的信息。虽然我很高兴编程工具变得更加易于使用,但我也很高兴有那些愿意让我们占用你的研究生学位的人存在!谢谢…… - XIVSolutions
1
@XIV。很高兴为您服务,我的解释是为像您这样的寻求者而准备的。误导者很常见,但在这里他们有“声望”并攻击真相。尽管FAQ鼓励我们纠正错误信息,但情况仍然如此。 - PerformanceDBA
显示剩余4条评论

1

对于查找表,我使用一个明智的主键 - 通常只是一个CHAR(1),在域中具有附加的Title(VARCHAR)字段。这可以保持关系强制执行,同时“保持SQL简单”。记住这里的关键是查找表不包含数据。它包含身份标识。其他一些身份标识可能是时区名称或指定的{{link1:IOC国家代码}}。

例如性别:

ID 标签
M  男性
F  女性
N  中性
select * from people where gender = 'M'

或者,可以使用ORM,而且可能永远不必进行手动SQL生成--在这种情况下,标准的“int”代理键方法很好,因为其他东西会处理它:-)

愉快的编码。


在这种情况下,这是有意义的。但如果情况不那么简单呢?例如,假设您有一个包含30个不同类别的查找表。您能想到哪些合理的主键,以避免未来的混淆和冲突? - BDuelz
在你添加ORM部分之前,我写了最后一条评论。所以我想你的意思是应用程序中没有必要使用额外的常量? - BDuelz
1
@DBuelz 我又更新了帖子,提到了“数据 vs 标识”。此外,键不必仅限于 CHAR(1),但如果它增长超过 CHAR(3) 或值非常牵强,则我想它是否真的是一个“查找”表,而不是更适合作为另一个关系数据表。应用程序中的常量将存在于某个级别,并且同步始终是要求。附加列具有“添加隐式文档”的附加优点。但是,仅使用此方法(单独)进行国际化通常会失败。 - user166390
@DBuelz 考虑一个年份查找表,其中自然主键可能是年份本身。年份仍然是代表自己的“标识”。 - user166390
我想我明白你的意思。我想我有点困惑于查找表的确切含义。 - BDuelz

1

为每个查找创建一个函数。 没有简单的方法。您想要性能和查询简单性。确保保持以下内容。您可以创建一个 SP_TestAppEnums 将现有查找值与函数进行比较,并查找未同步/返回零。

CREATE FUNCTION [Enum_Post](@postname varchar(10))
RETURNS int
AS
BEGIN
DECLARE @postId int
SET @postId =
CASE @postname
WHEN 'Open' THEN 1
WHEN 'Closed' THEN 2
END
RETURN @postId END GO
/* 调用函数 */ SELECT dbo.Enum_Post('Open') SELECT dbo.Enum_Post('Closed')

1
问题是:您是否需要在查询中包含查找表(我所在地区的域表)?假设这些类型的表通常是
  • 性质相当静态-域可能会扩展,但可能不会缩短。
  • 它们的主键值也很难改变(例如,“打开”状态的状态ID不太可能突然更改为创建时的其他内容)。

如果上述假设是正确的,则没有必要将所有这些额外的表添加到连接中,以便您的where子句可以使用朋友名称而不是id值。只需在需要时直接过滤status_id即可。我怀疑where子句中的非关键属性(如上面示例中的“名称”)更有可能发生更改,而不是关键属性(如上面示例中的“名称”):通过引用加入中域表的所需关键值,您更受保护。

域表用于

  • 通过外键关系限制变量的域,
  • 允许通过向域表添加数据来扩展域,
  • 使用用户友好的信息填充UI控件等。

自然地,您需要将域表引入到查询中,以便在实际需要域表的非键属性(例如值的描述性名称)时使用。

YMMV:很大程度上取决于上下文和问题空间的性质。


1

答案是“任何有意义的东西”。

查找表涉及连接或子查询,这并不总是高效的。我经常使用枚举来完成这项工作。它是高效和快速的。


虽然简洁,但这实际上是一个很好的答案。对于问题“查找表有多重要?”,唯一正确的答案是那些有效地等同于“取决于情况”的答案。 - Derek Greer

0
评论者们让我意识到了我错误的观点。然而,这个回答以及相关的讨论仍然保留在这里供参考。
我认为在这里应该使用常量,而不是数据库表。当你设计应用程序时,你希望状态表永远不会改变,因为你的应用程序已经将这些状态的含义硬编码进去。数据库的目的是存储可能会改变的数据。虽然有些情况可能模糊(例如,“这些数据可能每隔几个月就会更改…”),但这并不属于模糊的情况之一。
状态是应用程序逻辑的一部分;使用常量在应用程序内定义它们。这样不仅组织更严谨,而且还能显著提高与数据库的交互速度。

如果我两者都做了呢?如果我有额外的表格,只是为了记录的目的呢? - BDuelz
1
-1 查找表一组常量(代理或自然PK除外)。区别在于它们被编码到数据库中,因此可以保持完整性(使用正确的DDL)。数据库和代码库需要协同工作。使用常量与查找表不是正交的,它们是相互强化的。 - user166390
1
没有什么是百分之百静态的,甚至包括主键。如果您使用引用完整性并创建一个表来存储查找值,那么系统更好地设置为适应变化(如果发生)。如果它们从未发生,那也没关系——修改数据非常麻烦。 - OMG Ponies
@pst 这正是我所想的,因此我从未使用常量。 - BDuelz
@性能:明白了。我的一般设计模式考虑到应用程序逻辑和数据库有不同的角色,但我在这里看到了一些重新考虑这种做法的好理由。我会把这个答案留在这里作为错误示例 :) - Matchu
显示剩余3条评论

0
在可能的情况下(并不总是……),我使用这个经验法则:如果我需要将一个值硬编码到我的应用程序中(而不是让它保留在数据库中的记录),并且还将该值存储在我的数据库中,那么我的设计就有问题了。虽然并不总是如此,但无论所涉及的值是什么,它都代表了一部分数据或程序逻辑。很少有例外是两者兼备的。
并不是说你不会在项目进行到一半时发现它属于哪一类。但正如其他人所说,无论哪种方法都会存在折衷。就像我们并不总是能够在数据库设计中实现“完美”的规范化(出于性能原因,或者仅仅是因为在追求学术完美时也可以走得太远……),我们可能会对我们的“查找”值放置的位置做一些有意识的选择。
个人而言,我尽量遵循上述规则。它要么是数据,要么是程序逻辑,很少同时兼备。如果它最终成为(或在)数据库记录中,我尽量将其排除在应用程序代码之外(当然,除了从数据库中检索它之外……)。如果它在我的应用程序中被硬编码,我会尽量将其排除在我的数据库之外。
在我无法遵守这个规则的情况下,我会用我的理由记录代码,这样三年后,如果发生了故障,一些可怜的灵魂就能够弄清楚它是如何破坏的。

最近我写了一个小的新闻通讯应用程序,每个通讯都有“已取消”,“错误”,“已发送”等状态。在模型中,我为每个状态设置了常量以帮助逻辑处理。例如:if($something_went_wrong) $newsletter->status = Newsletter::ERROR; $newsletter->save()...正如您所见,我使用了ORM。 - BDuelz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接