写在前面
对于设计和创建数据库完全是个新手?没关系,Joe Celko, 世界上读者数量最多的SQL作者之一,会告诉你这些基础。和往常一样,即使是最专业的数据库老手,也会给他们带来惊喜。Joe是DMBS杂志是多年来最受读者喜爱的作者。他在美国、英国,北欧,南美及非洲传授SQL知识。他在ANSI / ISO SQL标准委员会工作了10年,为SQL-89和SQL-92标准做出了杰出贡献。
在你开始考虑你的数据库架构或表前,你需要细想下你的数据:数据是什么类型,你使用值的范围。它应该是唯一的,精确的且不含糊的。然后你的命名应该通俗易懂的。Joe Celko这样解释道。
不要太惊讶,在数据库设计里第一步是数据。但事实上,太多的程序员不会花时间在它们的数据设计上,而是首先设计架构。这样行不通。SQL处理的是结构化数据,而不是想文本或图片的非结构化数据。在关系型数据库管理系统(RDBMS)里一个基本概念是Codd博士所谓的信息原则(Information Principle)。这个规则写道:在关系型数据库管理系统里,所有的数据被建模为表里行里列里的标量。
这就是说表里的所有行有同样的结构。也就是说正确的表设计不会出现有一行是汽车的模型,另一行是鱿鱼的模型,还有一行表示Lady Gaga。
同样,这就是说行里的每列是同样数据元的值。在表里,一个列不会从鞋子大小变成其它温度的值。也就是说范围不会改变——如果你以摄氏度测量温度,那么这列的所有值都使用摄氏度的范围。
选择数据的范围和类型很重要。它让在一些范围内做数学和比较有意义,而不是与其它。这也解释了为什么SQL 是强类型语言(strongly typed language)。一些语言是弱类型的——那就是,变量在程序执行期间可以修改数据类型——一些则是强类型的;那些就是变量保持同样的数据类型,只有你在强制转化的时候才会是新的数据类型。并不是所有的强制转化都合法。弱类型转化问题的经典例子是PL/I里,如果你分配单个数组元为负数的平方根,它会把整个数组从FLOAT转为COMPLEX类型。这个发生的时候没有任何警告信息。关于弱数据类型有个老笑话:
教师:“比利,6乘以9多少?”
比利:“额~~,红色?”
教师:“不是!萨利,6乘以9多少?”
萨利:“星期四?”
教师:“不是!汤米,6乘以9多少?”
汤米:“54”
教师:“回答正确!现在告诉大家你怎么得到答案的。”
汤米:“我用红色除以星期四!”
人们在写弱类型语言时,经常会在数据元名称加上前缀或词尾,告诉大家原始数据类型,防止在程序中大家修改类型。这违反了ISO-11179元数据规则。这个标准总结了命名数据元是它本身应该是什么,而不是它在存储中的位置,或者在表里如何使用。
对于数据元,ISO_11179格式是:
“[<角色>_]<属性名>_<特征名>]"
(“[<role>_]<attribute>_<property>”)
在整个架构里,一个数据元有一个且只有一个名称。如果在整个企业里有且只有一个名称更佳。最好的话,在宇宙里有且只有一个名称。我来告诉你另一个老笑话:
“我是小孩时,我们有三只猫。”
“它们的名字是什么?”
“猫,猫和猫。”
“听起来分不清;你怎么把它们区分开?”
“谁在乎呢?当你叫它们时,它们也不会过来!”
我们希望数据在叫的时候就知道它是什么。逻辑上称同一律(the Law of Identity),通常简单的从字面上说A就是A。更正式的话,我们称作:
“是”就特指某物;实体应有名称。
“不是”任何特定之物,就彻底不是任何事物;实体应是唯一且精确的。
“是”任何一般之物,就彻底不是任何(其它)事物;实体应无二义性。
例如,没有一个东西可以是“id”——它太模糊,太通用(它是用来标识汽车,鱿鱼还是布兰妮·斯皮尔斯么?)。它是没有性质的属性。更好的命名会是”vehicle_id",如果那是我们有的属性性质(attribute property)。最好的数据元名称是“vin”,这个是普遍使用和定义的ISO 4030“车辆识别码(Vehicle Identification Number)”标准。VIN精确且很好理解。
同样,你也会看到没有性质的属性。我个人最爱的是“sex”,它可以是“sex_code”(按ISO 5218定义,可以参考它的标志符“SEX”,虽然这不是个好主意)。"sex_type"(动物或植物的生理选项:雄性还是雌性)或者“sex_frequency”(在我这一厢情愿的想法)。
可能最荒谬的错误是性质链。想下“type_code_id”什么意思。如果它是个标识,俺么在数据模型里它是唯一的并属于一个实体(想下"emp_id")。如果它是个代码,那么它有个外部的权威(想下“邮编”)。如果它是个类型,那对它有个测试(想下“血型”)。这就像没有一个名词来修改的一连串形容词。增加一个属性到链不会拯救含糊问题。
再次强调,基本规则是一个数据元名词告诉我们它是什么。名称不会告诉我们:
有表名的架构里它的位置
在特定表里它如何使用的(例如在名称里没有pk-,fk-或vw-等词缀的话)
它的数据如何物理存储的(例如对于整形、字符型等没有“i-”,“str-”等词缀)
没有告诉我们数据元不是什么。要确定并精确。
性质组件是从标准化列表里选取,你可以按需添加。这个列表成为你数据字典的一部分,并需要被执行。
在同个表里,当同样的数据元出现2个或更多的角色,要用到<角色>。例如,在组织报表里会有“supervisor_emp_id”和“subordinate_emp_id”,都来自Personnel表里的员工标识数据元“emp_id”。
来看下名称的长度。数据元名称太长或太短都不好。太短的名称很难理解,除非它是标准的缩写。它们也会模棱两可;我最喜欢的例子是一个系统,在数据元名称里有多个student的缩写方式,其中一个是“std”——经常会被误认为是“standard”的缩写,在有些地方甚至是“sexually transmitted disease(性病,由性交引起的病)”。
在名称里避免所有的特殊字符。坚持使用Latin-1字母,数字和下划线。它们会在其它程序语言里复用。没错,数据元名称不只为SQL。同样,避免同时使用仅微软支持的方括号“["或者仅ANSI/IOS支持的双引号标记。这个只是为在数据库里显示格式草率的编程完成,而不是前端显示。有一个可能的异常会是语言翻译问题,Latin-1字母不能正常工作。
执行大写规则来避免大小写敏感问题。我的规则是SQL 关键字是大写,标量数据元是小写,架构对象是大写。在我的《SQL 编程风格(Sql Programming Style )》书里有这方面的研究,但这里我会跳过细节。
对于数据元素名称的标准化性质后缀列表是基于Teradata的内部标准建立,在行业刊物上通用(CMP,MKP和其它出版商)。这些后缀有精确的含义。如果你需要发明你自己的,它们也要是精确的。
"_id"=标识者。在架构里它是唯一的,在架构里任何地方它指的是一个实体。唯一性和标识者不是同个东西。圆周率数字它是唯一的,但他不能标识一个实体。绝不使用“<表名>_id";这是基于位置的名字,并告诉你很可能这不是个真正的主键。简单的”id“太含糊,当你有无限个这样的名称,你就吧你的数据字典弄混。显然,自增长值也不是个标识。我一会谈下这个谬论。
"_date"或"_dt"=日期时间的维度。它是一些时间——工作,出生,终止等等。没有本身就是日期这样列名,那样会很糟糕,因为DATA在SQL里是注册的关键字。
"_nbr"或"_num"=标签数字;这是命名一些的数字字符。不要使用"_no",因为它看起来像布尔的是否值。我更喜欢"nbr"或"num",因为在多个欧洲语言里,它是常见的缩写,而且在"_num"里类似形状字母的组合会有视觉混淆。
"_name"或"_nm"=这是个字母名称本身就能看懂。它也成为名称量表。
"_code"或"_cd"=代码,被可信源,通常是企业外标准化维护的。例如,邮编是有米国邮政服务维护的。在它的上下文里,代码很好理解,因此你可能不需要翻译它。
"_size"=对于一个商品,例如衣服,鞋子, 信封或机器螺丝的行业标准或公司规模。
"_seq"=序列,序数。和标签数不是同个东西,因为它不会缺口。
"_tot"=合计,一个聚合的维度,逻辑上不同的部分。
"_tally"=值的个数,一个聚合的维度。也称为绝对标度。
"_status"=一个内部编码,反射了在知道的模式里将要修改的状态。考虑下军事地位。你会出生,然后改变状态到合法年龄。同时你不能和多个人结婚。你可以从已婚状态修改为离婚,从离婚变成已婚。如果你死了的话,就不能结婚。
"_cat"=分类,来自内部源的需要官方判定的编码。例如,五个飓风的类别。这不像个代码,需要这样的官方判定。
"_class"=不需要外部源的内部编码。一个类别就有一些共性一系列东西;对于动物分类你有规则,是哺乳还是爬行。有些情况你很难使用这些规则,例如在澳大利亚的哺乳动物,但是例外会变成它们自己的分类——单孔目。
"_type"=内外都有同样含义的编码。血型有新定义的测试过程。比起class,type通常更不正式,且会有重叠。type是三类中最弱的,它需要一个判断。在一些州,三轮摩托车是作为摩托车注册。在其它州,会作为汽车。在一些州,如果它有倒车档的话,也会作为汽车。
在实际的使用中,这3个方面会混淆。因此以行业标准来,即使它违反了上述的定义。
"_addr"或"_loc"=实体的地址或位置。地址和位置间有微妙的区别。地址可以指的是街道地址或一些外部的地理系统。位置指的是内部架构,例如仓库仓号。仓号可以保持一样,即使物理位置改变了。
"_img"=图片数据类型,例如.jpg, .gif等等。使用特定的格式作为性质会是重要的。
如果需要的话,可以随意添加。但记得跟踪并标准化你所做的。
对于数据库开发人员,SQL数据类型和域的清楚了解是基本要求,但不是基础。如果你选择了最合适的数据类型,它可以避开很多错误。此外,然后如果你通过约束尽可能精确的定义数据域,你会捕获各种这样的问题,那就是否则会困扰应用开发人员的工作。
在第1部分,我们为它们是什么而命名数据元,并为它们分类。因此现在我们知道,我们不能用通用的,神奇的,普遍的“id”作为数据元。它必须是一个在架构里有确切含义的确切名称。更好的是,名称应该是企业范围,行业范围或在全球的优先排序里。
在第2部分里,我们会决定通过选择合适的域把那些数据放入计算机。域的想法要回到Codd博士。嗯,实际上在这个好博士(Good Doctor)之前,因为它来自数学。域像程序设计里的数据类型,但涉及更多。域有合法的在它的值上完成的运算符,除此之外还有一个数据类型。例如,我可以使用INTEGER在数据库里记录温度;那是个数据类型。当我看到100,那是个值。同时,我可以加,减,乘和除整数,但在温度上这样做没有意义。你不能把体温是36°的人放一起来烧水。模型的类型决定允许什么操作。但我需要知道数字是否以华氏度(Fahrenheit (°F)),摄氏度(Celsius (°C))还是绝对温度(Kelvin (°K));那是计量单位。把这些放在一起,你就有了域。
标准SQL有CREATE DOMAIN语句,它创建的架构对象不是真正的域。它只是个我们即将讨论的缩写。
SQL有3大类数据类型:
数值(Numberic)
字符串(String)
日期(Temporal)
数值型分为精确和近似的。精确数值有INTEGER, SMALLINT, DECIMAL, NUMERIC和BIGINT。它们保存精确的数值并有明确的计算运算符。近似数值有REAL, FLOAT 和 DOUBLE PRECISION(双精度)。这些是浮点数,它们会有四舍五入的问题;近来IEEE浮点标准普遍使用。SQL里也不例外;大多数其它可以进行计算的编程语言使用全部,或者使用这些类型的一些,因为它们在SQL 发明很早以前就嵌入了计算机硬件。PC和迷你计算机用户很可能不知道二进码十进制(BCD(Binary Coded Decimal))数,但多年来它们是商业电脑主机的组成部分。如果你不能理解这些类型,可以google下它们。
字符串类型分为定长和变长。定长字符串是CHAR (n) 和NCHAR(n),这里(n)是它们的长度。NCHAR(n)是“National Character”的简称,它的真正含义是来自Unicode已经实行的任何语言的任何字符。CHAR (n) 是本地ASCII字符集。变长字符串不会像定长字符加个空白字符。对列选择长度和字符集是你在使用它之前,真的要考虑的约束。太短的列会阻止真正的数据,同时太长的列会引入虚假的数据。我最喜欢的一个技巧,当我看到一个以NVARCHAR(255)定义的列,用它来加载中文的心经。这是禅宗佛教(Zen Buddhism)的经典经文。如果我不能教他们SQL,我会带它们启蒙。
日期类型分为纯日期时间和间隔类型。日期时间类型分为日期和时间。它表示时间上一个点。日期类型包含年,月和日。时间类型包括时,分,秒和10进制的次秒。放一起,在标准SQL里它们组成TIMESTAMP数据类型,SQL Server称它们为DATETIME。间隔类型是像天,时,分和秒的时间持续。SQL Server不把它们作为特定类型来表示,使用整数的函数来得到类似结果。
对所有数据类型的四舍五入和截断实现定义。所有数据类型允许NULL,同时在SQL标准里,对所有的数据类型有一组核心的函数,提供所有有的属性扩展和细节实现。例如,SQL Server有BIT数据类型,它是一个精确的数字,只允许0, 1, NULL值。你也会找到更多的数据类型,但这“三个值”会完成你的大多数工作。
当你在数据上你要做计算时,使用数值类型。那就是说像数量,个数,合计等等数据元。如果你需要进行除简单加减外的计算,对于四舍五入和溢出定义一些额外的小数位。
对于要排序的嗲吗你也会使用数值类型。例子就是餐厅的星级(例如1星没有2星的好,2星的没有3星的好,以此类推)。不要为不进行计算或比较的尺度使用数值类型。这个错误的常见例子是邮编(ZIP code);第一个数字0有它的意思,你不能在它们上面进行计算,也不能进行排序。标签数用来表示层级。
对文本,名称和编码体系使用字符串可以用正则表达式表示。例如,邮政编码应该用CHAR(5)定义。不要尝试计算它们;在COBOL里可以,在SQL里不行。
对于日期数据使用日期数据类型。没错,听起来太明显,我都没必要说。但其中一个最常见的设计错误是对于日期和时间数据使用字符串。当然这样做的人不会写约束来阻止像“2010-02-31”的日期或者进行简单时间计算函数。他们已经犯了设计错误,把显示格式放入数据库,而不是前端。
实数和时间是连续的例子,其它数据类型是离散的。离散范围是任何两个不同值之间有限的数字(很可能0)。例如整数{4,9},在它们之间有{5,6,7,8}。有一期儿童电视节目iCarly里,女主持人说服另一个小孩,在5和6之间有一个叫Dirf的新的数字。这个笑话太滑稽了,因为很明显它是错误的。
连续性是数学结构,在任何两个不同值之间有无限个数据值。你总可以添加越来越多的小数到实数或没有任何限制的时间。浮点数有内建的功能来处理四舍五入和计算的问题,但是时间数据不行。通常这就是说你要用一对值来模式化事件(开始事时间,结束时间)。如果事件还是当前的,那么使用NULL作为结束事件值。然后在应用程序里你可以使用COALESCE()函数来转化NULL为CURRENT_DATE或其它有意义的值。
约束是表里的列像记录里的字段的一个原因。约束是在列里约束值的陈述句。最重要的一个是NOT NULL。首次用它来申明每列,然后如果你决定允许NULL,注释行申明来解释说明在上下文里的意思。例如:
sale_start_date DATE NOT NULL,
sale_end_date DATE, – sale is still in progress
CHECK(<涉及列的谓语>)是最简单的行级别约束。然和有效的谓语可以使用,一些典型的使用会是:
你也可以命名约束,如上所示。这是个很好的做法,因为它让错误信息更容易阅读。使用CASE表达式你会得到很多有意思的东西,对于其它复杂规则使用if-then逻辑的其它谓语,例如:
作为练习,写一个CASE表达式来验证是否为数字。表达式会很长但不难。
除了数据完整性,约束还会为你做其它好事。优化器可以用到它们来提高你的查询,插入,更新和删除。它们帮你节约很多前端代码;在这里一次搞定,就不用在很多应用程序里反复做,不管现在还是将来。它们保证所有的前端程序使用同样定义的数据元。
CHECK()约束也可以放在表级别。例如:
表约束涉及到两个或更多的列。在标准SQL里,你也可以有引用其它表的CHECK()约束,但现在我会跳过这个;它没有广泛应用,或不是SQL Server的组合。
SQL的策略是它我们用NULL进行比较时,逻辑值的结果是UNKNOWN,不是TRUE或FALSE。但在CHECK()约束里,TRUE和UNKNOWN同等对待。我们把质疑的好处给UNKNOW。
在行里定义里的最后选项是DEFAULT子句(默认值)。技术上来说它不是域定义的组成,它非常有用。在标准SQL里,它会在数据类型后立即出现,在行定义里,大多数对于它的放置位置都是自由的。它是个常量值或者调用合适数据类型的系统值。最常见的例子是,对于数字和字符串数据在编码架构里的默认值,对于日期数据的CURRENT_TIMESTAMP和CURRENT_DATE。例如ISO的性别编码使用0作为“未知”,1为”男性“,2为”女性“,9作为合法的人(lawful person )(企业和其它组织会在上下文里识别为”法人(legal persons)“)。例如,我们会像这样申明行:
默认值的目的是在没有直接给值的地方提供值。这通常使用INSERT INTO语句完成,在这里你必须构建整个行,但你不想暴露所有行给用户,或者你想节省一些编程工作。总是提供默认不太可能,当你有的时候再这样做。
现在我们会创建行,在接下来的第2篇里我们需要组合行到表里。那里我们约束的其它类型,它会应用到列,不止单行。
经作者同意授权转载
译者:Woodytu
来源:博客园(http://www.cnblogs.com/woodytu/)
原文链接:http://www.sqlservercentral.com/articles/Stairway+Series/69801/
线上分享:【1-50期】
专家专栏:【杨志洪】【杨建荣】【陈能技】【丁俊】【卢钧轶】【李海翔】【魏兴华】【邹德裕】【周正中】【高强】【白鳝】【卢飞】【王佩】
热门话题:【Oracle】【MySQL】【DB2】【PostgreSQL】【大数据】【云计算】【DevOps】【职场心路】【其他】
近期活动
DAMS第二届中国数据资产管理峰会
峰会官网:www.dams.org.cn
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721