对 SQL编写规范 的一些认识

来源:互联网 发布:p图软件 编辑:程序博客网 时间:2024/05/22 04:28
1、必须使用表的别名(Alias)

当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误.

这在多表查询的时候是可以理解的,但是单表也需要用别名吗 ?

2、用EXISTS替代IN、用NOT EXISTS替代NOT IN

在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下, 使用EXISTS(或NOT EXISTS)通常将提高查询的效率. 在子查询中,NOT IN子句将执行一个内部的排序和合并. 无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS.

例子:

(低效)

SELECT *

  FROM BO_PROJECT A

 WHERE A.PROJECT_CREATOR IN

       (SELECT B.USERID

          FROM V_USER_ORG B

         WHERE B.ORGANIZATIONNAME = '核心网组')

(高效)

SELECT *

  FROM BO_PROJECT A

 WHERE EXISTS (SELECT 1

          FROM V_USER_ORG B

         WHERE B.ORGANIZATIONNAME = '核心网组'

           AND B.USERID = A.PROJECT_CREATOR)

3、用UNION ALL 替换UNION ( 如果有可能的话)

当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并, 然后在输出最终结果前进行排序. 如果用UNION ALL替代UNION, 这样排序就不是必要了. 效率就会因此得到提高. 需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录. 因此各位还是要从业务需求分析使用UNION ALL的可行性. UNION 将对结果集合排序,这个操作会使用到SORT_AREA_SIZE这块内存. 对于这块内存的优化也是相当重要的. 下面的SQL可以用来查询排序的消耗量

低效: 

SELECT '的可研委托' TITLE,

       '可研委托' TYPE,

       'KYWT' TYPE_CODE,

       A.CREATEDATE,

       A.EDITOR,

       A.WORKFLOW_STATE,

       A.BO_PROJECT_ID,

       A.BO_PLAN_CONSIGN_ID APPID

  FROM BO_PLAN_CONSIGN A

UNION

SELECT '的可行性研究报告' TITLE,

       '可研交付' TYPE,

       'KYJF' TYPE_CODE,

       A.CREATEDATE,

       A.EDITOR,

       A.WORKFLOW_STATE,

       A.BO_PROJECT_ID,

       A.BO_BLAN_DELIVERY_ID APPID

  FROM BO_BLAN_DELIVERY A

高效: 

SELECT '的可研委托' TITLE,

       '可研委托' TYPE,

       'KYWT' TYPE_CODE,

       A.CREATEDATE,

       A.EDITOR,

       A.WORKFLOW_STATE,

       A.BO_PROJECT_ID,

       A.BO_PLAN_CONSIGN_ID APPID

  FROM BO_PLAN_CONSIGN A

UNION ALL

SELECT '的可行性研究报告' TITLE,

       '可研交付' TYPE,

       'KYJF' TYPE_CODE,

       A.CREATEDATE,

       A.EDITOR,

       A.WORKFLOW_STATE,

       A.BO_PROJECT_ID,

       A.BO_BLAN_DELIVERY_ID APPID

  FROM BO_BLAN_DELIVERY A

4、用EXISTS替换DISTINCT

当提交一个包含一对多表信息的查询时,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXIST替换, EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果. 例子:

(低效): 

SELECT DISTINCT U.FULLNAME

  FROM BO_PROJ_YEAR_INFO A, V_USER U

 WHERE A.PLAN_MANAGER = U.USERID

   AND A.BO_INVEST_PLAN_ID = '7cf394ba-9378-47db-871f-18fe14884036' 

(高效): 

SELECT U.FULLNAME

  FROM V_USER U

 WHERE EXISTS

 (SELECT 1

          FROM BO_PROJ_YEAR_INFO A

         WHERE A.PLAN_MANAGER = U.USERID

           AND A.BO_INVEST_PLAN_ID = '7cf394ba-9378-47db-871f-18fe14884036')

5sql语句用大写的;因为oracle总是先解析sql语句,把小写的字母转换成大写的再执行

6、优化GROUP BY

提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉.下面两个查询返回相同结果但第二个明显就快了许多.

低效: 

SELECT A.STATUS, COUNT(1)

  FROM BPM_WORKLIST A

 GROUP BY A.STATUS

HAVING A.STATUS IN ('CONFIRMED''NEW''CANCELLED')

高效: 

SELECT A.STATUS, COUNT(1)

  FROM BPM_WORKLIST A

 WHERE A.STATUS IN ('CONFIRMED''NEW''CANCELLED')

 GROUP BY A.STATUS

7、基于最小的结果集进行树形查询,然后再关联其他表

提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉.下面两个查询返回相同结果但第二个明显就快了许多.

低效: 

SELECT LEVEL, A.BO_PROJECT_ID, A.PROJECT_NAME, B.CE_FIXEDNUM

  FROM BO_PROJECT A, V_MIS_PROJECT B

 WHERE A.BO_PROJECT_ID = B.BO_PROJECT_ID(+)

 START WITH A.MANAGE_ATTRIBUTE = '2'

        AND A.SUBMITTED_DEPT = '43009'

CONNECT BY PRIOR A.BO_PROJECT_ID = A.PARENT_PROJECT

高效: 

SELECT P.TREELEVEL, P.BO_PROJECT_ID, P.PROJECT_NAME, B.CE_FIXEDNUM

  FROM (SELECT LEVEL TREELEVEL, A.BO_PROJECT_ID, A.PROJECT_NAME

          FROM BO_PROJECT A

         START WITH A.MANAGE_ATTRIBUTE = '2'

                AND A.SUBMITTED_DEPT = '43009'

        CONNECT BY PRIOR A.BO_PROJECT_ID = A.PARENT_PROJECT) P,

       V_MIS_PROJECT B

 WHERE P.BO_PROJECT_ID = B.BO_PROJECT_ID(+)

8SQL语句禁止查询参数直接拼写、需要用"?"参数化查询,防止SQL注入

低效:

DataAccessUtil dbUtil = new DataAccessUtil();

String sql = "SELECT * FROM V_USER A WHERE A.USERID=’" id ”’”;

List recordList = dbUtil.getDataBySQL(sql);

高效:

String sql = "SELECT * FROM V_USER A WHERE A.USERID=?";

DataAccessUtil dbUtil = new DataAccessUtil();

dbUtil.addParamString(1, id);

List recordList = dbUtil.getDataBySQL(sql);

二、SQL优化原则

1SELECT子句中避免使用 ‘ *

ORACLE在解析的过程中, 会将'*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间

2WHERE子句中的连接顺序

ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾.

3、用IN来替换OR  

这是一条简单易记的规则,但是实际的执行效果还须检验,在ORACLE8i下,两者的执行路径似乎是相同的. 

(低效) 

SELECT *

  FROM BO_PROJECT A

 WHERE A.PROJECT_CODE = '10402984'

    OR A.PROJECT_CODE = '10301993'

    OR A.PROJECT_CODE = '10102894' 

(高效)

SELECT *

  FROM BO_PROJECT A

 WHERE A.PROJECT_CODE IN ('10402984''10301993''10102894')

4、减少对表的查询

在含有子查询的SQL语句中,要特别注意减少对表的查询.例子:

(低效):

UPDATE BO_PROJECT A

   SET A.TOTAL_INVEST = (SELECT B.TOTAL_INVEST

                           FROM BO_PROJECT_TEMP B

                          WHERE B.PROJECT_NAME = A.PROJECT_NAME),

       A.ABC_CLASS    = (SELECT B.ABC_CLASS

                           FROM BO_PROJECT_TEMP B

                          WHERE B.PROJECT_NAME = A.PROJECT_NAME)

(高效):

UPDATE BO_PROJECT A

   SET (A.TOTAL_INVEST, A.ABC_CLASS) = (SELECT B.TOTAL_INVEST, B.ABC_CLASS

                                          FROM BO_PROJECT_TEMP B

                                         WHERE B.PROJECT_NAME =

                                               A.PROJECT_NAME)

5、通过内部函数提高SQL效率

复杂的SQL往往牺牲了执行效率. 能够掌握上面的运用函数解决问题的方法在实际工作中是非常有意义的,学会用分析函数

6、用索引提高效率

索引是表的一个概念部分,用来提高检索数据的效率,ORACLE使用了一个复杂的自平衡B-tree结构. 通常,通过索引查询数据比全表扫描要快. 当ORACLE找出执行查询和Update语句的最佳路径时, ORACLE优化器将使用索引. 同样在联结多个表时使用索引也可以提高效率. 另一个使用索引的好处是,它提供了主键(primary key)的唯一性验证.。那些LONG或LONG RAW数据类型, 你可以索引几乎所有的列. 通常, 在大型表中使用索引特别有效. 当然,你也会发现, 在扫描小表时,使用索引同样能提高效率. 虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价. 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修改. 这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O . 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢.。定期的重构索引是有必要的.:

ALTER  INDEX <INDEXNAME> REBUILD <TABLESPACENAME>

7、避免在索引列上使用NOT

通常我们要避免在索引列上使用NOT, NOT会产生在和在索引列上使用函数相同的影响. 当ORACLE”遇到”NOT,他就会停止使用索引转而执行全表扫描.

8、避免在索引列上使用计算

WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描. 

举例: 

(低效) 

SELECT * FROM  DEPT  WHERE SAL * 12 > 25000 

(高效)

SELECT * FROM DEPT WHERE SAL > 25000/12

9、避免在索引列上使用IS NULLIS NOT NULL

避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引.对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录. 如果至少有一个列不为空,则记录存在于索引中.举例: 如果唯一性索引建立在表的A列和B列上, 并且表中存在一条记录的A,B值为(123,null) , ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入). 然而如果所有的索引列都为空,ORACLE将认为整个键值为空而空不等于空. 因此你可以插入1000 条具有相同键值的记录,当然它们都是空! 因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引.

低效: (索引失效) 

SELECT * FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL; 

高效: (索引有效) 

SELECT * FROM DEPARTMENT WHERE DEPT_CODE >= 0;

10、避免改变索引列的类型

当比较不同数据类型的数据时, ORACLE自动对列进行简单的类型转换. 

假设 INSTID是一个数值类型的索引列. 

SELECT * FROM BPM_WORKLIST A WHERE A.INSTID = '272660' 

实际上,经过ORACLE类型转换, 语句转化为: 

SELECT * FROM BPM_WORKLIST A WHERE A.INSTID = TO_NUMBER('272660') 

幸运的是,类型转换没有发生在索引列上,索引的用途没有被改变. 

现在,假设ENDPOINT是一个字符类型的索引列. 

SELECT * FROM BPM_WORKLIST A WHERE A.ENDPOINT = 43211 

这个语句被ORACLE转换为: 

SELECT * FROM BPM_WORKLIST A WHERE TO_NUMBER(A.ENDPOINT) = 43211

因为内部发生的类型转换, 这个索引将不会被用到! 为了避免ORACLE对你的SQL进行隐式的类型转换, 最好把类型转换用显式表现出来. 注意当字符和数值比较时, ORACLE会优先转换数值类型到字符类型

11、需要当心的WHERE子句

某些SELECT 语句中的WHERE子句不使用索引. 这里有一些例子. 

在下面的例子里, 

(1) ‘!=' 将不使用索引

不使用索引:

SELECT * FROM BPM_PROCINST A WHERE A.ISDELETED != 0

使用索引:

SELECT * FROM BPM_PROCINST A WHERE A.ISDELETED > 0

(2) ‘||'是字符连接函数. 就象其他函数那样, 停用了索引.

不使用索引:

SELECT *

  FROM BPM_WORKLIST A

 WHERE A.ROLENAME || A.STATUS = 'PLAN_DEPT_MANAGERCOMPLETED'

使用索引:

SELECT *

  FROM BPM_WORKLIST A

 WHERE A.ROLENAME = 'PLAN_DEPT_MANAGER'

   AND A.STATUS = 'COMPLETED'

(3)  ‘+'是数学函数. 就象其他数学函数那样, 停用了索引.

不使用索引:

SELECT * FROM MIS_CAPITALEXPENDITURE A WHERE A.CE_EXPENDITURE + 100000000 > 200000000

使用索引:

SELECT * FROM MIS_CAPITALEXPENDITURE A WHERE A.CE_EXPENDITURE > 100000000

12、避免使用耗费资源的操作

带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎 

执行耗费资源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要执行两次排序. 通常, 带有UNION, MINUS , INTERSECT的SQL语句都可以用其他方式重写. 如果你的数据库的SORT_AREA_SIZE调配得好, 使用UNION , MINUS, INTERSECT也是可以考虑的, 毕竟它们的可读性很强。

13、对于复杂的分页查询,请简化取得总记录数的SQL,不要直接在取列表的SQL前加COUNT(1)

取记录总条数的SQL不要使用ORDER BY

14、怎样写高效的分页查询

低效:

SELECT *

  FROM (SELECT A.*, ROWNUM RN FROM (SELECT * FROM BPM_WORKLIST) A)

 WHERE RN BETWEEN 21 AND 40

高效:

SELECT *

  FROM (SELECT A.*, ROWNUM RN

          FROM (SELECT * FROM BPM_WORKLIST) A

         WHERE ROWNUM <= 40)

 WHERE RN >= 21

对比这两种写法,绝大多数的情况下,第二个查询的效率比第一个高得多。

这是由于CBO优化模式下,Oracle可以将外层的查询条件推到内层查询中,以提高内层查询的执行效率。对于第二个查询语句,第二层的查询条件WHERE ROWNUM <= 40就可以被Oracle推入到内层查询中,这样Oracle查询的结果一旦超过了ROWNUM限制条件,就终止查询将结果返回了。

而第一个查询语句,由于查询条件BETWEEN 21 AND 40是存在于查询的第三层,而Oracle无法将第三层的查询条件推到最内层(即使推到最内层也没有意义,因为最内层查询不知道RN代表什么)。因此,对于一个查询语句,Oracle最内层返回给中间层的是所有满足条件的数据,而中间层返回给最外层的也是所有数据。数据的过滤在最外层完成,显然这个效率要比第二个查询低得多。

上面分析的查询不仅仅是针对单表的简单查询,对于最内层查询是复杂的多表联合查询或最内层查询包含排序的情况一样有效。

15、通过拼装批量SQL来提高插入批量数据的性能

如果有时候需要在JAVA代码中对某个表插入批量数据,通过一个循环插入比较慢,因为要连接多次数据库,为了提高效率,可以把多个插入数据的SQL语句拼成一个,只需执行一次就可以完成数据库操作。

低效:

INSERT INTO BO_PROJECT A (A.BO_PROJECT_ID, A.PROJECT_NAME) VALUES(sys_guid(),'测试项目_1');

INSERT INTO BO_PROJECT A (A.BO_PROJECT_ID, A.PROJECT_NAME) VALUES(sys_guid(),'测试项目_2');

INSERT INTO BO_PROJECT A (A.BO_PROJECT_ID, A.PROJECT_NAME) VALUES(sys_guid(),'测试项目_3');

--...

INSERT INTO BO_PROJECT A (A.BO_PROJECT_ID, A.PROJECT_NAME) VALUES(sys_guid(),'测试项目_99');

INSERT INTO BO_PROJECT A (A.BO_PROJECT_ID, A.PROJECT_NAME) VALUES(sys_guid(),'测试项目_100');

高效:

INSERT INTO BO_PROJECT A

  (A.BO_PROJECT_ID, A.PROJECT_NAME)

  SELECT SYS_GUID(), '测试项目_1'

    FROM DUAL

  UNION ALL

  SELECT SYS_GUID(), '测试项目_2'

    FROM DUAL

  UNION ALL

  SELECT SYS_GUID(), '测试项目_3'

    FROM DUAL

  --...

  UNION ALL

  SELECT SYS_GUID(), '测试项目_99'

    FROM DUAL

  UNION ALL

  SELECT SYS_GUID(), '测试项目_100' 

    FROM DUAL