前言:
如今同学们对“c语言数据库建立”大体比较着重,朋友们都想要学习一些“c语言数据库建立”的相关知识。那么小编同时在网摘上汇集了一些有关“c语言数据库建立””的相关文章,希望各位老铁们能喜欢,你们一起来了解一下吧!范围和简介简介
本规范是针对关系型数据库ORACLE的相关特性,拟定的用于指导和规范相关开发过程的规范,其旨在通过该规范的约束和建议,使开发人员可以在他们所编写的代码中保持统一正确的风格,提供代码的可读性以及减少出现错误的几率。
范围
本规范仅适用于关系型数据库系统ORACLE以及其对应的PL/SQL脚本语言。
关键词
编程规范、数据库、ORACLE、PL/SQL
术语和定义
本规范采用以下的术语描述:
★ 规则:编程时强制必须遵守的原则。
★ 建议:编程时必须加以考虑的原则。
★ 说明:对此规则或建议进行必要的解释。
★ 示例:对此规则或建议从正、反两个方面给出。
数据库整体设计规范(必读)设计应用里面,多个数据库之间请不要通过DBLINK访问。请不要采用触发器。请不要使用视图和物化视图。请不要使用外键约束,如果数据存在外键关系,请在程序层面实现。请尽量不要使用 job,如果不得已必须使用,Job 的设计必须是可重复执行的。请尽量不要采用存储过程,业务逻辑请在程序层面实现。应用必须具有自动重连的机制。但是又要避免每执行一条 SQL 语句就检查一下 DB 的可用性。命名命名应使用富有意义的英文词汇,多个单词组成的,中间以下划线分割。命名只能使用英文字母,数字和下划线。命名避免使用 Oracle 保留字和系统关键字。命名长度以不超过 15 个字符为宜(避免超过 20)。命名全部采用小写,并且名称前后不能加引号。数据库对象设计规范表设计在设计时尽量包含两个日期字段:gmt_created(创建日期),gmt_modified(修改日期)且
非空, 对表的记录进行更新的时候,必须包含对 gmt_modified 字段的更新。
尽可能使用简单数据类型,不要使用类似数组或者嵌套表这种复杂类型。必须要有主键,且尽量不要使用有实际意义的字段做主键。需要 join 的字段,数据类型保持绝对一致。当表的字段数非常多时,可以将表分成两张表,一张作为条件查询表,一张作为详细内容表(主要是为了性能考虑)。当字段的类型为枚举型或布尔型时,建议使用 char(1)类型。命名同一个模块的表尽可能使用相同的前缀,表名尽可能表达含义,例如:
CRM_SAL_FUND_ITEM。
字段命名应尽可能使用表达实际含义的英文单词或缩写,不要使用类似“VALUE1”这种无意义的字段名。布尔值类型的字段命名为 is+描述。如 member 表上表示是否为 enabled 的会员的字段命名为 IsEnabled。字段类型
注意:能用数值类型就用数值类型,如:IP地址也可以使用数值类型来存放。
类型
规范
NUMBER(p,s)
固定精度数字类型
NUMBER
不固定精度数字类型,当不确定数字的精度时使用,PK 通常使用此类型
DATE
当仅需精确到秒时,选择 DATE 而不是 TIMESTAMP 类型
TIMESTAMP
扩展日期类型,不建议使用
VARCHAR2
变长字符串,最长 4000 个字节
CHAR
定长字符串,除非是CHAR(1),否则不要使用
CLOB
当超过 4000 字节时使用,但是要求这个字段必须单独创建到一张表中,然后有 PK 与主表关联。此类型应该尽量控制使用
字段注释标准字段注释由一组"@"开头的标签+空格+文本组成。MD_USER 表的部分字段为例:
Name
Type
Comments
PARTY_ID
VARCHAR2(20)
@desc 主键 ID
CORPORATION_ID
VARCHAR2(20)
@desc 用户所在公司 ID
@fk md_corporation.party_id
STATUS
VARCHAR2(20)
@desc 状态
@values disable|enable: 未激活状态|激活状态
IS_PRI_ACCOUNT
CHAR(1)
@desc 是否为主账号。后台生成UK 时使用
@values y|n:是帐号,非主帐号
@logic 一个公司内部,有且仅有一个主账号存在
注释标签说明
标签名
中文含义
必填
备注
@desc
字段中文描述
Yes
@fk
字段对应的外键字段
@values
取值范围说明。多个值以"|"分隔
如此字段的值由系统自动生成,可忽略不书写。
@sample
数据范本
对于复杂数据格式,最好给一个数据范本。
@formula
计算公式
写明该字段由哪些字段以何种公式计算得到。
@logic
数据逻辑
简要写明该字段的数据是在何种业务规则下,如何变化的。
@redu
标识此字段冗余
@depr
标识此字段已废弃
简要写明:废弃人 废弃日期 废弃原因
索引设计Bitmap 索引通常不适合我们的环境。索引根据实际 SQL,由 DBA 创建。不要创建带约束的索引,所有的约束效果都通过显示创建约束然后再 using index 一个已经创建好的普通索引来实现。命名<table_name>_<column_name>_ind,各部分以下划线(_)分割。多单词组成的 column name,取前几个单词首字母,加末单词组成 column_name。如: sample 表 member_id 上的索引:sample_mid_ind。约束设计主键最好是无意义的,由 Sequence 产生的 ID 字段,类型为 number,不建议使用组合主键。若要达到唯一性限制的效果,不要创建 unique index,必须显式创建普通索引和约束(pk 或 uk),即先创建一个以约束名命名的普通索引,然后创建一个约束,用 using index ...指定索引。当删除约束的时候,为了确保不影响到 index,最好加上 keep index 参数。主键的内容不能被修改。外键约束一般不在数据库上创建,只表达一个逻辑的概念,由程序控制。当万不得已必须使用外健的话,必须在外健列创建 INDEX。命名主键约束: _pk 结尾,<table_name>_pk;unique 约束:_uk 结尾,<table_name>_<column_name>_uk;check 约束: _ck 结尾,<table_name>_<column_name>_ck;外键约束: _fk 结尾,以 pri 连接本表与主表,<table_name>_pri_<table_name>_fk;SEQUENCE命名seq_<table_name>触发器命名<table_name>_A(After)B(Before)I(Insert)U(Update)D(Delete)_trg。若是用于同步的触发器以 sync 作为前缀:sync_<table_name>_trg。过程、函数、包命名过程以 proc_开头,函数以 func_开头,包以 pkg_开头。变量命名约定:本地变量以 v_为前缀,参数以 p_为前缀,可以带_I(输入),O(输出)、_IO(输入输出)表示参数的输入输出类型。书写规范大小写风格规则:数据库脚本中涉及到的各种保留字、关键字、操作符、数据类型、标识符、对象、字段、变量、常量等的大小写风格必须遵循以下规范:
大小写规范表
文字类型
大小写
示例
ORACLE保留字
大写
IDENTIFIED、INITIAL、LONG、MAXEXTENTS、RENAME、ROWNUM、SYSDATE、VARCHAR2
ORACLE关键字
大写
ANALYZE、AUTOEXTEND、COMPILE、DBA、EXTENT、INSTANCE、MAXTRANS、SAVEPOINT、TABLESPACE
ANSI SQL保留字
大写
ADD、BETWEEN、CREATE、DISTINCT、DROP、FROM、SESSION、SELECT、TABLE、UPDATE、VARCHAR
ANSI SQL关键字
大写
BEGIN、COMMIT、CONSTRAINT、COUNT、CURSOR、DECLARE、EXECUTE、PRIMARY、SUM、TRANSACTION
PL/SQL保留字
大写
ARRAY、BOOLEAN、DATABASE、DO、EXIT、RAISE、SAVEPOINT、RUN、WHILE、XOR
操作符
大写
ALL、AND、ANY、ESCAPE、EXISTS、OR、UNION
数据类型
大写
INT、NUMBER、DATE
系统数据库对象(如:视图、函数、存储过程、包等)
大写
USER$、DBA_JOBS、DECODE、SUBSTR、DBMS_OUTPUT、PUT_LINE、NO_DATA_FOUND
自定义数据库对象
大写
T_PUB_TABLE_NAME、F_PUB_GETID、P_KBS_GET_SERVICEINFO
字段名
大写
USER_NAME、STAFFNO
常量
大写
SECONDS_OF_DAY
变量
逻辑首字母大写
v_BeginDate、v_FaxNumber、c_StaffAuth
说明:
ORACLE的关键字和保留字请参见视图V$RESERVED_WORDS;上表中需要大写的关键字/保留字,可以录入UltraEdit的wordfile.txt,在使用UltraEdit开发时输入这些关键字保留字后会自动变为大写。缩进风格规则:脚本代码块采用缩进风格书写,保证代码清晰易读,风格一致。缩进格数统一为2个空格,但需要关键字右对齐或者参数/字段向上对齐的时候例外。
示例1:
CREATE SEQUENCE SQ_CSP_TABLENAME
MINVALUE 1
START WITH 1
INCREMENT BY 1;
示例2:
IF c_CursorName%ISOPEN THEN
CLOSE c_CursorName;
END IF;
规则:缩进必须使用空格键,不允许使用TAB键。规则:同一条语句占用多于一行时,每行的第一个关键字应当右对齐,但INSERT...SELECT语句例外。
示例1:
FETCH c_Cursor
INTO v_Field1, v_Field2, ...
↑
关键字右对齐
示例2:
SELECT FIELD1, FIELD2, ...
FROM T_CSP_TABLENAME
WHERE FIELD1 > 1
AND (FIELD2 < SYSDATE OR FIELD2 > SYSDATE + 3)
AND FIELD3 LIKE 'HUAWEI%'
GROUP BY FIELD1, FIELD2
ORDER BY FIELD1, FIELD2;
↑
关键字右对齐
规则:在INSERT...SELECT语句中,如果需要换行时,应使INSERT INTO部分的字段与SELECT部分的字段一一对应,以增强可读性。
示例:
错误的写法:
BEGIN
INSERT INTO T_DEST_TABLENAME (DESTFIELDNAME1, DESTFIELDNAME2,
DESTFIELDNAME3, DESTFIELDNAME4, DESTFIELDNAME5,
DESTFIELDNAME6, DESTFIELDNAME7)
SELECT SRCFIELDNAME1, SRCFIELDNAME2, SRCFIELDNAME3,
SRCFIELDNAME4, SRCFIELDNAME5, SRCFIELDNAME6,
SRCFIELDNAME7
FROM T_SRC_TABLENAME;
END;
正确的写法A:
BEGIN
INSERT INTO T_DEST_TABLE_NAME (DESTFIELDNAME1, DESTFIELDNAME2,
DESTFIELDNAME3, DESTFIELDNAME4, DESTFIELDNAME5,
DESTFIELDNAME6, DESTFIELDNAME7)
SELECT SRCFIELDNAME1, SRCFIELDNAME2,
SRCFIELDNAME3, SRCFIELDNAME4, SRCFIELDNAME5,
SRCFIELDNAME6, SRCFIELDNAME7
FROM T_SRC_TABLE_NAME;
END;
正确的写法B:
BEGIN
INSERT INTO T_DEST_TABLE_NAME (DESTFIELDNAME1, -- 注释
DESTFIELDNAME2, -- 注释
DESTFIELDNAME3, -- 注释
DESTFIELDNAME4, -- 注释
DESTFIELDNAME5, -- 注释
DESTFIELDNAME6, -- 注释
DESTFIELDNAME7) -- 注释
SELECT SRCFIELDNAME1, -- 注释
SRCFIELDNAME2, -- 注释
SRCFIELDNAME3, -- 注释
SRCFIELDNAME4, -- 注释
SRCFIELDNAME5, -- 注释
SRCFIELDNAME6, -- 注释
SRCFIELDNAME7 -- 注释
FROM T_SRC_TABLE_NAME;
END;
正确的写法C:
BEGIN
INSERT INTO T_DEST_TABLE_NAME (
DESTFIELDNAME1, -- 注释
DESTFIELDNAME2, -- 注释
DESTFIELDNAME3, -- 注释
DESTFIELDNAME4, -- 注释
DESTFIELDNAME5, -- 注释
DESTFIELDNAME6, -- 注释
DESTFIELDNAME7) -- 注释
SELECT SRCFIELDNAME1, -- 注释
SRCFIELDNAME2, -- 注释
SRCFIELDNAME3, -- 注释
SRCFIELDNAME4, -- 注释
SRCFIELDNAME5, -- 注释
SRCFIELDNAME6, -- 注释
SRCFIELDNAME7 -- 注释
FROM T_SRC_TABLE_NAME;
END;
说明:
在错误的写法中,虽然SELECT语句部分的SRCFIELDNAME1, SRCFIELDNAME2, SRCFIELDNAME3可以写在一行中,但由于INSERT INTO语句部分中DESTFIELDNAME1, DESTFIELDNAME2写在一行中,而DESTFIELDNAME3在下一行,因此SELECT语句中每行的字段应与INSERT INTO语句中的字段一一对应(如正确的写法A);INSERT INTO语句中的各个字段折行后,应缩进并与上一字段左对齐(如正确的写法B),或者与INTO关键字左对齐(如正确的写法C);SELECT语句中折行后的第一个字段名应缩进并与上一行的第一个字段名左对齐。规则:INSERT INTO语句中,如果需要对每个字段增加注释,应将每个字段单独列为一行,并在行尾增加注释。
示例1:
INSERT INTO T_DEST_TABLE_NAME (DESTFIELDNAME1, -- 注释1
DESTFIELDNAME2, -- 注释2
DESTFIELDNAME3) -- 注释3
VALUES (FieldValue1,
FieldValue2,
FieldValue3);
或者
INSERT INTO T_DEST_TABLE_NAME (
DESTFIELDNAME1, -- 注释1
DESTFIELDNAME2, -- 注释2
DESTFIELDNAME3) -- 注释3
VALUES (
FieldValue1,
FieldValue2,
FieldValue3);
或者
INSERT INTO T_DEST_TABLE_NAME
(
DESTFIELDNAME1, -- 注释1
DESTFIELDNAME2, -- 注释2
DESTFIELDNAME3 -- 注释3
)
VALUES
(
FieldValue1,
FieldValue2,
FieldValue3
);
其他说明:
在规范的代码模板中详细给出了各种情况下的范例写法,请编码过程参考;在数据库脚本的编码过程中,请严格按照代码模板进行书写;对于模板中未涉及的情况,应在保证符合上述规范原则,同时和代码模板中的风格保持一致的前提下,根据实际情况灵活处理。空格及换行规则:不允许把多个短语句写在一行中,即一行只写一条语句。
示例:
v_Variable1 := 1; v_Variable2 := 'abc';
应写成:
v_Variable1 := 1;
v_Variable2 := 'abc';
说明:两个赋值语句不能写在一行中,必须分两行写。
规则:相对独立的程序块之间、变量说明之后必须加空行。
示例:
v_Variable1 := 1;
IF v_BeginDate IS NULL THEN
v_BeginDate := SYSDATE - 15;
END IF;
应写成:
-- 初始化局部变量
v_Variable1 := 1;
-- 判断开始时间
IF v_BeginDate IS NULL THEN
v_BeginDate := SYSDATE - 15;
END IF;
说明:两个程序块在逻辑上相对独立,应用空行加以分隔,同时增加注释。
建议:建议对超过120字符的语句要分行书写,长表达式应在低优先级操作符处换行,操作符或关键字放在新行之首。划分出的新行应适当地缩进,使排版整齐、语句可读。是否分行应根据实际情况而定,原则是保证代码整齐、语句可读。
分行示例:
120字符
↓
…… (a * b * c * d) + (e * f) + ……
应写成:
…… (a * b * c * d)
+ (e * f) + ……
说明:
加法的优先级低于乘法,因此应在加号处换行;两组乘法虽然在逻辑上会先于加法执行,但显式加上括号使可读性更强。
不分行示例:
具体内容请参见代码模板“创建初始化数据模板”部分,有时初始化数据的语句不分行情况下可读性更强。
规则:调用函数或过程时,如果参数列表超过120字符,应根据逻辑内容进行换行,或者每个参数占用一行。
示例:
错误的写法:
120字符
↓
P_PROCEDURE(i_Param1, i_Param2, i_Param3, o_Param1, oParam2, ...);
正确的写法A:
P_PROCEDURE(i_Param1, i_Param2, i_Param3,
o_Param1, o_Param2, ...);
正确的写法B:
P_PROCEDURE(i_Param1, -- 注释
i_Param2, -- 注释
i_Param3, -- 注释
o_Param1, -- 注释
o_Param2, -- 注释
...) -- 注释
说明:
因为前三个为输入参数,后两个为输出参数,因此在第3、4参数之间换行比较清晰(如正确的写法A);有时为了增加注释,可以使每个参数皆占用一行(如正确的写法B);参数换行以后与上一行的第一个参数对齐。规则:双目运算符、操作符前后应以空格分隔,间隔符之后应以空格分隔。
示例:
v_DateVar:=TO_DATE('2001-01-01 01:30:00','YYYY-MM-DD HH24:MI:SS');
v_IntegerVar1:=v_IntegerVar2+v_IntegerVar3;
IF v_Number>0 THEN
应写成:
v_DateVar := TO_DATE('2001-01-01 01:30:00', 'YYYY-MM-DD HH24:MI:SS');
↑ ↑
赋值符前后加空格 逗号后面加空格
v_IntegerVar1 := v_IntegerVar2 + v_IntegerVar3;
↑ ↑
赋值符前后加空格 加号前后加空格
IF v_Number > 0 THEN
↑
比较符号前后加空格
建议:不同优先级的操作符混合使用时,建议使用括号进行隔离。
示例:
…… a * b + c ……
应写成:
…… (a * b) + c ……
说明:使用括号使代码的优先级更加清晰,而且可以避免犯错。
建议:SQL语句中存在不同优先级的关系运算符时,建议使用括号。
示例:
WHERE FIELD1 > 1 AND FIELD2 < SYSDATE OR FIELD3 > SYSDATE + 3
应写成:
WHERE (FIELD1 > 1 AND FIELD2 < SYSDATE) OR (FIELD3 > SYSDATE + 3)
说明:如果SQL语句的条件复杂,很容易出现问题,因此增加括号可以提高可阅读性。
创建表规则:脚本中的字段名缩进为2个空格。规则:脚本中字段名称、字段类型、DEFAULT关键字应左对齐,NULL/NOT NULL应右对齐(也可以认为就是NULL关键字左对齐)。
示例:
CREATE TABLE T_WF_TABLE_NAME
(
INTFIELD INT NOT NULL,
FLOATFIELD NUMBER(4, 2) DEFAULT 1.23 NOT NULL,
DATEFIELD DATE DEFAULT SYSDATE NULL,
VARCHAR2FIELD VARCHAR2(200) NULL
)
TABLESPACE SERVICE_MAIN_DAT;
说明:至于每行中的每个项目之间使用多少个空格没有明确要求,只要求从纵向上各个字段的名称、数据类型、缺省值、是否为空等对齐美观即可。
创建过程/函数/包规则:脚本中的参数缩进为2个空格。规则:脚本中的参数名称、数据类型、注释信息应左对齐,IN/OUT/IN OUT关键字左对齐右对齐皆可。
示例:
CREATE OR REPLACE PROCEDURE P_CSP_PROCEDURENAME
(
i_DateParam DATE, -- 注释
o_IntParam IN OUT INT, -- 注释
o_VarcharParam OUT VARCHAR2, -- 注释
o_RetCode OUT INT -- 注释
)
或者
CREATE OR REPLACE PROCEDURE P_CSP_PROCEDURENAME
(
i_DateParam DATE, -- 注释
o_IntParam IN OUT INT, -- 注释
o_VarcharParam OUT VARCHAR2, -- 注释
o_RetCode OUT INT -- 注释
)
说明:至于每行中的每个项目之间使用多少个空格没有明确要求,只要求从纵向上各个参数的名称、数据类型、IN/OUT关键字等对齐美观即可。
SQL 开发规范编码规范在代码中不允许出现任何 DDL 语句DDL 语句一律由 DBA 编写并统一执行写 SQL 的时侯一定要使用绑定变量对于极少数情况下不使用绑定变量提高性能,使用之前一定要和 DBA 沟通写 SQL 的时候一定要给每个字段指定表名做前缀比如 select a.id,a.name from test a; 好处是一来带来性能的提升,二来可以避免一些错误的发生。在 sqlmap 中的变量,要用#号,而不要用$符号如#appid#。因为$name$ 是字面意义的替换,这种形式会有 SQL 注入的漏洞,而 #name# 是带类型的替换,不存在 SQL 注入的风险。请不要写 select * 这样的代码,指定需要的字段名避免在 where 子句中对字段施加函数通常,不允许在字段上添加函数或者表达式,这样将导致索引失效,如:
错误的写法:
select *
from iw_account_log
where to_char(trans_dt, 'yyyy-mm-dd') = '2007-04-04';
select qty from product where p_id + 12 = 168;
正确的写法:
select *
from iw_account_log
where trans_dt >= to_date('2007-04-04', 'yyyy-mm-dd')
and trans_dt < to_date('2007-04-05', 'yyyy-mm-dd');
select qty from product where p_id = 168 - 12;
如果是业务要求的除外,但需要在编写时咨询DBA特别注意,当表连接时,用于连接的两个表的字段如果数据类型不一致,则必须在
一边加上类型转换的函数,错误的写法(a.id 是 number 类型,而 b.operator_number 是 char 类型):
select count
from adm_user a, adm_action_log b
where a.id = b.operator_number
and a.username = '小钗';
正确的写法:
select count
from adm_user a, adm_action_log b
where to_char(a.id) = b.operator_number
and a.username = '小钗';
select count
from adm_user a, adm_action_log b
where a.id = to_number(b.operator_number)
and a.username = '小钗';
上面两种写法哪个正确?遇到这种情况时必须咨询 DBA!
严格要求使用正确类型的变量,杜绝 oracle 做隐式类型转换的情况推荐在 sqlmap 的变量中指定变量的数据类型,如:select * from iw_user where iw_user_id = #userid:VARCHAR#其中,对于时间类型的字段,必须使用 TO_DATE 进行赋值(当前时间可直接用 sysdate 表示),不允许下列这些错误用法:
错误的写法(使用 date 类型的变量):
select *
from iw_account_log
where trans_account = #transaccount :varchar#
and trans_dt >= #dateBegin :date#
and trans_dt < #dateEnd :date#
错误的写法(将 to_date 函数和数字进行算术运算):
select *
from iw_account_log
where trans_account = #transaccount
:varchar#
and trans_dt >= to_date(#dateBegin :varchar#, 'yyyy-mm-dd hh24:mi:ss')
and trans_dt <
to_date(#dateBegin :varchar#, 'yyyy-mm-dd hh24:mi:ss') + 1
正确的写法:
select *
from iw_account_log
where trans_account = #transaccount
:varchar#
and trans_dt >= to_date(#dateBegin :varchar#, 'yyyy-mm-dd hh24:mi:ss')
and trans_dt < to_date(#dateEnd :varchar#, 'yyyy-mm-dd hh24:mi:ss') /* 或 trans_dt < sysdate */
对于变量数据类型错误导致 SQL 严重性能问题的,按严重的编码错误 Bug 处理!全模糊查询无法使用 INDEX,应当尽可能避免比如:select * from table where name like '%jacky%';外连接的写法不推荐使用 ANSI 连接,如 inner join、left join、right join、full outer join,而推荐使用(+)来表示外连接
不推荐的写法:
select a.*, b.goods_title
from iw_account_log a
left join beyond_trade_base b
on a.TRANS_OUT_ORDER_NO = b.trade_no
where a.trans_code = '6003'
and a.trans_account = #transacnt :varchar#
and a.trans_dt > to_date(.. .)
推荐的写法:
select a.*, b.goods_title
from iw_account_log a, beyond_trade_base b
where a.TRANS_OUT_ORDER_NO = b.trade_no(+)
and a.trans_code = '6003'
and a.trans_account = #transacnt :varchar#
and a.trans_dt > to_date(.. .)
表连接分页查询的使用包含排序逻辑的分页查询写法,必须是三层 select 嵌套:
错误的写法:
SELECT t1.*
FROM (SELECT t.*, ROWNUM rnum
FROM beyond_trade_base t
WHERE seller_account = :1
AND gmt_create >= TO_DATE(:2, 'yyyy-mm-dd')
AND gmt_create < TO_DATE(:3, 'yyyy-mm-dd')
ORDER BY gmt_create DESC) t1
WHERE rnum >= :4
AND rnum < :5
正确的写法:
SELECT t2.*
FROM (SELECT t1.*, ROWNUM rnum
FROM (SELECT t.*
FROM beyond_trade_base t
WHERE seller_account = :1
AND gmt_create >= TO_DATE(:2, 'yyyy-mm-dd')
AND gmt_create < TO_DATE(:3, 'yyyy-mm-dd')
ORDER BY gmt_create DESC) t1
WHERE ROWNUM <= :4) t2
WHERE rnum >= :5
不包含排序逻辑的分页查询写法,则是两层 select 嵌套,但对 rownum 的范围指定仍然必须在不同的查询层次指定:
错误的写法:
SELECT t1.*
FROM (SELECT t.*, ROWNUM rnum
FROM beyond_trade_base t
WHERE seller_account = :1
AND gmt_create >= TO_DATE(:2, 'yyyy-mm-dd')
AND gmt_create < TO_DATE(:3, 'yyyy-mm-dd')) t1
WHERE rnum >= :4
AND rnum <= :5
正确的写法:
SELECT t1.*
FROM (SELECT t.*, ROWNUM rnum
FROM beyond_trade_base t
WHERE seller_account = :1
AND gmt_create >= TO_DATE(:2, 'yyyy-mm-dd')
AND gmt_create < TO_DATE(:3, 'yyyy-mm-dd')
AND ROWNUM <= :4) t1
WHERE rnum >= :5
注意下面两种写法的逻辑含义是不同的:
按创建时间排序(倒序),然后再取前 10 条:
SELECT t2.*
FROM (SELECT t1.*, ROWNUM rnum
FROM (SELECT t.*
FROM sell_offer t
WHERE owner_member_id = :1
AND gmt_create >= TO_DATE(:2, 'yyyy-mm-dd')
AND gmt_create < TO_DATE(:3, 'yyyy-mm-dd')
ORDER BY gmt_create DESC) t1
WHERE ROWNUM <= 10) t2
WHERE rnum >= 1
随机取 10 条,然后在这 10 条中按照交易创建时间排序(倒序):
SELECT t1.*
FROM (SELECT t.*, ROWNUM rnum
FROM beyond_trade_base t
WHERE seller_account = :1
AND gmt_create >= TO_DATE(:2, 'yyyy-mm-dd')
AND gmt_create < TO_DATE(:3, 'yyyy-mm-dd')
AND ROWNUM <= 10
ORDER BY gmt_create DESC) t1
WHERE rnum >= 1
先连接后分页与先分页后连接性能较差:
SELECT t2.*
FROM (SELECT t1.*, ROWNUM rnum
FROM (SELECT a.*, b.receive_fee
FROM beyond_trade_base a, beyond_trade_process b
WHERE a.trade_no = b.trade_no
AND a.seller_account = :1
AND a.gmt_create >= TO_DATE(:2, 'yyyy-mm-dd')
AND a.gmt_create < TO_DATE(:3, 'yyyy-mm-dd')
ORDER BY a.gmt_create DESC) t1
WHERE ROWNUM <= :4) t2
WHERE rnum >= :5
性能较好:
SELECT /*+ ordered use_nl(a,b) */
a.*, b.receive_fee
FROM (SELECT t2.*
FROM (SELECT t1.*, ROWNUM rnum
FROM (SELECT t.*
FROM beyond_trade_base t
WHERE seller_account = :1
AND gmt_create >= TO_DATE(:2, 'yyyy-mm-dd')
AND gmt_create < TO_DATE(:3, 'yyyy-mm-dd')
ORDER BY gmt_create DESC) t1
WHERE ROWNUM <= :4) t2
WHERE rnum >= :5) a,
beyond_trade_process b
WHERE a.trade_no = b.trade_no
后面这种写法的适用情况:
where 子句中的查询条件都是针对 beyond_trade_base 表的(否则得到的结果将不相同)关联 beyond_trade_process 表时,用的是该表的主键或者唯一键字段(否则将改变结果集的条数)Hint 的使用sql 中的/*+ ordered use_nl(member offer)*/是 hint,用来确定 SQL 的执行计划,请在
DBA 确认后使用。
"<>"、"!="、"not in"、"exsits"和"not exists"的使用规范原则上一般禁止使用"<>"、"!="和"not in",而应该转换成相应的"="和"in"查询条件
错误的写法:
select a.id, a.subject, a.create_type
from product
where status <> 'new'
and owner_member_id = :1
正确的写法:
select a.id, a.subject, a.create_type
from product
where status in ('auditing',
'modified',
'service-delete',
'tbd',
'user-delete',
'wait-for-audit')
and owner_member_id = :1
错误的写法:
select a.id, a.subject, a.create_type
from product
where create_type not in ('new_order', 'vip_add')
and owner_member_id = :1
正确的写法:
select a.id,a.subject,a.create_type
from product
where create_type = 'cust_add'
and owner_member_id = :1
原则上不允许使用"exsits"和"not exists"查询,应转换成相应的"等连接"和外连接来查
询
错误的写法:
select a.id
from company a
where not exsits (select 1 from av_info_new b where a.id = b.company_id)
正确的写法:
select a.id
from company a, av_info_draft b
where a.id = b.company_id
and b.company_id is null
错误的写法:
select count
from company a
where exsits (select 1 from av_info_new b where a.id = b.company_id)
正确的写法:
select count from company a, av_info_draft b where a.id = b.company_id
注:在通过等连接替换 exsits 的时候有一点需要注意,只有在一对一的时候两者才能较容易替换,如果是一对多的关系,直接替换后两者的结果会出现不一致情况。因为 exsits 是实现是否存在,不管存在一条还是多条,而等连接时返回所关联上的所有数据。
如有特殊需要无法完成相应的转换,必须在 DBA 允许的情况下使用"<>"、"!="、"not in"、"exsits"和"not exists"其它编写规范对表的记录进行更新的时候,必须包含对 gmt_modified 字段的更新,并且不要使用 dynamic 标记,如:
错误的写法:
update BD_CONTACTINFO <dynamic prepend="set">
......
<isNotNull prepend="," property="gmtModified"> GMT_MODIFIED = #gmtModified:TIMESTAMP# </isNotNull>
</dynamic> where ID = #id#
正确的写法(当然,这里更推荐直接更新为 sysdate):
update BD_CONTACTINFO
set GMT_MODIFIED =
#gmtModified:TIMESTAMP# <dynamic>
......
</dynamic>
where ID = #id#
不允许在 where 后添加 1=1 这样的无用条件,where 可以写在 prepend 属性里,如:错误的写法:
select count from BD_CONTRACT t where 1=1
<dynamic>
......
</dynamic>
正确的写法:
select count from BD_CONTRACT
<dynamic prepend="where">
......
</dynamic>
对大表进行查询时,在 SQLMAP 中需要加上对空条件的判断语句,具体可在遇到时咨询 DBA,如:
性能上不保险的写法:
select count from iw_user usr
<dynamic prepend="where">
<isNotEmpty prepend="AND" property="userId">
usr.iw_user_id = #userId:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="email">
usr.email = #email:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="certType">
usr.cert_type = #certType:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="certNo">
usr.cert_no = #certNo:varchar#
</isNotEmpty>
</dynamic>
性能上较保险的写法(防止那些能保证查询性能的关键条件都为空):
select count from iw_user usr
<dynamic prepend="where">
<isNotEmpty prepend="AND" property="userId">
usr.iw_user_id = #userId:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="email">
usr.email = #email:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="certType">
usr.cert_type = #certType:varchar#
</isNotEmpty>
<isNotEmpty prepend="AND" property="certNo">
usr.cert_no = #certNo:varchar#
</isNotEmpty>
<isEmpty property="userId">
<isEmpty property="email">
<isEmpty property="certNo">
query not allowed
</isEmpty>
</isEmpty>
</isEmpty>
</dynamic>
另外,对查询表单的查询控制建议使用 web 层进行控制而不是客户端脚本
(JAVASCRIPT/VBSCRIPT)
聚合函数常见问题不要使用 count(1)代替 count(*)count(column_name)计算该列不为 NULL 的记录条数count(distinct column_name)计算该列不为 NULL 的不重复值数量count()函数不会返回 NULL,但 sum()函数可能返回 NULL,可以使用nvl(sum(qty),0)来避免返回 NULLNULL 的使用理解 NULL 的含义,是"不确定",而不是"空"查询时,使用 is null 或者 is not null更新时,使用等于号,如:update tablename set column_name = nullSTR2NUMLIST、STR2VARLIST 函数的使用:适用情况:使用唯一值(或者接近唯一值)批量取数据时编写规范:a 表必须放在 from list 的第一位,并且必须在 select 后加上下面的 hint
正确的写法:
select /+ ordered use_nl(a,b) */ b.
from TABLE(CAST(str2varlist(:1) as vartabletype)) a, beyond_trade_base b where a.column_value = b.trade_no;
格式规范注释说明本注释说明主要用于 PL/SQL 程序及其它 SQL 文件,其它可作参考;SQLPLUS 接受的注释有三种:这儿是注释
/* 这儿是注释 */
REM 这儿是注释
开始注释,类似JAVAK 中的开始注释,主要列出文件名,编写日期,版权说明,程序功能以及修改记录:
REM
REM $Header: filename, version, created date,auther
REM
REM Copyright
REM
REM
REM FUNCTION
REM function explanation
REM NOTES
REM
REM MODIFIED (yy/mm/dd)
REM who when - for what, recently goes first
块注释,如表注释,PROCEDURE 注释等,同JAVA:
/*
*This table is for TrustPass
*mainly store the information
*of TrustPass members
*/
注意: 在“/*”后应当另起一行,或与其后描述有间隔,否则在 SQLPLUS 中会有问题。
单行注释,如列注释:
login_id VARCHAR2(32) NOT NULL, -- 会员标
缩进
低级别语句在高级别语句后的,一般缩进 4 个空格:
DECLARE
v_MemberId VARCHAR2(32),
BEGIN
SELECT admin_member_id INTO v_MemberId
FROM company
WHERE id = 10;
DBMS_OUTPUT.PUT_LINE(v_MemberId);
END;
同一语句不同部分的缩进,如果为 sub statement,则通常为 2 个空格,如果与上一句某部分有密切联系的,则缩至与其对齐:
BEGIN -- sub statemen
FOR v_TmpRec IN (SELECT login_id,
gmt_created, -- here indented as column above
satus
FROM member
WHERE site = 'china'
AND country = 'cn') LOOP
NULL;
END LOOP;
END;
断行一行最长不能超过 80 字符同一语句不同字句之间逗号以后空格其他分割符前空格
SELECT offer_name
||','
||offer_count as offer_category,
id
FROM category
WHERE super_category_id_1 = 0;
常用语法变量声明
规则:代码中声明与表的字段相对应的变量时,应保证变量名和字段名相同。
说明:这样要求的目的是增强可读性。
示例:
DECLARE
v_DateField T_TABLENAME.DATEFIELD%TYPE;
BEGIN
SELECT DATEFIELD INTO v_DateField
FROM T_TABLENAME
WHERE ROWNUM = 1;
END;
规则:代码中声明与表的字段相对应的变量时,对类型的定义需要使用%TYPE方式。
说明:这样表结构的变动不会影响存储过程,避免表结构变更后出现变量长度或类型不一致的问题。
返回值
规则:函数的返回值定义应遵从下列规范:成功出口返回0,失败出口返回大于0整数。过程中定义的用于返回错误码的OUTPUT参数,其定义应遵从下列规范:成功出口返回0,失败出口返回非0整数。
符号*
规则:脚本中不允许出现“*”的用法,必须用实际的字段名代替,INSERT语句必须指定要插入的字段名。
示例:
1、游标定义
错误用法:
CURSOR c_CursorName IS
SELECT * FROM TABLENAME ...
正确用法:
CURSOR c_CursorName IS
SELECT FIELD1, FIELD2, ... FROM TABLENAME ...
2、INSERT 语句
错误用法:
INSERT INTO TABLENAME VALUES ...
正确用法:
INSERT INTO TABLENAME (FIELD1, FIELD2, ...) VALUES ...
包的使用
建议:ORACLE的包类似C++中的Class,有包头和包体两部分组成;包能够把相关的功能封装性在一个包中,包里面里可以有函数和过程。PACKAGE与PROCEDURE和FUNCTION的优点是封装性比较好;在开发过程中,可以把功能点紧密相关的PROCEDURE、FUNCTION封装在一个包里面。在对包体进行编译时,调用该包的存储过程不会失效。
绑定变量
规则:使用绑定变量的SQL语句,能使用绑定变量方式的业务逻辑下不允许使用绑定常量实现。
说明:在存储过程拼SQL语句作为动态SQL执行时,尤其需要注意。
示例:
错误用法:
v_SQL := 'DELETE FROM TABLENAME WHERE FIELD1 > SYSDATE AND FIELD2 = 102';
EXECUTE IMMEDIATE v_SQL;
正确用法:
v_ID := 102;
v_SQL := 'DELETE FROM TABLENAME WHERE FIELD1 > :1 AND FIELD2 = :2';
EXECUTE IMMEDIATE v_SQL USING SYSDATE, v_ID;
规则:为提升性能,减少循环的开销,可以使用批量绑定。
说明:下面示例中的UPDATE语句可以一次将多条记录进行更新,而避免了通过使用循环同一条SQL执行多次。
示例:
CREATE OR REPLACE PROCEDURE P_CSP_TEST
/*
Description : 根据输入的多个工单流水号,更新对应的SERVICECLASSID值
Author : 姓名 工号
Date : YYYY-MM-DD
Version : 版本
Caller : 调用者
Callee : 被调用者
Comments : 注释信息
History :
1. Date : 修改日期,格式为YYYY-MM-DD
Author : 修改人姓名与工号
Modification : 修改说明
2. ......
*/
(
i_SerialNos VARCHAR2, -- 一次输入多个工单流水号以'~'分隔'
i_ServiceClassID VARCHAR2,
o_Ret OUT VARCHAR2 -- 成功返回0,失败返回1
)
AS
TYPE typ_StrArray IS TABLE OF VARCHAR2(500) INDEX BY BINARY_INTEGER;
v_ArrSerial typ_StrArray;
BEGIN
FOR i IN 1..9999 LOOP
-- 把输入参数的值取出存放在数组v_ArrSerial中
EXIT WHEN GetParamStr(i_SerialNos, '~', i, v_ArrSerial(i)) <> 0;
END LOOP;
-- 根据数组中的工单流水号批量更新
FORALL i IN 1..v_ArrSerial.COUNT
UPDATE T_WF_SERVICEINFO t
SET t.SERVICECLASSID = i_ServiceClassID
WHERE SerialNo = v_ArrSerial(i);
COMMIT;
o_Ret := 0;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
o_Ret := 1;
END;
异常处理
规则:函数/过程中应该有异常处理的代码,除非需要将任何可能的异常都向上抛出。
说明:不论代码逻辑是否简单,只要有可能会抛出异常,函数/过程块就应该包括异常处理代码。详见代码模板。
规则:如果需要的话,可以在异常处理部分将异常继续向上抛出给调用者。
说明:如果有些异常是预料可能产生,而且脚本需要根据是否有异常来做不同的逻辑处理,这种情况可以在异常处理部分将该异常进行处理,但可能有些意料之外的异常,需要继续向上抛出,便于调用者了解脚本执行是否成功,以便于调用者作下一步的处理如写错误日志等操作。
规则:如果需要自定义异常,必须在异常处理块中对其进行处理。
说明:如果自定义了异常,却没有相应的代码进行处理,那么就应该去掉该异常的定义。
规则:在对容错性要求比较高的情况下,对异常块中的代码还需要判断是否可能触发异常,必要的时候应使用嵌套的异常。
说明:在异常处理部分中的写日志之前,如果有事务,一定要先ROLLBACK;异常块中写日志、向表中插入一条初始化记录等语句也可能失败,对这些代码必要时也应该增加异常保护,即使用嵌套的异常处理。
事务控制
规则:在任何出口之前,只要存在事务未结束,必须提交或者回滚,除非有特殊设计考虑。
说明:存储过程在每个出口前,如果启动了事务必须结束所有事务,以提交(COMMIT)或回滚(ROLLBACK)来结束事务,否则可能会导致表锁等严重问题;如果在存储过程中,没有启动事务,就不必执行COMMIT或ROLLBACK,否则多余的ROLLBACK或COMMIT操作将增加数据库的额外开销。
规则:ORACLE的事务是串行的,嵌套调用中内层存储过程的提交会导致外层的存储过程事务被一并提交;诸如生成流水号这类存储过程应使用独立事务,否则在产生流水号时就将之前的业务逻辑操作提交,而后续的操作不能保证在一个事务中。例如现有系统中一个工单循环派给多个部门时,循环体内产生流水号,如最后一个派单失败时回滚操作根本就不能回撤所有操作,导致事务一致性被破坏。
建议:尽量分解大事务,事务的大小应视系统的性能和应用的具体情况而定,过多过小的事务造成重做日志同步的等待,比如要往一个表插入100万条记录,如果每条记录提交一次,则事务太小,可以做一个计数器,设置1万或事务5万条作一次提交。
游标使用
规则:原则上避免使用游标,尤其是动态游标。
说明:
游标的效率较低,如果在代码中可以通过不同的条件分支实现的逻辑,尽量不要使用游标。动态游标通常都可以转化为若干个静态游标,因此除非必要,尽量使用静态游标代替动态游标。
建议:推荐使用CURSOR FOR LOOP语句,可以隐式打开关闭游标;否则,显式打开游标,就必须在任何出口之前显式关闭游标。下面的示例通过 FOR IN LOOP取数据,不需要显示打开和关闭游标。
示例:
DECALRE
CURSOR c_Dept IS SELECT DEPTNO, DNAME FROM DEPT ORDER BY DEPTNO;
v_Tot_Salary EMP.SALARY%TYPE;
BEGIN
FOR r_Dept IN c_Dept LOOP
DBMS_OUTPUT.PUT_LINE('Department:' || r_Dept.DEPTNO);
v_Tot_Salary := 0;
END LOOP;
END;
规则:如果存储过程返回游标类型参数,必须在任何出口前打开游标。
说明:如果存储过程有游标类型的输出参数,而在某个分支中未打开,其调用者使用该游标获取数据时会报错。
示例:
CREATE OR REPLACE PROCEDURE P_TMP_TESTSYSCURSOR(
o_Cur OUT SYS_REFCURSOR -- 注释
)
AS
BEGIN
IF v_Flag > 0 THEN
OPEN o_Cur FOR SELECT FIELD1 FROM T_WF_TABLE;
RETURN;
ELSE
RETURN;
END IF;
END;
应写成:
CREATE OR REPLACE PROCEDURE P_TMP_TESTSYSCURSOR(
o_Cur OUT SYS_REFCURSOR -- 注释
)
AS
BEGIN
IF v_Flag > 0 THEN
OPEN o_Cur FOR SELECT 0 AS RET, FIELD1 AS MSG FROM T_WF_TABLE;
RETURN;
ELSE
OPEN o_Cur FOR SELECT -1 AS RET, ’ErrorMsg’ AS MSG FROM DUAL;
RETURN;
END IF;
END;
代码规模
建议:建议每个存储过程和函数的规模不宜超过500行。
说明:存储过程和函数的逻辑不宜太过复杂,应当注意对于同样的计算,存储过程执行效率远低于其他应用程序,所以要避免将大量业务逻辑都放到存储过程实现。
SQL建议与约束建议
建议:在开发过程中,在SQL语句中,尽量不要指定索引。
说明:指定索引后,在以后的优化过程中,无法通过创建和优化索引,使对应的SQL语句用到其他更好的索引,对指定的索引删除、重建改名后,可能导致该SQL语句用不到索引。
建议:在不使用DISTINCT、UNION、ORDER BY、GROUP BY情况下,也能实现业务功能的情况,一定不要使用这些功能。使用这些功能会导致对应的SQL语句排序,增加系统的开销。
示例:
错误的用法:
SELECT COUNT(*)
FROM (
SELECT SERIALNO
FROM T_PUB_COMMONINFO
WHERE A.PARTID >= '0127'
AND A.PARTID <= '1227'
ORDER BY ACCEPTBEGINTIME DESC -- 没有用的ORDER BY
)
建议:创建组合索引时,要注意组合索引的顺序和字段的选择性,把经常出现在WHERE条件中同时选择性比较好的字段放在复合索引的第一个位置。
说明:SQL语句在使用复合索引时,与该复合索引字段的组合顺序有关,当索引的第一个字段出现在WHERE条件中,这时候对该表的查询能够用到该索引,当该索引的第一个字段没有出现在SQL语句的查询条件中,但该索引的其他的字段出现在WHERE条件中时,在基于规则的优化模式中,SQL引擎会用全表扫描方式查询,在基于成本的优化模式中的(First Rows)方式查询,SQL引擎会对该索引全索引扫描方式查询。
示例:
错误的用法:
ALTER TABLE T_WF_SERVICEINFOHIS
ADD CONSTRAINT PK_WF_SERVICEINFOHIS PRIMARY KEY (MONTHDAY, SERIALNO, SERVICECLASSID);
说明:在业务逻辑的查询中,许多时候之间按SERIALNO, SERVICECLASSID查时,在基于规则的优化模式中,用不到索引。
正确的用法:
ALTER TABLE T_WF_SERVICEINFOHIS
ADD CONSTRAINT PK_WF_SERVICEINFOHIS PRIMARY KEY (SERIALNO, SERVICECLASSID, MONTHDAY);
建议:创建表时数据和索引建议在不同的表空间。
建议:在WHERE条件表达式中,尽可能避免在要使用到索引的字段上使用函数,如果要使用函数建议创建相应的函数索引。
示例:
错误用法:
SELECT FIELD
FROM TABLENAME
WHERE SUBSTRB(FIELD, 1, 4) = '5378'
正确用法:
SELECT FIELD
FROM TABLENAME
WHERE FIELD LIKE '5378%'
建议:当查询条件选择性很低时使用索引反而降低效率,这种情况下,应该用特殊的方法屏蔽该索引,如果字段为数值型的就在表达式的字段名后+ 0,为字符型的就并上空串。
示例:
SELECT NUM_FIELD
FROM TABLENAME
WHERE NUM_FIELD + 0 > 30
SELECT STRING_FIELD
FROM TABLENAME
WHERE STRING_FIELD || '' = 'EXAMPLE'
建议:在SQL语句中,尽可能使用变量绑定,少用常量绑定。
建议:如果业务逻辑允许的情况下,尽量用UNION ALL代替UNION,用UNION ALL代替OR。
示例:
错误的用法:
SELECT SERIALNO
FROM T_WF_DISPOSALSTATUSHIS
WHERE (ACCEPTPHONE = :B4 OR CALLERNO = :B3 OR USERPHONE1 = :B2)
AND ACCEPTTIME > SYSDATE - :B1
正确的用法:
SELECT SERIALNO
FROM T_WF_DISPOSALSTATUSHIS
WHERE (ACCEPTPHONE = :B4)
AND ACCEPTTIME > SYSDATE - :B1
UNION ALL
SELECT SERIALNO
FROM T_WF_DISPOSALSTATUSHIS
WHERE (CALLERNO = :B3)
AND ACCEPTTIME > SYSDATE - :B1
UNION ALL
SELECT SERIALNO
FROM T_WF_DISPOSALSTATUSHIS A
WHERE (USERPHONE1 = :B2)
AND ACCEPTTIME > SYSDATE - :B1
建议:如果要对整个表或分区的数据删除,建议使用TRUNCATE替代DELETE。
建议:为了提高系统的并发性,尽可能的使事务的时间缩短。
建议:创建分区表的索引时,必须创建本地(LOCAL)索引。
说明:如果创建的是全局索引,在对分区表的某个分区TRUNCATE时,导致该索引失效;需要对该全局索引进行重新编译。
建议:避免通过DUAL表赋值。
说明:过多的对DUAL表的访问,导致调用该表的等待时间事件比较长。比如取系统时间之类的操作,往一个表插入记录等。
示例:
错误的用法:
SELECT SYSDATE INTO v_Date
FROM DUAL
正确的用法:
v_Date := SYSDATE
错误的用法:
INSERT INTO TABLENAME(FIELD1, FIELD2, FIELD3)
SELECT '2', SYSDATE, SUSBTR(v_Name, 1, 30)
FROM DUAL;
正确的用法:
INSERT INTO TABLENAME(FIELD1, FIELD2, FIELD3)
VALUES('2', SYSDATE, SUSBTR(v_Name, 1, 30));
禁止
规则:
严禁用系统表空间(SYSTEM/SYSAUX/USER)作为用户默认表空间;严禁在系统表空间(SYSTEM/SYSAUX/USER)上创建用户数据库对象;严禁在SYSTEM/SYS等系统用户下,创建用户数据库对象。
规则:SQL语句的WHERE子句中应尽可能将字段放在等式左边,将计算操作放在等式的右边,除非是要屏蔽该字段的的索引,否则禁止字段参与表达式运算。
说明:任何对字段的操作都将造成此字段上的索引被屏蔽,导致全表扫描,这里所谓的操作包括数据库函数、计算表达式等等。
示例:
错误的用法:
SELECT SOME_FIELD
FROM TABLENAME
WHERE NUM_FIELD / 30 < 1000
正确的用法:
SELECT SOME_FIELD
FROM TABLENAME
WHERE NUM_FIELD < 1000 * 30
错误的用法:
SELECT SOME_FIELD
FROM TABLENAME
WHERE TO_CHAR(LOGDATE, 'YYYYMMDD') = '19991201'
正确的用法:
SELECT SOME_FIELD
FROM TABLENAME
WHERE LOGDATE >= TO_DATE('19991201', 'YYYYMMDD')
AND LOGDATE < TO_DATE('19991202', 'YYYYMMDD')
规则:SQL语句的WHERE子句中每个条件的操作符两边类型应相同,禁止潜在的数据类型转换。
说明:潜在的字段数据类型转换将造成索引被屏蔽,导致全表扫描。例如将字符型数据与数值型数据比较,ORACLE会自动将字符类型字段用TO_NUMBER函数进行转换。
示例:
错误的用法:
表TABLENAME中的列STRING_FIELD是字符型(VARCHAR),则以下语句存在类型转换:
SELECT SOMEFIELD
FROM TABLENAME
WHERE STRING_FIELD > 10
正确的用法:
SELECT SOMEFIELD
FROM TABLENAME
WHERE STRING_FIELD > '10'
规则:SQL语句的WHERE子句中避免使用IN操作,严禁使用NOT IN操作。
说明:在SQL语句中,能用表连接尽量使用表连接,不能使用表连接则使用EXISTS,严禁使用IN。
示例:
错误的用法:
SELECT SOME_FIELD
FROM TABLE1
WHERE FIELD1 IN (
SELECT FIELD2
FROM TABLE2
)
正确的用法:
SELECT T1.SOME_FIELD
FROM TABLE1 T1, TABLE2 T2
WHERE T1.FIELD1 = T2.FIELD2
错误的用法:
SELECT SOME_FIELD
FROM TABLE1
WHERE FIELD1 NOT IN (
SELECT FIELD2
FROM TABLE2
)
正确的用法:
SELECT SOME_FIELD
FROM TABLE1 T1
WHERE NOT EXISTS (
SELECT 1
FROM TABLE2 T2
WHERE T2.FIELD2 = T1.FIELD1
)
规则:禁止对VARCHAR(2000)之类的大字段值进行ORDER BY、DISTINCT、GROUP BY、UNION之类的操作。
说明:此类操作将消耗大量的CPU和内存资源。
规则:禁止在没有事务的存储过程和代码中,随意使用COMMIT和ROLLBACK。
说明:过多多余的ROLLBACK和COMMIT容易引起数据库的同步日志等待事件,对系统的性能有影响,下面语句中的COMMIT和ROLLBACK就是多余的。
示例:
错误的用法:
CREATE OR REPLACE PROCEDURE P_MS_QUERYBYSERIALNO
(
i_SerialNo VARCHAR2, -- 工单流水号
rCursor OUT PACK_SERVICE.t_RetDataSet -- 返回结果集
)
AS
BEGIN
OPEN rCursor
FOR SELECT b.STAFFNO AS STAFFNO,
DECODE(b.COMMITRESULT, 0, '成功', '失败') AS COMMITRESULT
FROM T_MS_SENDLOGHIS a, T_MS_INTERFACECALLED b
WHERE b.SERIALNO = i_SerialNo;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END;
正确的用法:
CREATE OR REPLACE PROCEDURE P_MS_QUERYBYSERIALNO
(
i_SerialNo VARCHAR2, -- 工单流水号
rCursor OUT PACK_SERVICE.t_RetDataSet -- 返回结果集
)
IS
BEGIN
OPEN rCursor
FOR SELECT b.STAFFNO AS STAFFNO,
DECODE(b.COMMITRESULT, 0, '成功', '失败') AS COMMITRESULT
FROM T_MS_INTERFACECALLED b
WHERE b.SERIALNO = i_SerialNo;
EXCEPTION
WHEN OTHERS THEN
OPEN rCursor
FOR SELECT '成功' AS STAFFNO,
'成功' AS COMMITRESULT
FROM DUAL
WHERE 1 = 2;
END;
规则:禁止利用SQL语句做一些业务逻辑的判断或操作。
示例:
错误的用法:
SELECT STAFFNO, STAFFNAME
FROM T_PUB_STAFF
WHERE (i_StaffNo IS NULL OR STAFFNO = i_StaffNo)
AND (i_StaffName IS NULL OR STAFFNAME LIKE '%' || i_StaffName || '%')
错误分析:上面的SQL语句中,利用SQL引擎对变量的值进行判断,导致在使用过程中,对该表进行全表扫描。
正确的用法:通过代码中对变量的值进行判断然后决定执行对应的SQL语句。
规则:禁止在生产系统,直接用PL/SQL Developer或TOAD之类的工具中直接调试存储过程。
规则:禁止在PL/SQL Developer中执行SELECT * FROM TABLE_NAME FOR UPDATE,然后点击锁图标方式,再编辑数据的方式进行更新数据库中的数据。
开发工具UltraEdit-32
全称:UltraEdit-32 Professional Text/HEX Editor
公司:IDM Computer Solutions, Inc.
网址:
简介:UltraEdit是目前比较流行的文本编辑器,它可以将文本中的字符串按自己所期望的字体及格式显示出来,对显示代码中关键字、字符串、系统函数等尤为有用,并且可以在编辑过程中自动按照自己所定义关键字列表(WORDFILE.TXT文件)校正大小写。
PL/SQL Developer
全称:PL/SQL Developer
公司:Allround Automations
网址:
简介:PL/SQL Developer是能支持断点单步调试ORACLE脚本的工具,通过它可以方便的查看修改ORACLE的数据库对象及其属性、执行各种SQL语句,调试测试存储过程函数等,是开发ORACLE脚本和现场解决ORACLE脚本问题的好工具。
TOAD
全称:TOOLS FOR ORACLE APPLICATION DEVELOPER
公司:Quest Software
网址:
简介:TOAD能支持断点单步调试ORACLE脚本,同时还有较强的DBA管理功能。
PowerDesigner
全称:PowerDesigner
公司:Sybase
网址:
简介:PowerDesigner 是一个独具特色的建模工具集,它融合了以下几种标准建模技术:使用 UML 的应用程序建模、业务流程建模和传统数据库建模。我们主要利用该工具完成数据库建模,PowerDesigner能以ER图的方式体现各实体之间的关系,尤其适用于项目设计阶段的表结构设计。