从一个项目到另一个项目,尽管SQL已经存在了几十年,但不幸的是,我们仍面临缺乏统一的数据库设计标准的问题。 我怀疑原因部分是因为大多数开发人员都不了解数据库的体系结构。 在雇用开发人员的多年工作中,我只遇到过几次可以正确规范化数据库的人员。 老实说,这可能是一项艰巨的任务,但是我采访过的许多开发人员,即使他们精通SQL,也没有数据库设计技能。
本文与数据库规范化无关。 如果您想学习这一点,我
在这里简要地介绍了基本知识。
如果您有一个正常工作的数据库,则需要回答以下问题:“可以应用哪些标准来促进该数据库的
使用 ?”。 如果这些标准被广泛使用,那么您将很容易使用数据库,因为您不必在每次开始使用新数据库时都学习并记住新的标准集。
CamelCase是命名还是下划线?
我经常遇到数据库,其中的表以
CustomerOrders
或
customer_orders
的样式命名。 哪个更好用? 也许您想应用一个已经建立的标准,但是如果您要创建一个新的数据库,那么我建议使用下划线来增加可访问性。 短语“低于价值”与“低于价值”相比具有不同的含义,但是带有下划线的第一个将始终为
under_value
,第二个将始终为
under_value
。 当使用CamelCase时,我们得到
UnderValue
和
UnderValue
,它们在不区分大小写的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_id
,
star_id
或
star_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.owner
与
companies.company_id
具有外键约束。 因此从本质上讲,此SQL是错误的。 必须使用相同的名称:
SELECT * FROM some_table s JOIN some_other_table o ON o.company_id = s.person_id;
现在,我们可以很清楚地看到我们有一个错误,您只需要检查一行代码,而无需引用表定义。
但是,我要指出,这并非总是可以做到的。 如果您有一个包含源仓库和目的地的表,则可能要比较
source_id
和
destination_id
以及
source_id
。 在这种情况下,最好提供名称
source_warehouse_id
和
destination_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 NULL
和
IS NOT NULL
中的一个位置正确地实现了它们。 由于NULL值在定义上是未知的,因此无法为其设计运算符。 因此,有
IS NULL
和
IS 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
parts
很难找到一个更简单的例子。
此代码返回
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
NULL
(
p.city
),因此
WHERE
任何分支都不会导致
true
。
但是,我们知道未知的城市要么是巴黎,要么不是巴黎。 如果是巴黎,则第一个条件为true(
<> 'London'
)。 如果不是巴黎,则第二个条件为true(
<> 'Paris'
)。 因此,
WHERE
必须为
true
,但不是,因此,SQL生成逻辑上不可能的结果。
这是我在伦敦遇到的一个错误。 每次编写可生成或包含NULL值的SQL时,都有冒错误结果的风险。 这种情况很少发生,但是很难识别。
总结
- 使用
__
代替CamelCase
。
- 表名必须为复数形式。
- 给具有标识符的字段扩展名称(
item_id
而不是id
)。
- 避免使用不明确的列名。
- 如果可能,请使用与外键相同的名称为其命名。
- 尽可能将NOT NULL添加到所有列定义中。
- 尽可能避免编写可生成NULL值的SQL。
尽管并不完美,但该数据库设计指南将使您的生活更轻松。