数据库设计标准


从一个项目到另一个项目,尽管SQL已经存在了几十年,但不幸的是,我们仍面临缺乏统一的数据库设计标准的问题。 我怀疑原因部分是因为大多数开发人员都不了解数据库的体系结构。 在雇用开发人员的多年工作中,我只遇到过几次可以正确规范化数据库的人员。 老实说,这可能是一项艰巨的任务,但是我采访过的许多开发人员,即使他们精通SQL,也没有数据库设计技能。

本文与数据库规范化无关。 如果您想学习这一点,我在这里简要地介绍了基本知识。

如果您有一个正常工作的数据库,则需要回答以下问题:“可以应用哪些标准来促进该数据库的使用 ?”。 如果这些标准被广泛使用,那么您将很容易使用数据库,因为您不必在每次开始使用新数据库时都学习并记住新的标准集。

CamelCase是命名还是下划线?


我经常遇到数据库,其中的表以CustomerOrderscustomer_orders的样式命名。 哪个更好用? 也许您想应用一个已经建立的标准,但是如果您要创建一个新的数据库,那么我建议使用下划线来增加可访问性。 短语“低于价值”与“低于价值”相比具有不同的含义,但是带有下划线的第一个将始终为under_value ,第二个将始终为under_value 。 当使用CamelCase时,我们得到UnderValueUnderValue ,它们在不区分大小写的SQL方面是相同的。 此外,如果您有视力问题,并不断尝试使用头戴式耳机和大头针来强调单词,那么下划线就更容易阅读了。

最后,对于那些英语不是母语的人,很难阅读CamelCase。
总而言之,这不是严格的建议,而是个人喜好。

表名是复数还是单数?


数据库理论专家已经争论了很长时间,表应该是单数(客户)还是复数(客户)。 让我不加理论地简单地切入这个Gordian难题,仅借助实用主义即可:复数表名与保留关键字的冲突可能性较小。

您有用户- users吗? SQL具有user关键字。 您需要约束表吗? constraint是保留字。 audit一词
保留,但是您需要audit表吗? 只需使用名词的复数形式,然后大多数保留字就不会在SQL中打扰您。 甚至具有出色SQL解析器的PostgreSQL,也偶然发现了user表。

仅使用复数,冲突的可能性就会大大降低。

请勿将ID为“ id”的列命名


这些年来,我本人已经犯了罪。 当我在巴黎与一位客户合作时,当我将id列命名为id时,DBA投诉了我。 我以为他只是个学徒。 实际上,列名customers.id是唯一的,而customers.customer_id是信息的重复。

后来我不得不调试它:

 SELECT thread.* FROM email thread JOIN email selected ON selected.id = thread.id JOIN character recipient ON recipient.id = thread.recipient_id JOIN station_area sa ON sa.id = recipient.id JOIN station st ON st.id = sa.id JOIN star origin ON origin.id = thread.id JOIN star destination ON destination.id = st.id LEFT JOIN route ON ( route.from_id = origin.id AND route.to_id = destination.id ) WHERE selected.id = ? AND ( thread.sender_id = ? OR ( thread.recipient_id = ? AND ( origin.id = destination.id OR ( route.distance IS NOT NULL AND now() >= thread.datesent + ( route.distance * interval '30 seconds' ) )))) ORDER BY datesent ASC, thread.parent_id ASC 

注意到问题了吗? 如果SQL使用完整的id名称,例如email_idstar_idstar_id ,则这些错误将在我编写此代码时立即消失 ,而不会在以后尝试理解错误之处时立即消失

帮个忙,并使用全名作为ID。 待会儿谢谢

列名


为列提供尽可能多的描述性名称。 假设temperature列与此无关:

 SELECT name, 'too cold' FROM areas WHERE temperature < 32; 

我住在法国,对我们来说32度的温度将“太冷”。 因此,最好将fahrenheit列命名为。

 SELECT name, 'too cold' FROM areas WHERE fahrenheit < 32; 

现在一切都完全清楚了。

如果您有外键限制,请尽可能在限制两侧的列中使用相同的名称。 这是一个经过深思熟虑的合理SQL:

 SELECT * FROM some_table s JOIN some_other_table o ON o.owner = s.person_id; 

这段代码确实可以。 但是,当您查看表定义时,您会看到some_other_table.ownercompanies.company_id具有外键约束。 因此从本质上讲,此SQL是错误的。 必须使用相同的名称:

 SELECT * FROM some_table s JOIN some_other_table o ON o.company_id = s.person_id; 

现在,我们可以很清楚地看到我们有一个错误,您只需要检查一行代码,而无需引用表定义。

但是,我要指出,这并非总是可以做到的。 如果您有一个包含源仓库和目的地的表,则可能要比较source_iddestination_id以及source_id 。 在这种情况下,最好提供名称source_warehouse_iddestination_warehouse_id

还要注意,在上面的示例中, owner将比company_id更好地描述目的。 如果这让您感到困惑,则可以命名owning_company_id列。 然后,名称将告诉您该列的用途。

避免使用NULL值


该建议已为许多经验丰富的数据库开发人员所熟知,但是不幸的是,他们讨论的次数不够多:没有充分的理由,不允许在数据库中使用NULL值。
这是一个重要但相当复杂的话题。 首先,我们讨论该理论,然后讨论其对数据库体系结构的影响,最后,我们将分析一个由于存在NULL值而引起的严重问题的实际示例。

数据库类型


数据库可以包含不同类型的数据:INTEGER,JSON,DATETIME等。该类型与列相关联,并且添加到该列的任何值都必须与此类型相对应。

但是什么是类型? 这是一个名称,一组有效值和一组有效操作。 它们帮助我们避免了不良行为。 例如,如果您尝试比较字符串和数字,在Java中会发生什么?

 CustomerAccount.java:5: error: bad operand types for binary operator '>' if ( current > threshold ) { ^ first type: String second type: int 

即使您没有注意到current > threshold比较的类型也无法比拟,编译器仍会为您抓住这一点。

具有讽刺意味的是,存储您的数据的数据库(也是您防范数据损坏的最后一道防线)与类型紧密合作! 真恶心。 例如,如果您的customers表具有代理键,则可以执行以下操作:

 SELECT name, birthdate FROM customers WHERE customer_id > weight; 

当然,这没有任何意义,实际上,您会遇到编译错误。 许多编程语言使捕获此类类型错误更加容易,但是对于数据库,情况恰恰相反。

这是数据库世界中的正常情况,可能是因为第一个SQL标准于1992年发布 。 在那些年中,计算机运行缓慢,毫无疑问,使实现复杂化的所有因素都降低了数据库的速度。

然后NULL值出现在场景中。 SQL标准仅在IS NULLIS NOT NULL中的一个位置正确地实现了它们。 由于NULL值在定义上是未知的,因此无法为其设计运算符。 因此,有IS NULLIS NOT NULL而不是= NULL!= NULL 。 NULL值的任何比较都会导致出现新的NULL值。

如果您觉得这很奇怪,那么用“ unknown”(而不是NULL)代替NULL会容易得多:

比较NULL未知值会导致NULL未知值。

是的,现在我明白了!

空值是什么意思?


武装理论的碎片,我们考虑它的实际后果。

您需要向所有当年薪水超过5万美元的员工支付$ 500的奖金。您可以编写以下代码:

 SELECT employee_number, name FROM employees WHERE salary > 50000; 

您刚刚被解雇了,因为您的老板收入超过5万美元,但是他的薪水不在数据库中(在employees.salary列中为NULL),并且比较运算符无法将NULL与5万进行比较。

为什么此列中为NULL? 也许工资是保密的。 信息可能尚未到达。 也许这是一名顾问,却没有得到报酬。 也许他只有小时工资,而不是工资。 有很多原因可能导致数据丢失。

列中是否存在信息表明该信息取决于其他情况 ,而不取决于主键和数据库的非规范化。 因此,其中可能存在NULL值的列是创建新表的良好候选者。 在这种情况下,您可能有表,时___ 等。您仍然因盲目组合薪水而老板没有薪水而被解雇。 但是随后您的基地开始向您提供足够的信息,以表明该问题不仅仅是薪水问题。

是的,这是一个愚蠢的例子,但这是最后一根稻草。

NULL值导致逻辑上不可能的情况


在您看来,我对NULL值很感兴趣。 但是,让我们看另一个更接近现实的例子。

几年前,我在伦敦的一个域名注册商工作,试图理解为什么80行SQL查询返回错误的数据。 在那种情况下,肯定应该返回信息,但是这没有发生。 我很ham愧地承认,但是花了我一天的时间才知道原因是这样的综合条件:

  • 我用了OUTER JOIN。
  • 他们可以轻松生成NULL值。
  • NULL值可能导致SQL给出错误的答案。

许多开发人员都不了解后者。因此,让我们看一看《 数据库中的深度 》一书中的示例。 两个表的简单图:

suppliers
Supplier_id
城市
s1
伦敦的

parts

part_id
城市
1
空值

很难找到一个更简单的例子。

此代码返回p1

 SELECT part_id FROM parts; 

该代码将做什么?

 SELECT part_id FROM parts WHERE city = city; 

它不会返回任何内容,因为即使是另一个NULL或相同的NULL,也无法比较NULL值。 看起来很奇怪,因为即使我们不知道,每条线上的城市也应该相同,对吗? 那么什么将返回以下代码? 在进一步阅读之前,请尝试理解此内容。

 SELECT s.supplier_id, p.part_id FROM suppliers s, parts p WHERE p.city <> s.city OR p.city <> 'Paris'; 

我们没有得到一个响应字符串,因为我们无法比较city NULLp.city ),因此WHERE任何分支都不会导致true

但是,我们知道未知的城市要么是巴黎,要么不是巴黎。 如果是巴黎,则第一个条件为true( <> 'London' )。 如果不是巴黎,则第二个条件为true( <> 'Paris' )。 因此, WHERE必须为true ,但不是,因此,SQL生成逻辑上不可能的结果。

这是我在伦敦遇到的一个错误。 每次编写可生成或包含NULL值的SQL时,都有冒错误结果的风险。 这种情况很少发生,但是很难识别。

总结


  • 使用__代替CamelCase
  • 表名必须为复数形式。
  • 给具有标识符的字段扩展名称( item_id而不是id )。
  • 避免使用不明确的列名。
  • 如果可能,请使用与外键相同的名称为其命名。
  • 尽可能将NOT NULL添加到所有列定义中。
  • 尽可能避免编写可生成NULL值的SQL。

尽管并不完美,但该数据库设计指南将使您的生活更轻松。

Source: https://habr.com/ru/post/zh-CN484188/


All Articles