SQL Server 数据库设计、命名、编码规范

2.简介

数据库设计是指对于一个给定的应用环境,构造最优的数据库模式,建立数据库及其应用系统,有效存储数据,满足用户信息要求和处理要求

数据库设计和开发标准是使Newegg Support Center的数据库系统的设计和开发正式化的标准。通过此标准,来规范数据库设计。

通过一致的系统解决方案,能给我们的系统带来以下优点:

 开发出高可管理性的高质量系统

 能够快速的进行开发

 减少维护代码的时间

 Þ 非常容易的把代码从一个项目拷贝至另一个项目

 Þ 节省把游标,错误处理信息从一个项目中拷贝到另一个项目中的时间

 Þ 使程序逻辑简单化

 Þ 不用花费时间在常规的事情上,比如对象名称转换等,并可允许多次设计,编程和对复杂事件的测试工作

 在代码出错时大大节省时间

 只要在第一次是有个良好的设计

3.开发环境

3.1数据库模型

CA公司的ERwin/SQL是数据建模的一个首选工具.

在开发经常改变的项目时,使用ERwin来生成表(创建/删除),索引,规则,数据类型等数据库对象的脚本,在对项目文件进行修改之前,请确认已经对这些脚本进行过备份.

任何数据库的改变,不管是在开发中还是在产品服务器中,都要用ERwinDiagram中进行相应的修改.如果产品服务器上做出了更改,则一定要对主脚本和ERwin diagram进行相应更新.

3.2 Diagrams

使用Visio进行数据diagram,流程图,服务器拓朴和其它diagrams进行设计.文档化系统或者处理流程可以大大有利于团队间的协作.

3.3版本控制

推荐使用Visual SourceSafe(VSS)对NESE数据库对象进行管理.

在任何项目中,都应该有很好的代码更改控制,初始版本文件应该放到VSS中并被注释.所有对这些文件的后继更改都应该放到VSS中管理.

3.4 源码目录结构

在项目刚开始时,找到一个所有Team成员都能够访问的共享.按照以下结构初始化VSS目录和数据库子目录:

/CMD 包括全系统脚本的脚本文件,如果是一个多数据库的系统,应该有一个可能创建所有数据库的命令文件.

/DBName 在系统中的每个数据库应该有自己的目录结构,如下:

/CMD:用来创建此数据库的脚本文件,并且来更改数据库构架.

/DAT:用来刷新此数据库的数据文件.

/SP:用来存储存储过程的脚本

/TBL:除了下面子目录,这个目录应该包括表的定义脚本,每个表应该有它自己的脚本,此脚本应该包括经表的删除,创建语句,索引,触发器,完整性参照,Check约束,默认值约束等,每个表创建语句应该包括在不同的脚本中,并被把归类到类似于下面的相应的子目录中.注意:本目录中的脚本和以下子目录的脚本应该命名为它所影响的表名,比如:表名.sql:

/Check:为每个表创建独立的Check约束定义脚本,Check约束应该使用alter table add constraint 来创建,并且每个alter table 语句只能包括一个contraint。

/DEFAULT:为每个表创建单独的Default约束定义脚本。注意这些是Default Constraints,而不是在创建表时的Default。

/FK:为每个表创建单独的外键约束定义脚本。

/Index:为每个表创建一个索引定义脚本,并把对应索引定义脚本放进去.

/PK:为每个表创建单独的主键定义脚本

/TRG:为每个表创建单独的触发器定义脚本,并把应用到此表上的触发器放进此脚本文件中。

/UDF:用户自定义函数。

/View:视图定义脚本。

/DCL: 数据控制语句脚本—主要包括控制所有的数据库对象的Grant 和 Revoke语句。

/MISC:这儿用于存放各式各样的其它脚本,例如alter table脚本或者一次更改的脚本,注意,不要放此目录看成是一个包罗各种脚本的容器,只应包括需要放到VSS中,但是又不属于上面所列目录的脚本。

4.物理数据库模型&字典

E-R 图表和数据字典可以让任何使用数据库的人都明确的知道如何从数据库中获得数据。E-R图对表明表之间关系很有用,而数据字典则说明了每个字段的用途以及任何可能存在的别名

4.1 物理数据模型

物理模型图形化的展现数据库的实现,它由逻辑数据模型及底层关系型数据库管理系统来决定,由逻辑模型转变为物理模型包括以下的任务:

 为实体和属性创建合理的数据库名字

 为每个属性设计数据类型和是否允许为空

 定义主键,外键和索引

 定义规则和默认值

 为优化性能,尽可能使数据库设计规范化,比如遵循1NF(第一范式),2NF(第二范式) 和3NF(第三范式)

为加快数据库设计速度,目前有很多数据库辅助工具(CASE工具),如Rational公司的Rational Rose,CA公司的Erwin, Sybase公司的Power Designer以及Oracle公司的Oracle Designer等。

ERwin主要用来建立数据库的概念模型和物理模型。它能用图形化的方式,描述出实体、联系及实体的属性。ERwin支持IDEF1X方法。通过使用ERwin建模工具自动生成、更改和分析IDEF1X模型,不仅能得到优秀的业务功能和数据需求模型,而且可以实现从IDEF1X模型到数据库物理设计的转变。ERwin工具绘制的模型对应于逻辑模型和物理模型两种。在逻辑模型中,IDEF1X工具箱可以方便地用图形化的方式构建和绘制实体联系及实体的属性。在物理模型中,ERwin可以定义对应的表、列,并可针对各种数据库管理系统自动转换为适当的类型。

设计人员可根据需要选用相应的数据库设计建模工具。例如需求分析完成之后,设计人员可以使用Erwin画ER图,将ER图转换为关系数据模型,生成数据库结构;画数据流图,生成应用程序。

ERwin是现在被广泛使用的数据模型设计软件,IDEF1X用来建立信息模型的标准。物理模型基于逻辑模型,ERwin同时支持逻辑模型和物理模型视图的设计。同时,ERwin可以产生所有的用来创建数据库的DDL脚本.

4.2 IDEF1X 方法

下面的关系图描述最常用的IDEF1X表达式协定:

4.3 物理数据库数据字典

使用ERwin来对物理数据库数据字典进行维护。在属性定义编辑器中输入属性定义 。然后你就可以从ERwin中生成报表,把它们放到word 文档中,在属性报表中,将包括属性名字,数据类型,Null/Not Null和主/外键选项。把实体和属性定义放到一个一个文档中是很困难的,所以在不同的报表中创建它们.

4.4 性能设计

作为一个常规的规则,在一个具有标识列(identity property)的列上定义一个主键。这使与之存在关系的表有一个窄的行宽,从而使同一数据页中能被存储更多的数据行,因此相同条件下将会有更少的描描操作。

 定义聚集主键,这将会使二级索引占用较少空间。

 在外键上创建索引。

 不要使用过多的索引,要确认你知道哪个索引将被使用。

 确认对应表存在统计信息。同时在数据库中打开Auto Generate Stats 和 Auto create stats 选项。在一个只读的数据库中,我们可能需要手动的创建统计信息.

 在一个只读的数据库或者是很少被更改的数据库中使用100%的索引填充率。

 SELECT语句中,在非事务和特别的完整性要求的上下文中,要使用Table Hints――WITH NOLOCK

5.命名协定

5.1 数据库命名原则及版本控制

5.1.1数据库命名原则

数据库命名要遵从以下命名原则:

表意性原则:数据库命名本着表意性原则,即命名应尽量反映存储/action/view/column的数据内容。

长名原则:很少使用或者不使用缩写,适用于DB命名之外的任一对象

数目最少化原则:数据库对象应该尽量满足数据最小化原则,也就是数据库数目,存储过程,视图等数量最小化。

5.1.2 数据库版本控制

当由于多个版本的应用系统同时存在或者是其它特殊原因而使用完成相似的功能的对象存在同时存在时,使用版本控制:版本号在对象名的后面加上[_v1]

如:

    Up_IM_DailyUsageStatsUpdate_v2

Up_IM_CustomerGetUsingLastName_v2

5.2 Server/命名实例的命名

基于Domain命名, DOMAIN 的粒度,可分为三级 ,分别是Server级别,Database级别和表的前缀级别。此为第一级别。

1. ONLINE PRODUCTION SERVER

NEWSQL

NEWSQL2

EHISSQL

WAREHOUSE PRODUCTION SERVER

S3SQL01

S4SQL01

S7SQL01

S6SQL01

5.3 数据库命名

数据库命名应当遵从上面所提及到的协定,包括没有特殊的字符,使用文字数据字式字符……..,项目代码命名不应该同样被数据库使用.数据库名字应该唯一以避免冲突等.以Domain来标识, 拆分DATABASE,基于大的逻辑范畴,如operation 范畴以及部门类别,另外:其它与部门相关的DATABASE 可以放到相关的SERVER 下。

 数据库数目最少化原则

 数据设置尽可能MERGER原则

 peration database AND Dept level Database set up rules

 拆分DATABASE,基于大的逻辑范畴,如operation 范畴以及部门类别

 其它与部门相关的DATABASE 可以放到相关的SERVER 下。

 下面为与OPEARATION 相关的DATABASE示例:

如: 下面为与OPEARATION 相关的DATABASE:

ItemProfile

Inventory

Codecenter

CustomerProfile

SalesOrder

Accounting

Sevice(RAM)

eCommerce

SupplerChainManagement

DropShip
SerialNumber
WorkFlow

ShippingCarrier

WarehouseManagment

HumanResource

 其它与部门相关的DATABASE 可以放到相关的SERVER 下。

MKT

Pm

Mis

CUSTOMER SVR

ADMIN

ACCOUNTING

2. INTERNAL DEPT DATABASE SERVER

5.4 数据库对象—表,视图,列名,约束,规则,默认值

数据库对象应该被清晰的命名,要确认不存在歧义。数据库对象的名字应该包括自己尽可能多的信息,并要详细的说明与之相对应的项目文档.选择合适的命名空间是一个很让人困惑的问题(比如我们不能从一个存储过程的命名上看到是哪个项目在使用这个对象),命名名字唯一的在数据库级别标识自己,无论在哪儿使用它,请确保在一个项目中命名方法的一致.在一个小的上下文看来很清晰的对象在一个大的上下文中可能就会丢失他的含义.节省击键次数永远不能用来作选择名字的标准.

下面供述了数据库对象的命名协定,所有的数据库对象命名都应该遵循下面协定.

 如果不是长度限制,缩写应该被避免.当使用缩写时应该遵从下面的缩写规范的规则.缩写应该在项目范围内保持一致并被存档.

 不要使用特殊的字符;对文字数字式字符加以限制.

 在命名时为了更好的标识表与应用程序之间的关系,我们要使用“[function name]|[main module name]_表名”的形式.

比如: Newegg_customer

Blocked_customer

Abs_customer

EDI_....

MDF_....

 当要修改一个对象的架构时,首先要确认与之存在依存关系的对象不受影响.要查看与之存在依存关系的对象,可以在企业管理器中”右键相应对象”->”ALL TASKS”->”DISPLAY DEPENDENCIES”来查看或者使用系统存储过程SP_DEPENDS来查看:

EXEC SP_DEPENDS 'dbo.GetCategories’

 当底层表结构更改时,视图都应该被重新创建.这个应该特别被注意,特别是视图是使用如”SELECT * FROM table”时.

 强烈建议任何时候都不要使用类似于”SELECT * FROM”的语句.

 当在不存在事务上下文中或者是完整性要求不是很高时,在SELECT语句中使用WITH NOLOCK.

5.5 缩写规范

由于长度的限制而需要使用缩写时,应当遵从以下的规则:

 以分类单词作为名字的起始.

 缩写的第一个字符应该与单词的第一个字母相同,比如,SFER就不是Transfer的有效缩写.

 Identify and abbreviate the root word, usually the shortest form of the word.

 缩写时省略元音字符,除非特殊原因,重复字符中将被省略一个。

 缩写不应该暗示到其它的单词(比如:单词“parent”可能被缩写为”PRNT”,但是“PRNT”很容易让人想起单词“PRINT”,一个比较好的缩写就是使用”PSRNT”)

 相似单词的缩写应该一致,比如如果Charge缩写为CHRG,则change应该被缩写为CHNG.

 当组合词或者短语被广泛使用时,并且大多人都能识别出这些缩写时,应该使用他们的首字母缩写,因为大多人都能识别出这些缩写。

5.6 列名

列名应该基于逻辑设计中的属性名,

修饰符: 可选,它以角色名而被大家所熟知(PrimaryPartID, AlternatePartID)

主词(primary word): 除非是主键,否则不需要。因为表名已经限定列名,所以不必要冗余的加入此信息。

限定词: 可选;附加的描述信息,用来帮助我们更明确的了解数据的意义(InventoryOnHandQuantity, CustomerLastName)

分类词: 需要;用来标识数据的类型,一般列名的最后一个单词(PartID, CountryCode, CustomerName, InvoiceAmount). 下面是一些标准的分类词:

CLASSIFIER DESCRIPTION

Address Street or mailing address data

Age Chronological age in years

Average A numerical average (self-explanatory)

Amount Currency amount -- always is money

Code Code from a specific domain of coded values

Count Count of something -- years, payments, etc.

Date Calendar date

Datetime Date including time

Day Day of month (1 - 31)

Description   Brief narrative description (usually of a code)

Duration Span of time, whether days, months, years, etc.

Identifier Unique identifier for something

Image A graphic image, such as a bitmap

Indicator A boolean indicator

Measurement Measurement of physical dimensions

Month Month of year

Name Formal name of something

Number Number (self-explanatory)

Percent Number expressed as a percent

Quantity a number of things

Rate Number expressed as a rate

Ratio A proportion, or expression of relationship in quantity, size, amount, etc. between two things

Text Freeform textual information

Time Time of day

Weight Weight measurement

Year Calendar year or julian year number

下面的ERwin diagram 示例推荐的命名协定:

5.7 存储过程命名

存储过程命名使用上面描述的表,视图和列的命名一样的规则,包括不要使用特殊的字符,大写首词,不要使用组名(比如,myProc1),不要使用系统保留的“sp_”前缀(比如可以使用up_),使用文字数据式字符,为了使存储过程名字更有意义,名字中应包括“对象”+“动作”的方式 。

存储过程命名示例:

Up_IM_CustomerGet(IM 标识itemmaintain系统使用)

Up_IM_LoginValidate

Up_IM_RegionDataRotate

Up_IM_DailyUsageStatsUpdate

Up_IM_CustomerGetUsingLastName

下面是可选的命名规则,使用“动作”+“对象”的方式:

Up_IM_BuildSalesPerspectives

Up_IM_InsertSellInFileAudit

5.8 游标命名

游标应该以下面的标准来命名:

表名或者对象名字{使用此游标的对象名字}+Cursor

例如:

FiscalMonthCURSOR

EmployeeListCURSOR

5.9 触发器命名

触发器命名应该遵从以下的协定:

表名+ { Ins | Upd | Del}

Ins标识此触发器为insert触发器,UPD标识此触发器为update触发器,同理DEL被用来标识delete触发器:

触发器示例:

CustomerIns

ProductCodeDel

BusinessUnitUpd

5.10 索引命名

索引的命名应该在表空间内唯一,当查看执行计划时可以有效的对索引进行识别..

[IX][类型(U标识Unique,C标识Clustered)][列名(s)]

 当对单列进行索引时,你可能需要使用列的全名.

 当对多列进行索引时,要使用你所能想到的最优的缩写.

 当对一个表的所有列进行索引时,使用ALL单词.

 在多列名中使用下划线以增加可读性.

 不要为索引加上序列号

例如:

IXUC_SalesId (clustered unique)

IXU_SalesDate (unique)

IX_SalesId_MonId_QtrId_WkId (composite index)

IX_MonId_YrId (composite index)

IXC_SalesId (clustered not unique)

5.11 主键和外键命名

主键:对主键进行命名应该遵从以下标准:

PK_表名

例如,主键名

PK_Customer

外键:对外键进行命名时应该遵从以下标准:

ChildTableName_FK_ParentTableName

外键名字示例:

Customer_FK_Country

Sales_FK_Customer

5.12 Check约束命名

Check约束的命名遵从以下的约定:

表名CK{数字}

数字被用来区别同一表中的其它Check约束.

Check约束命名示例:

CustomerCK1

5.13 源文件命名

文件命名应该使用和数据库对象命名相同的规则-包括没有特殊字符,限制名字以避免歧义,大写每个单词的首字符,所有的包括SQL脚本的文件应该以.SQL为结束字符,而不是.SP,.TRG等。可执行的批处理文件应该是以.CMD为扩展名.

5.14 Job的命名

Job命名使用以下方式,其中在” Job名”中要标识出此JOB的功能:

Job_[应用系统名_][数据库名_]Job名/ [storce procedure名]

For Example:

Job_IM_IMDB_backup

5.15 用户自定义函数命名

满足NESE命名的基本原则

用户定义的函数使用fn来标识

名字中应包括对象动作的方式

For example:

Un_IM_CustomerGet(IM 标识itemmaintain系统使用)

5.16 用户自定义数据类型命名

满足NESE命名的基本原则

用户定义的函数使用Udt来标识

用户定义的数据类型

如:

UdtPhone

UdtBirthday

UdtAge

5.17 复制命名

在满足NESE命名的基本原则利用Rp来标识复制

Rp_[DBname]复制名{_[Trn]|[Mrg]|[Snp]}

[Trn]:标识为Transactional Publication

[Mrg]:Merge Publication

[Snp]:Snapshot publication

如:

  Rp_Foodmart_Trn

6.SQL Server代码规范

6.1 对象创建脚本

使用数据库(USE)

不要在SQL Server源文件使用USE 数据库名语句,这会导致只能在此数据库创建对象,OSQL命令行工具可以在数据库中重定向.

删除对象:

IF Exists语句应该在Drop 对象语句之前执行:

IF object_id(‘tablename’) IS NOT NULL

DROP TABLE TableName

表创建,索引,触发器

在开发过程中,所有的表创建脚本都可以合并为一个项目中的文档中(使用ERwin将会非常将会非常容易).此文档中每个表创建语句的后面应该有Go语句,当系统到达一个稳定点时,应该把文档中的从多个表按照表的不同分到单独的文件以利于维护.这此文档应该放到/TBL子目录下.

在每个创建单独表的文档中,都应该在创建表语句的前面使用if exists() drop table 语句.

每表创建单独的索引脚本文件,并把它们放到/TBL的子目录/INDEX下,命名为<表名>.SQL.在每个创建索引的语句之前,应该使用”if exists() drop…”语句,这样当此索引 已经存在时,它将会被先删除然后重新创建.

如果必要,创建单独的触发器脚本,并把它放置在/TBL的子目录 /TRG下.也命名为<表名>.SQL.

主键,外键,Check约束和默认值

所有的约束和默认值定义都应该直接通过ALTER TABLE命令来创建,在创建表的同时对约束和默认值进行嵌入式的定义应该被禁止,为了清晰起见.一个ALTER TABLE语句不应该包括多个对表的更改,我们推荐在每个更改语句之前使用“if exists() alter …. Drop onstraint…”,这样当此约束已经存在时,可以被重新创建。

主键定义有单独的脚本,并放置在/TBL的子目录/PK下。

外键定义也应该有独立的脚本,并放置与/TBL的子目录/FK下。

每个表的Check约束语句也应该放在一个单独的目录/TBL的子目录/CHECK之下。

表中列的默认值定义应该放在一个独立的脚本,并放置与TBL的子目录/DEFAULT之下。

在CREATE TABLE 中显式的为表的每一列定义是否为空,不要依靠数据库的默认设定,因为在数据库中这可能不同,从而出现了无意识的错误。

为索引和主键指定CLUSTERED或者NONCLUSTERED,不要依靠数据库的默认设定。

Grant 语句

.Grant语句应该独立与表,存储过程等的创建语句,为Grant语句创建单独的文件可以使我们很容易的重新对数据库对象进行授权。

6.2 数据库更改脚本

当在产品服务器上对数据库进行更改时,要非常小心和注意.这会避免产品服务库发生丢失数据或者停止服务等灾难的发生.

这个脚本应该是可以对数据库的对象进行判断的,比如,如果一个对象已经存在,那应该成功结束,而不是把此对象删除,因为此对象可能已经包括数据.比如表,如果要删除一个已经存在的表,则也删除了表中的数据.

当表或者视图重新被创建时,也要重新创建授权.

任何时候,当表被创建或者被重新创建时,确认所有的依靠关系,比如PK,FK,索引,触发器,默认值和视图都应该对重新创建.

当底层表结构更改时,视图都应该被重新创建.这个应该特别被注意,特别是视图是使用如”Select * from table”时.

如果一个表被复制,在SQL Server 2000使用sp_repladdcoumn和sp_ repldropcolumn存储过程.

脚本包括错误检测及事务的回滚.当把一个表分成两个表或者是多个复杂的轮换时,这个就很重要.

在脚本中应该包括Raiserror脚本,这样会使以后的打错工作较为容易.理想情况下,应该首先使用Raiserror(‘msg’,0.1) WITH NOWAIT,因为这样当它被执行时会被送到客户端。

DECLARE @rows int, @rc int

SET NOCOUNT ON

IF NOT EXISTS( SELECT * FROM INFORMATION_SCHEMA.COLUMNS

WHERE COLUMN_NAME = 'AgreementDurationID' AND

TABLE_NAME = 'BudgetSellIn')

BEGIN

IF EXISTS (SELECT * FROM sysobjects

WHERE name ='BudgetSellIn' AND xtype = 'u' AND

replinfo in (1,3))

BEGIN

EXEC @rc = sp_repladdcolumn

@source_object = 'BudgetSellIn'

,@column = 'AgreementDurationID'

,@typetext = 'int NOT NULL CONSTRAINT DF_BudgetSellIn_AgreementDuID DEFAULT 0'

IF @rc <> 0

BEGIN

RAISERROR('Failed adding column AgreementDurationID to BudgetSellIn....', 0,1) WITH NOWAIT

GOTO CRASH

END

-- Drop the default constraint

ALTER TABLE BudgetSellIn

DROP CONSTRAINT DF_BudgetSellIn_AgreementDuID

IF @@ERROR <> 0

BEGIN

RAISERROR('Failed droping DEFAULT CONSTRAINT DF_BudgetSellIn_AgreementDuID from BudgetSellIn....', 0,1) WITH NOWAIT

GOTO CRASH

END

RAISERROR('Successfully added column AgreementDurationID to BudgetSellIn..',0,1) WITH NOWAIT

END

END

ELSE

RAISERROR('New Column AgreementDurationID already exists in BudgetSellIn..',0,1) WITH NOWAIT

IF NOT EXISTS(SELECT * FROM sysobjects

WHERE name = 'BudgetSellIn_FK_AgreementDuration' and type = 'f')

BEGIN

ALTER TABLE BudgetSellIn

ADD CONSTRAINT BudgetSellIn_FK_AgreementDuration FOREIGN KEY

(AgreementDurationID) REFERENCES AgreementDuration(AgreementDurationID)

IF @@ERROR <>0

BEGIN

RAISERROR('Failed Adding FOREIGN KEY To AgreementDuration....', 0,1) WITH NOWAIT

GOTO CRASH

END

RAISERROR('Successfully added FOREIGN KEY To AgreementDuration....', 0,1) WITH NOWAIT

ELSE

RAISERROR('FOREIGN KEY to AgreementDuration Already exists on Table BudgetSellIn...',0,1) WITH NOWAIT

END

GOTO VERYEND

CRASH:

RAISERROR ('Error Adding New Columns to BudgetSellIn...', 18,127)

VERYEND:

6.3 综合开发实践

 不要在存储过程中使用输入参数来更改它们的功能。这样会使程序中的耦合度大大增加。例如,一个存储过程根据输入参数向Customer表中插入记录或者Employee表中插入记录,这会使此存储过程复杂化和松耦合度,此时使用两个独立的存储过程(一个存储过程实现向Customer表插入记录和一个实现向Employee表插入记录)将会更好.

 不要使用关键字作为对象的标识.

 使用ANSI风格的连接。不要使用老的T-SQL 外连接“*=”,这可能在将来的版本中不被支持.

 不要在存储过程中使用双引号,这是因为ANSI标准中默认就是QUOTED_IDENTIFIERS是设置为ON的,所以SQL Server将视任何双引号之内的字符为标识符.

 当调用存在参数的存储过程时,一个比较的好建议就是使用参数名和参数值来传递,而不是依次使用参数值来调用。

 为了清晰起见,it’s often a good practice to specifically include the correct parameters. For example:

 当创建索引时,不管是否是cluster,都要明确的使用它

 当创建在一个具有Identity属性列的表时,应该包括起始数字和增加数字,也要包括是否为空。如:

Create table Table1 (ColA int identity(1,1), colB varchar not null).

 我们都知道应该尽是避免硬编码,但是必要时,增加注释来说明硬编码的意义,例如:

“where SalesChannelLevelID = 1 – SellInsales”

 当使用RPC调用一个存储过程时,在服务器名字上加上中括号,比如,一个服务器的名字可能叫“Fridge/Inst1”,如果不加中括号,将会出错.

INSERT INTO #CMODSSourcePROHistory

EXECUTE('['+@ServerName +'].CMODS.dbo.RSPGetSourcePROHistory')

6.4 文本文件格式

保持单行字符少于87个

每行SQL语句的宽度应该少于87个字符,如果太宽,我们在查看和打印时易读性会很差。

缩进间距

在T-SQL文件中,使用3个字符的缩进,我们可以在查询分析器中配置Tab键为3个空格的缩进(默认为8),如下示例所示:

IF EXISTS (SELECT ...

FROM Table ...)

BEGIN

statement 1

statement 2

IF (...)

BEGIN

statement 1

END

END –- end of if table rows exist

SQL代码格式化

保证代码在项目范围内的一致性。为所有的代码段使用相同的格式和缩进方式.

大写所有的关键字,并且为SELECT,FROM,WHERE,GROUP BY,HAVING语句另起一行.为WHERE中的每一列另起一行,右对齐这些关键字。当WHERE语句包括子SELECT或者OR语句时,缩进它们,以使得比较容易的分清他们之前的依从关系(比如哪些语句是在一起的)

在复杂的连接中,缩进JOIN 和ON关键字,这会使SELECT,FROM和WHERE更突出,

排列CASE和相应的END语句。

我们大多喜欢在列表的左边使用逗号,因为这样在将来增加新的列时不用在最后的列后面加上逗号.同样这样也非常容易检查到是否有逗号没有写.

下面的例子显示一个复杂的WHERE T-SQL语句语句:

SELECT

Col1

,Col2

,Col3

,CASE WHEN t1.Col1 = 1 THEN ‘First’

WHEN t1.Col1 = 2 THEN ‘Second’

ELSE ‘Last’

END AS Col4Name

FROM Table1 t1

JOIN Table2 t2

ON t1.Col1 = t2.Col2

AND t2.Date = ‘1/1/1994’

WHERE (t2.Col4 = 3

OR ( t2.Col3 < 6

AND t2.Col4 > 5

)

)

AND NOT EXISTS

(SELECT *

FROM Table5 t5

WHERE t5.Col2 = t1.Col2)

GROUP BY...

HAVING ...

在IF和WHILE时,为BEGIN和END另起一行.

当在BEGIN和END中有很多行的T-SQL语句时,对END进行注释以使它们能够很好的匹配起来.

IF EXISTS (SELECT *

FROM #tmptable)

BEGIN

statement1

statement2

END

ELSE

BEGIN

statement1

statement2

END

WHILE (sky = ‘blue’)

BEGIN

statement1

statement2

END –- end of while sky = ‘blue’

当进行插入操作时,应该给出列名

从来不应该依靠列的物理顺序来进行表的插入操作。应该在插入操作进行指定插入列的顺序,这将会使我们的维护和排错时更加容易.

INSERT Table1 (

Column1

,Column2

,Column3

....)

SELECT

Column1

,Column2

,Column3

...

FROM Table2

WHERE ....

OR

INSERT Table1

(

Column1

,Column2

,Column3

....)

VALUES (

value1

,value2

,value3

....)

在SELECT列的列表中要使用表的别名

在Insert/Select 语句中,updates或者deletes操作可能要连接很多表,不管是否需要,我们推荐总是为每列加上表的别名。这将会防止当在连接的表中加入新列是出现错误“ambiguous column name”发生,同时这也能帮助维护人员列是来源于哪个表,试着使用表的别名来标识表是非常有用的,但是记住不要超过3

或者4个字符。

在Insert/Select语句,

INSERT Table1 (

CountryCode

,SubsidiaryCode

....)

SELECT

Ctry.CountryCode

,Sub.SubsidiaryCode

...

FROM Country ctry

JOIN Subsidiary sub

ON ctry.SubsidiaryCode = sub.SubsidiaryCode

------------------------------------

-- For update statements, do not use a column alias

-- for the target column on the set statement.

-- To avoid ambiguouity, the table alias is listed after the UPDATE.

------------------------------------

UPDATE ctry

SET Countryname = ictry.CountryName

,SubsidiaryCode = ictry.SubsidiaryCode

FROM Import..Country ictry

JOIN Country ctry

ON ictry.CountryCode = ctry.CountryCode

标题(header)

所有的文件都应该有一个标题,把文件头把在Create…AS语句之后,通过这种方法注释,其中也将包括更改历史,都将会存储在sysComments中,在用来和最近的Visual Sourcesafe中的版本进行比较时将会非常有用.

/******************************************************************

*name : --函数名/View/Store proc

*function : --函数功能

*input : --输入参数

*output : --输出参数

*author : --作者

*CreateDate : --创建时间

*UpdateDate : --函数更改信息(包括作者、时间、更改内容等)

*************************************************************************/

如:

IF EXISTS ( SELECT * FROM sysobjects

WHERE name = ' AddPublications ' AND type = 'P' )

BEGIN

PRINT 'Dropping Procedure: AddPublications '

DROP PROCEDURE AddPublications

END

PRINT 'Creating Procedure: AddPublications '

GO

CREATE PROCEDURE AddPublications

AS

/******************************************************************

** Desc: The purpose of this procedure is to create the Publications

** for replication, as defined in the Publication table.

**

** Processing Steps:

** 1. If database is not already published, publish it

** 2. For each Publication in Publication table:

** IF Publication doesn't exist

**  Create Publication

** IF SnapShot Job for Publication doesn't exist

** Create SnapShot Job

** 3. Change LogReader Agent from default settings

** 4. Change Checkup Agent Heartbeat from default settings

**

** Parameters: none

** Restart:

** Restart at the beginning. No code modifications required.

**

** Tables Used:

** Publication    select

** master..sysdatabases   select

** Distribution..MSPublications select

** Distribution..MSSnapShot_Agents select

** Distribution..MSLogReader_Agents select

** msdb..sysjobs select

** msdb..sysjobsteps select

**

** Return values: = 0 Success

** < 0 Failure

**

** Called By: None

**

** Calls: sp_replicationdboption

** sp_addpublication

** sp_addpublication_snapshot

** sp_update_job

** sp_update_jobstep

** sp_update_jobschedule

**

** Author: Michael Eldridge

** Date: 02/05/1998

********************************************************************************

** Change History

********************************************************************************

** Date Author Description

** ---------- --------- -----------------------------------------------------

** 10/03/1998 jsuther Sphinx agents now have step named 'Run Agent.' not

** 'Run Replication Agent.'

** 08/18/2000 brianell Standardized

******************************************************************************/

SET NOCOUNT ON

SET ARITHABORT ON

注释

在注释中的“Change History”部分中包括更改历史.

在一个包括多行的注释中,双中划线应该被放在每行的开始,不要使用/* */风格的注释,因为注释中可能被嵌套.

不允许注释超过87个字符的宽度.

在注释中包括下面的信息将会很有帮助:谁申请了特定的更改,更改的时间,为了达到的商业目的的描述.

Comment complicated sections of code just above the section of code at the same indent level.

-----------------------------------------------------------------

-- Coment at level one

-----------------------------------------------------------------

WHILE (...)

BEGIN

statement 1

statement 2

-------------------------------------------------------------

-- Comment at level 2

-------------------------------------------------------------

statement 3

END

6.5 使用Return

使用Reture来标识存储过程,批处理的成功或者是失败.

>= 0 标识成功

<-99 标识失败 (SQL Server reserves -1 through -99).

当存储过程中包括很多语句时,Return一个明确的数字对于错误语句的定位来说是非常重要的.

IF @@error <> 0

BEGIN

RAISERROR (‘ValidateEndDate 50001: Invalid End Date’),18,127

RETURN -200

END

输出参数可以被用来返回值.

EXEC @rc = ValidateReportEndDate @SourceID, @FiscalPeriodID output

所以有存储过程应该用Return来标识是否有错误发生.即使发生错误的可能性不在在(比如一个查询中只包括SELECT语句),也应该使用Return 0来标识成功执行.有效的使用Return将能很好的保证数据的一致性.

Select 语句可以返回一个或者多个行集.不要使用”SELECT * FROM Customer”,因为如果在Customer表中新增了一列,应用程序有可能没有对此列进行处理的操作,从而有可能产生错误,并且“SELECT * FROM Customer”对于数据库性能来说有很多负面影响。

6.6 错误处理

我们应该在我们的系统中包括错误处理操作。我们推荐使用@@ERROR对所有可能会出现问题的语句后进行判断.

根据场景的不同,我们可以从两种方法中选择一种,那就是RAISEERROR和RETURN(只在存储过程中可用).

不要在RAISERROR中使用WITH_LOG,因为这要求SA的权限.

不要使用Sysmessages来为RAISERROR存储错误信息,因为在多数据库服务器的环境下会增加维护成本.

当调用一个存储过程时,一定要对错误进行检查,也就是对Return的信息进行检查.如果存储过程返回一个值,此时它将会重值@@Error为0.

EXEC @rc = ValidateReportEndDate @SourceID, @FiscalPeriodID output

IF @rc <> 0 or @@error <> 0

RAISERROR (‘LoadSalesFile 50001: Error calling ValidateReportEndDate Date’),18,127

RETURN -200

END

6.6.1 在存储过程和触发器中使用RAISEERROR

在存储过程和触发器中, RAISERROR 应该按以下格式返回错误信息:

{name of,proc or trigger generating error}, {error number} : {error message string}

示例:

CustomerListGet 50001: Unable to retrieve. Invalid channel id. (proc example)

CustomerINS 50002: User is not a manager. Insert denied. (trigger example)

这是一个简单的例子:

IF @@ERROR <> 0

BEGIN

SELECT @errmsg='50000: Cannot Declare SnapShot_Cursor'

GOTO ErrorHandler

END

ErrorHandler:

SELECT @errmsg = @procname+' '+@errmsg

RAISERROR (@errmsg,18,127)

6.7 Print 语句

在存储过程和SQL脚本中我们使用print 来把某些重要点的信息打印出来,从而帮助我们排错或者是查看实现的性能.在SQL语句完成后打印出对象名字,影响的行数和用时将会很有用.

语句RAISEERROR(‘’,0,1) WITH NOWAIT语句在PRINT 或者SELECT中是首选的,因为它将将消息立即发送给客户端。Print 或者SELECT将会在批处理结束时才送到客户端(或者是缓冲区满).

这就是个例子:

SELECT @Msg = 'Rows inserted from ' +@SalesTbl +': ('+

+ convert(varchar,@rows)

+') at '+convert(varchar,getdate(),120)

RAISERROR (@Msg, 0,1) WITH NOWAIT

Or

SELECT @Time = convert(varchar,getdate(),120)

RAISERROR( '%d rows have been inserted to %s table at %s' ,0,1

,@rows, @obj,@Time) WITH NOWAIT

Output:

34567 rows have been inserted to Sales133 table at 2001-02-21 14:47:26

6.8 参照完整性

使用ALTER TABLE ADD CONSTRAINT 命令来强制参照完整性. 除非要调整数据库间的完整性,否则不要使用触发器来强制参照完整性。

6.8.1 主键

每个表都应该有主健存在。

当需要使用代理关键字来标识行的时候,使用identity列. 此时应该在此列上使用primary约束

从SQLServer 7.0开始,我们一般推荐在主键上创建clustered 索引来最小化存储空间和达到性能要求.

6.8.2 外键

在外键的定义中,FK列应该和被参照的PK(Unique)列有相同的数据类型.注意, 在一列上创建FK并不会自动在此列上创建索引.所以我们推荐在FK上手动创建索引以提升性能.

6.9 触发器

触发器可以被用来强制商业规则,在数据库间强制数据完整性.

6.10 游标

除非有特殊的原因,定义游标时要定义局部的,只读向前的和静态的.

DECLARE ErrorStatsCursor CURSOR LOCAL FORWARD_ONLY STATIC FOR

-- DECLARE ErrorStatsCursor INSENSITIVE CURSOR FOR -- This is the global cursor ANSI92

SELECT PumpMilestoneID

FROM PumpMilestone

ORDER BY PumpMilestoneID

OPEN ErrorStatsCursor

FETCH NEXT FROM ErrorStatsCursor

INTO @MilestoneID

WHILE @@FETCH_STATUS = 0

BEGIN

-- First, get the average time for the Milestone

***

SELECT @MsgID = @@Error

IF @MsgID <> 0

GOTO ErrorHandler

FETCH NEXT FROM ErrorStatsCursor

INTO @MilestoneID

END

CLOSE ErrorStatsCursor

DEALLOCATE ErrorStatsCursor

RETURN 0

ErrorHandler:

IF CURSOR_STATUS('local', 'ErrorStatsCursor') >= 0 -- Cursor Open

CLOSE ErrorStatsCursor

IF CURSOR_STATUS('local', 'ErrorStatsCursor') = -1 -- Cursor Closed

DEALLOCATE ErrorStatsCursor

/* IF CURSOR_STATUS('global', 'ErrorStatsCursor') >= 0 -- Open and Allocated so close it

CLOSE ErrorStatsCursor

IF CURSOR_STATUS('global', 'ErrorStatsCursor') = -1 -- Cursor Closed

DEALLOCATE ErrorStatsCursor

*/

EXEC util_ErrorLog @ProcName, @MsgText, @MsgID

RAISERROR (@MsgText, 18, 127)

RETURN -200

6.11 用户自定义函数

调用时一定要使用所有者后加用户自定义函数名的形势,例如:

SELECT dbo.fnColumnList('sysobjects', 1)

应该避免:

 不能够使用临时表,但是可以使用表变量

 在用户自定义函数中不能够使用debugging,print和RAISEERROR

 不能够输出多个值,除非输出表

 在用户自定义函数中不能够使用可选参数,比如你在一个函数中定义了三个参数,并为它们赋值,以后如果你调用此函数时一定要为这三个参数分别赋值.

6.12 局部变量命名标准

这是局部变量的命名标准:

@rows

int

标识此语句影响的行数:

SELECT @rows = @@rowcount

@err

int

本地变量存储全局变量@@error的值:

SELECT @err = @@error

@errmsg

varchar(200)

这个用来存储错误信息. 在 RAISERROR有他的编码示例.

@msg

varchar(200)

被打印出来的message字符串。

6.13 返回最后插入的标识值

在@@IDENTITY之外,SQL Server 2000介绍了另外两种方法来对Identiey进行检查.我认为 Scope_identity()比@@IDENTITY要好用一些.因为Scope_IDENTITY()返回插入到同一作用域中的 IDENTITY 列内的最后一个 IDENTITY 值.

Ø @@IDENTITY: 返回最后插入的标识值。

Ø Scope_identity():返回插入到同一作用域中的 IDENTITY 列内的最后一个 IDENTITY 值。一个作用域就是一个模块——存储过程、触发器、函数或批处理。因此,如果两个语句处于同一个存储过程、函数或批处理中,则它们位于相同的作用域中。

如下:

drop table jbt1

drop table jbt1hist

create table jbt1 (a int identity(1,1), b int not null)

create table jbt1hist (a int, b int not null, c int identity(222,1))

drop trigger jbt1ins

create trigger jbt1ins on jbt1 for insert as

insert into jbt1hist (a,b) select a,b from jbt1

insert into jbt1 (b) select 1

select @@identity -- gives identity of the table in the trigger (jbt1hist).

Select SCOPE_IDENTITY() -- gives identity of table we just inserted into (jbt1)

另一个就是IDENT_CURRENT, 返回为任何会话和任何作用域中的指定表最后生成的标识值。

SELECT IDENT_CURRENT('extractlist')

7.安全

数据是公司的重要资产. 在进行数据库或者应用程序设计的时候就应该对安全性进行设计. 数据仓库的安全性和有效的安全控制不只是开发后期所考虑的事情.在设计时,安全就要有高的优先级,尽管有时安全控制将会使设计非常复杂.

为了确保最大的安全性,应该应用下面的标准:

在混合验证或者是Windows身份验证中,应用程序不应该以SA来登录SQLServer,此外,所有的用户名和密码都不能够在应用程序中硬编码.

• 只给用户分配他应该得到的权限。

在混合或者是windows身体验证中为所有的用户使用一个共用的帐号是不理的,每个用户一定要有它自己的登录.

为了防止打破所有者链,所有的数据库对象的所有者都应该是DBO

8.事务处理

8.1 编写高效的事务处理代码

 批处理顺序的批理批中的每一个事务。在线处理系统(OLTP)一次处理一个事务。使用这两种方式都要有一个有一个行之有效的方法来对handles recovery

 避免使用不必要的大事务。

 在用户自定义事务中从来不要使用DDL语句(比如CREATE TABLE或者SELECT INTO),因为它将会在事务的执行过程中锁定Sysobjects 和sysindexes。一个例外就是数据库更改语句。

 最小化事务执行的时间

 在事务中以相同的顺序使用数据库对象来最小化死锁。

 总是对错误进行检查,如果有错误发生,回滚事务

例如:

IF @@TRANCOUNT = 0 BEGIN TRAN

IF @@TRANCOUNT > 1 ROLLBACK TRAN

8.2 批处理

在批处理文件中使用OSQL来调用存储过程和其它的SQL脚本.

如果服务器使用NT集成的验证,使用/E 选项来打开信任连接.而不能使用/U /P.

下面是一个在NT环境下使用批处理的例子,如果有任何错误发生,它将会重试2次.

set sp=LevelSalesTransactions >>%log%

set qry=osql /S%facsvr% /dstaging /E /b /n /I /w250 /H%_Job_Name% /Q "exec %sp% '%1', %Semaphore% " >> %log%

(%qry% || (sleep 60 & %qry% || (sleep 60 & %qry%))) >>%Log%

if errorlevel 1 set errmsg=***%_job_name% failed running %sp% & goto problem

:Finish

call FINISHED.cmd

goto done

:problem

call errhandl.cmd

:done

 

附录A:最常见的不应该出现的问题

 

数据库设计和开发标准描述我们在数据库系统的开发过程中应该怎么做,为了更好的强调这一点,下面的列表描述了哪些不应该做.

1. 创建没有主健的表

2. 创建没有clustered索引的表

3. 不使用事务来强制数据的一致性和完整性.

4. 使用了事务,但是即不检查是是否有错误发生,也不对有问题的事务进行回滚.或者是没有对有错误发生的事务进行后续的错误处理.

5. 在数据库中不强制父子(或者主外键)关系,忽略了参照完整性.

6. 没有对安装,备份,恢复和维护等的具体计划.

7. 在数据库系统开发后来实现安全性或者是根本就没有对安全性进行设计.

8. 在数据库模型设计完成之前进行开发,然后对数据库构架进行分修正.

9. 在主外键关系的相应列中数据类型不匹配.

10. 在数据库中有无关的冗余数据.

11. 不在存储过程中加上合理的注释。

12. 在写过代码之后再写设计文档或者根本就不写设计文档.

13. 不对设计和编码进行检查.

你可能感兴趣的:(SQL Server 数据库设计、命名、编码规范)