【问题标题】:Is it ever a good idea to have a record in a reference table in your database that represent "all other records"?在您的数据库中的参考表中拥有一个代表“所有其他记录”的记录是一个好主意吗?
【发布时间】:2013-05-28 23:47:19
【问题描述】:

我有一个带有 SQL Server 后端的 asp.net-mvc 网站。我正在简化我的情况以突出和隔离问题。我在数据库中有 3 个表

  1. 文章表(ID、名称、内容)
  2. 位置表(id、名称)
  3. ArticleLocation 表(id​​、文章 ID、位置 ID)

在我的网站上,当您创建一篇文章时,您可以从多选列表框中选择您希望将该文章发送到的位置。

大约有 25 个位置,因此我正在讨论添加一个名为“全局”的新位置作为快捷方式,而不是让人们从列表框中选择 25 个不同的项目。我仍然可以将其作为前端的快捷方式,但现在我正在争论是否有利于将其流向后端。

因此,如果我有一篇全球性的文章,而不是 ArticleLocation 表中的 25 条记录,我将只有一条,然后我会在前端做一些技巧来选择所有项目。我想弄清楚这是否是一个非常糟糕的主意。

我能想到的让我紧张的事情:

  1. 如果我创建了一篇文章并选择了全球,但后来最后添加了 3 个新位置,该怎么办。如果没有这个全局设置,这 3 个位置将无法获取文章,但以新的方式,它们会。我不确定什么更好,因为第二件事实际上可能是您想要的,但它不太明确。

  2. 我对报告有要求,我想按所有全局文章进行过滤。想象一下,我需要一个 article.IsGlobal() 方法。现在我想我可以说如果一个项目的位置数与位置表中的所有记录相同,我可以将其翻译为被认为是全球性的,但由于人们可以添加新位置,我觉得这种方法有点不稳定.

对于在真正反映“所有记录”的参考数据表中创建记录这一困境,是否有人有任何建议。感谢任何建议

【问题讨论】:

  • 无论您选择哪种方式,您都将定义一个您可能不想要的要求:新位置是否会自动获取在该位置尚不存在时定义的文章。因此,如果您要使用全局标志,您似乎还应该添加一个关于它是否继承新位置的属性(或使其成为该位置的属性)。
  • Article 表中添加AllLocations 标志可能更清楚。标记设置为1 的任何文章都会出现​​在所有位置,无论它们是何时创建的,并且不需要在ArticleLocation 表中包含任何条目。如果作者不希望文章自动出现在未来的位置,则仍然可以将文章显式添加到所有现有位置。您可以隐藏视图背后的一些乐趣,并且可以添加一个触发器来强制执行一条规则,即一篇文章适用于任何地方或特定位置,但不能同时适用于两者。
  • 保持简单,要么使用@HABO 的建议,要么按照您的第一直觉在位置表中添加“全球”记录。如果您出于各种原因开始创建快捷方式,我只会使用更高级的解决方案,即“欧洲”、“美洲”......等等。
  • 我想知道为什么最复杂的答案得到了最多的支持 :) 似乎 SO 人们更喜欢复杂的技术重复而不是非常简单有效的解决方案 :-)
  • 如果表格相对简单,我将允许在 ArticleLocation 中的 locationId 为空,而空条目将意味着它不依赖于位置,因此不依赖于全局。对我来说似乎也合乎逻辑。

标签: sql-server asp.net-mvc database database-design database-schema


【解决方案1】:

应要求,这是我的评论提升为答案。这也是一个扩展它的机会。

我将把我的答案限制在具有单个位置列表的系统上。我已经完成了公司层次结构的事情:公司、部门、地区、州、县、办公室和员工等等。变丑了。

对于 OP 的问题,似乎在 Articles 表中添加一个 AllLocations 位可以明确意图。标记设置为 1 的任何文章都会出现​​在所有位置,无论它们是何时创建的,并且不需要在 ArticleLocation 表中包含任何条目。如果作者不希望文章自动出现在未来的位置,则仍可以将文章显式添加到所有现有位置。

实施涉及更多工作。我会在ArticleArticleLocation 表中添加INSERTUPDATE 触发器,以强制设置AllLocations 位并且ArticleLocation 中没有相应的行,或者该位是清除的规则和位置可以明确设置。 (个人偏好让数据库在可行的情况下保护自己免受“坏数据”的影响。)

根据您的需要,表值函数是隐藏一些脏工作的好方法,例如dbo.GetArticleIdsForLocation( LocationId ) 可以在内部处理 AllLocations 标志。您可以在存储过程和对JOINArticle 的即席查询中使用它。在其他情况下,视图可能是合适的。

欢迎您借用的另一个功能(“从您的朋友那里窃取!”)是让管理员的登录页面成为“例外”页面。在这个地方,我展示了从大规模的燃烧灾难到单纯的小错误。在这种情况下,与零个位置相关联的文章将被视为非关键内容,但值得检查。

明确显示在每个位置的文章可能会引起添加新位置的人的兴趣,因此我可能会为此创建一个网页。可能应该更新某些文章以明确说明新位置或重新考虑将其更改为所有位置。

【讨论】:

  • 从纯数据库的角度来看,这是一个非常干净的解决方案。但是,考虑对整个堆栈的副作用:UI 现在需要一个名为“Global”的硬编码下拉条目,并且服务器端代码必须生成两个略有不同的 sql 查询,具体取决于用户选择“Global”还是任何其他条目。我根本不想在这个答案上大便,只是想补充一下全部考虑因素。
  • @DanLing - 我倾向于在 UI 中添加一个复选框,以允许用户明确指定文章适用于现在和未来的所有位置。选中后,它应该禁用其他选项,无论是多选列表还是每个位置的复选框数组。是的,它更复杂,但它应该让用户非常清楚他们正在做不同的事情。根据位置的数量和文章的典型分布,我可能还有按钮来检查全部或没有。 POLS.
  • 糟糕,出于某种原因,我认为 OP 说在下拉菜单中包含“全局”是一项要求。
  • @DanLing - 至于数据库方面,这就是生活。无论是全局标志还是魔法位置,数据库都需要对正在发生的事情有所了解。通常,我更喜欢减少魔法内容的解决方案。通过使用存储过程,所有细节都可以在明确定义的接口上进行测试。在美好的一天,可以更改实现,同时保持存储过程接口不变。
  • @MosheBixenshpaner - 读写的平衡是什么?每篇文章的平均位置?文章数?全球文章数量?查询率?查询复杂度?可接受的延迟?可用于缓存的内存? ...您可能正朝着过早的优化前进。 使用任何人都可以维护的典型数据库操作进行基准测试。 如果存在问题,请对其进行量化、重新设计和重新测量。如果您有不同的要求,例如所有开源软件或者它必须在 Arduino 上运行,那么您正在解决具有不同约束的不同问题。
【解决方案2】:

代表“所有其他记录”是个好主意吗?

在表格中表示一棵是个好主意吗?树的表示“所有其他记录”。

树和层次结构并不容易使用,但是有很多例子、文章和书籍可以解决这个问题——比如 Celko 的 SQL 中的树和层次结构; Karwin 的 SQL 反模式

所以你在这里实际上拥有的是一个层次结构(也许只是一棵树)——它可能有助于从一开始就以这种方式解决问题。您示例中的Global 只是另一个Location(树的根),因此当添加新位置时,您可以决定它是否是Global 的子节点。


事实

  • 位置(LocationID)存在。
  • 位置(LocationID)包含在父位置(LocationID)中。
  • 文章(ArticleID) 存在。
  • 文章(ArticleID) 可在Location(LocationID) 获得。

约束

  • Each位置包含在at most one父位置中。 It is possible that for some父位置more than one位置包含在that父位置中。

  • It is possible that some 文章 可在more than one 位置 and that for some位置more than one文章可在that位置获得。


逻辑


通过这种方式,您可以为文章分配任何位置 - 但必须在需要时将其解析到叶级别

层次结构(树)在这里以“幼稚的方式”表示;改用闭包表、嵌套集或枚举路径——或者,如果你喜欢递归......

【讨论】:

  • 一篇文章与一个位置和该位置的父级相关联是什么意思?
  • @PhilipKelley -- 这就是必须将其解析到叶级别的意思。必须处理可能的冗余,意味着有一个select distinct ArticleID, LocationID ...
  • 父位置并不真正适合,因为其他所有记录都将具有相同的父位置。 .我完全愿意将树存储在 db 表中,但这并不是真正的树。 .
【解决方案3】:

tl;博士

在这种情况下,据我了解,我认为在 Location 表中创建一个“全局”位置是个好主意。我绝对发现在 Article 表中创建一个“全局”标志更可取。


“这是个好主意吗……?”不是我们喜欢在 SO 上回答的问题。这主要是一个辩论问题,而不是问答问题,此外,我们在社区中有足够的创造力提出一些示例,无论如何“它”都是一个好主意。

对于您更具体的问题,如何在数据库中表示“所有位置”?这是基于您的业务需求的判断。

您希望“所有位置”包括未来的位置吗?

如果不是,那么您可能应该只实现“所有位置”作为选择数据库中所有当前位置的助手。

您是否预计会有位置层次结构?

现实世界中的位置具有显着的层次结构:

  • 全球
  • 跨国(大陆、贸易区)
  • 国家
  • 行政区域(州、省、州等)
  • 城市
  • 邻里

如果您认为自己想要选择一个国家,而不是全球,那么实施像 Damir 建议的分层表示是最好的方法。但是,如果您不确定除了 Global 之外是否还会有任何其他位置分组,那么现在分层数据结构的工作量太大。您需要做的就是确保您当前的实现具有到可能的未来分层表示的迁移路径。

全局作为伪位置

如果您确实希望将来的位置包含在 Global 中并且需要分层位置结构,那么基于多年经验,我的直觉是创建“Global”作为一个伪位置。也就是说,Global 将是 Location 表中的位置之一,但它具有特殊含义。这绝对是一个折衷方案,但具有更改数据结构以支持 Global 的好处,这意味着“Global”创建的所有特殊情况都是通过在查询中排除或包含某些位置来处理的而不是通过在某处检查一些标志。 (或者如果你喜欢标志,你可以在 Location 表中添加一个“伪位置”标志。)

使用 Global 作为位置,会自动处理对 Location 表的添加或删除。对所有全球文章的查询很简单:与对任何其他位置的所有文章的查询相同。按位置报告文章也很简单,全球文章就像任何其他位置一样出现在报告中。您还可以表示“全球”文章(所有当前和未来位置)和“所有位置”文章(所有当前位置,但没有 个未来位置)之间的区别。

选择应该在特定位置可见的所有文章稍微困难一些,现在它是对“全局”以及该位置的检查,但至少它是检查同一个表中的 2 个值而不是检查两个不同的表.

SELECT article_id FROM ArticleLocation WHERE location_id in (1, 5);

SELECT article_id FROM ArticleLocation WHERE location_id = 5
UNION
SELECT id FROM Article WHERE is_global;

【讨论】:

    【解决方案4】:

    从逻辑上看,正如您所描述的那样,GLOBAL 实际上应该是全球性的并保持全球性,即使您添加了新位置(问题 1 已解决)。但这也意味着 GLOBAL 与“所有位置”不同(因为可能还存在一些我们尚未定义的其他位置)。我认为您的要求 2 尤其需要这种逻辑 - 否则添加新位置将完全失败。

    分析完成!从上面我们看到 GLOBAL 比所有这些位置都重要。尝试将其定义为位置是没有意义的。寻求最简单的解决方案!

    • 文章表(id、名称、内容、全局

    即布尔标志 - 文章是否是全局的。在用户界面中,只需将其作为一个复选框进行 - 如果选中,多选框将被禁用。简单,容易,满足要求。完成!

    【讨论】:

      【解决方案5】:

      添加新位置时是否需要自动将一些文章添加到新位置?如果是,那么在这种情况下,我会考虑在后端添加新的“全局”属性。

      否则可能不值得付出努力。即使您为每篇文章选择了 10000 篇文章和 20 个不同的位置,这将是大约 20 万条记录,这在您设置索引时还不错。

      检查您现有的数据,看看人们是如何选择地点的。如果大多数用户只选择了几个位置而不是全部,那么这确实是一个边缘情况,除非它真的会产生问题,否则您不应该对其进行处理。

      【讨论】:

        【解决方案6】:

        我同意@HABO 的评论(他应该已经发布了——如果他发布了,请给他点赞)。将属性添加到表格文章以识别与所有位置相关联的那些项目,现在未来,大概在文章的生命周期内,从长远来看应该可以节省您的时间和精力。当然,触发器和计数都可以解决问题,但是它们很尴尬,并且如果/当后续系统更改发生时支持它们会很痛苦。 UI 使用起来会更简单,因为用户只需单击一个复选框(或其他任何内容),而不是多次单击无法预见长度的下拉列表中的所有内容。

        (@Damir 的层次结构理念也可以,但是——从经验丰富的角度来说——它们很麻烦,我不会在这里介绍一个,除非有更多的系统和/或商业用途来摆脱它。)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-01-07
          • 2010-09-21
          • 1970-01-01
          • 2017-07-12
          • 1970-01-01
          相关资源
          最近更新 更多