腾龙国际客服【微662282】SQL 嵌套 N 层太长太难写怎么办?

19 views (last 30 days)
yuyuww
yuyuww on 27 Sep 2022
Answered: yuyuww on 27 Sep 2022
长SQL有什么危害?
怎么办?
Java
Python
Scala
SPL
常规计算能力
超越SQL的能力
应用集成、低耦合与热切换
SPL资料
我们工作中写SQL处理数据是家常便饭,不管是应用内数据处理还是临时查询分析都可以用SQL完成,相对其他技术(如Java等高级语言)也更简单。不过,SQL的简单只限于简单需求,有些复杂计算场景SQL写起来却很难,嵌套N层以至于达到几百上千行,说SQL代码长度时通常不会以行计而是以KB计。这种情况并不少见,相信经常写SQL的小伙伴并不陌生。
为什么会出现这种情况呢?在http://c.raqsoft.com.cn/article/1639032922105 里详细分析了这个问题。虽然SQL比其他数据处理技术更简单,但仍存在一些短板(如缺乏有序支持、集合化不彻底、没有高级语言的对象引用机制等),导致复杂计算用SQL表达出来很繁琐。这种既复杂又很长的SQL会带来很多问题。
长SQL有什么危害?
复杂长SQL给我们带来的第一个困扰是难写。
其实,代码长本身还不是大问题,很多任务步骤多也就会写得长,但并不难。而SQL的长,常常是伴随着难,而且会随着长度增加变得异常难。SQL在解题时很绕(思维方式上的难),你没法按照正常思路去实施算法,明明想出一个好的解法,简单一二三四步就完成了,但用SQL就要绕来绕去,不嵌套几层写个几十上百行好像体现不出你水平一样。而且不光嵌套子查询,复杂SQL还伴随多表关联和各种过滤条件,写的时候要保持头脑异常清醒,一旦写错不仅很难定位,有时还会把数据库跑死。这当然跟SQL不提倡过程(CTE语法提供了一定支持)有关,一个任务写100行代码,分成 100 个语句还是只有 1 个语句,其复杂度完全不是一个层面的。
另外,写代码还离不开调试,而SQL的调试功能可谓难用至极。直到今天各大数据库都没提供像样的调试功能,相比其他高级语言的开发调试环境简直不忍直视。一句嵌套N层的长SQL发现写的不对只能把子查询从长语句里一层一层摘出来单独执行调试定位,费时费力,这更增大了SQL的书写难度。
难写就意味着开发周期长,我们写SQL都是为前端业务服务的,一个计算需求连写带调试搞个三五天甚至一周,恐怕业务时效性都没了,被怒怼也是有苦难言。
除了难写,复杂SQL还很难维护。数据处理业务的稳定性往往很差,经常要修改,不过想要修改这些SQL恐怕并非易事。别说新人接手,就是作者本人也经常出现过一段时间自己都看不懂的尴尬情况。改一个SQL跟重新写一个的时间差不多,这还玩啥。
另外复杂SQL还会影响数据库移植。虽然数据库移植不经常发生,不过一旦发生就要命。近些年随着业务的高速发展以及开源数据库的崛起,更换数据库时有发生。这就涉及到SQL迁移,我们知道数据库都是有方言的,在一个数据库上使用的函数到另外一个数据库未必管用,而且各类数据库对SQL标准的支持程度不同(如Oracle窗口函数支持的很好,其他数据库就要差很多),这些“特殊的”内容夹杂在复杂SQL里就很难实现数据库迁移了。
不管怎样,复杂SQL都是数据开发人员的噩梦。
怎么办?
SQL难的问题可不是第一天出现,大家也都在积极寻找解决办法。其实现在很多开发方法已经不再推荐写复杂SQL,像ORM技术算是革掉SQL半条命,而微服务等框架更是要求SQL仅仅用于完成基本的数据读写,而不能用于做复杂计算和业务处理。这些方法的思路很清晰,即把SQL的应用局限于基本的读写任务,在应用端完成复杂的数据处理和业务逻辑,这样就可以规避SQL的那些问题,架构上也更符合现代应用的需要。
不用SQL,那些复杂的计算和业务逻辑该用什么技术来做呢?
当然只能是其它程序语言了,几乎所有应用级程序语言都可以指挥SQL工作,实现这种开发架构不是问题。那么,这个问题是不是就被轻易解决了?
我们来考察一下这些常用的技术。
Java
Java肯定是第一选择,毕竟众多应用程序都是Java开发的,如果能搞定这些数据处理就会有很多优势。Java天然支持过程计算,实现复杂计算时虽然代码可能更长,但可以按照正常思维实现算法,这样是不是就可以替代SQL了?
No,没有这么简单。
由于Java缺乏专业的结构化数据对象,缺少来自底层的有力支持,在实现SQL这类的复杂计算时并不容易。
结构化数据计算的返回值的结构随计算过程而变,大量的中间结果同样是动态结构,这些都难以事先定义,而Java是强类型语言,又必须事先定义数据对象的结构(否则只能用map这类操作繁琐的数据对象),这就使Java的结构化计算僵化死板,lambda语法的能力严重受限。解释性语言可以简化参数的定义,函数本身就可指定参数表达式应解释成值参数还是函数参数,而Java是编译型语言,难以区分不同类型的参数,必须设计复杂的接口才能实现匿名函数(主要指lambda语法),这连SQL程序员都不易掌握。省略数据对象而直接引用字段(比如写成"单价*数量"),可显著简化结构化数据计算,但Java缺乏专业的结构化数据对象,目前还无法支持此类表面简单实则巧妙的语法,这就使Java代码冗长且不直观(只能写成"x.单价*x.数量")。
缺少结构化数据计算类库还会导致代码过长,同样的一个分组汇总用SQL一句就能写出来改成Java就要写几十行,这显然也对简化复杂SQL无益。这个问题即使在Java8增加了Stream后也没有明显改善,为了简化运算用Java取代SQL基本不可能(有些应用用Java做数据处理往往是由于架构上的要求,就其简便性上还远不及SQL)。
另外,Java作为编译型语言很难热部署,也就难以及时应对多变的数据计算场景。
Python
直接的Java不行,那大热的Python怎么样呢?Python+SQL可比Java+SQL容易得多了吧。
的确,Python(主要是Pandas)提供了丰富结构化计算类库,计算实现要比 Java 简单很多。不过,实际使用Pandas处理数据尤其是复杂运算时也会碰到代码很难写的情况。其实,Pandas 本来就不是为结构化数据设计的,它并不是我们熟悉的数据表(一行行数据记录的集合),而是个矩阵。用来处理结构化数据时,做些过滤合并这些简单运算还好点,碰到分组有序等复杂一些运算,就会比较麻烦了,思路上也经常要绕一下。
这还不是Python的主要问题,Python与应用结合的难点在于其集成性上。
Python很难与Java应用集成!
Python和Java集成主要有两种方式。一种是服务式调用,既然Python和Java是两套体系,那就单独部署通过服务(网络通信)的方式交互,这种方式要维护两套应用比较麻烦,还会存在一些安全认证系统权限等管理问题,而且数据交换的性能也很差,不到万不得已不会采用这种方式。另一种是使用Jython这种解释器(JVM上的Python),由于采用Java开发可以很方便与应用集成,但Jython又缺乏足够的计算库(如Pandas)很难应付那些复杂计算。无论采用何种方式,都会在应用方面产生巨大限制,导致在Java应用中用Python来实现复杂计算的可行性不高。
除非你把整个应用都用Python写,但是Java的各种企业级能力又用不上了,做做小应用还行吧。
Scala
Scala的特性与Python类似,都提供了DataFrame对象,实现简单的结构化数据计算也比较简单,作为高级语言有序、对象属性化等特性支持良好,相对Java在集合运算上也更简单。Scala运行于JVM之上,天生就容易被JAVA集成,这些都是Scala的优点。
Scala的缺点在于使用难度较大,难学更难精,用于复杂数据处理与SQL相比尤有劣势,也就不用想通过Scala简化复杂SQL了。这大概也是为什么Spark要回归SQL的原因了吧。而且,Scala作为编译型语言同样不支持热部署。
SPL
这些高级语言都存在这样那样的缺点,从简化复杂SQL的角度来看无一能胜任。那还有什么办法呢?
其实,从前面的分析中我们明显能够感受到,面向结构化数据计算尤其是复杂计算,现有技术(开发语言)都各有优缺点,SQL擅长复杂计算但对有序、过程支持不好,Java恰好反过来支持有序和过程但集合计算能力很弱。如果能综合这些技术的优点,那简化复杂SQL的目标也就达成了。
开源SPL恰好是这样一个产品。
SPL全称Structured Process Language,是一个专门用于结构化数据计算的程序语言。
常规计算能力
一致的数据类型
SPL相对Java提供了更专业的结构化数据类型,即序表。和SQL的数据表一样,序表是批量记录组成的集合,具有结构化数据类型的一般功能,可以与SQL无缝交互承接SQL的返回值。
序表支持所有的结构化计算函数,计算结果也同样是序表,而不是Map之类的数据类型。比如对分组汇总的结果,继续进行结构化数据处理。
Orders.groups(year(OrderDate):y; sum(Amount):m).new(y:OrderYear, m*0.2:discount)
1
丰富的计算类库
在序表的基础上,SPL提供了丰富的结构化数据计算函数,比如过滤、排序、分组、去重、改名、计算列、关联、子查询、集合计算、有序计算等。这些函数具有强大的计算能力,无须硬编码辅助,就能独立完成计算。
如组合查询:
Orders.select(Amount>1000 && Amount<=3000 && like(Client,"\*bro\*"))
1
内关联:
join(Orders:o,SellerId ; Employees:e,EId).groups(e.Dept; sum(o.Amount))
1
SPL支持简单形式的Lambda语法,无须定义函数名和函数体,可以直接用表达式当作函数的参数。修改业务逻辑时,也不用重构函数,只须简单修改表达式。SPL是解释型语言,使用参数表达式时不必明确定义参数类型,使Lambda接口更简单。比如计算平方和,想在sum的过程中算平方,可以直观写作:Orders.sum(Amount*Amount)。
同时作为解释执行语言的SPL还天然支持动态数据结构,可以根据计算结果结构动态生成新序表,特别适合计算列、分组汇总、关联这类计算。较复杂的计算通常都要拆成多个步骤,每个中间结果的数据结构几乎都不同。SPL支持动态数据结构,不必先定义这些中间结果的结构。比如,根据某年的客户回款记录表,计算每个月的回款额都在前10名的客户。
Sales2021.group(month(sellDate)).(~.groups(Client;sum(Amount):sumValue)).(~.sort(-sumValue)).(~.select(#<=10)).(~.(Client)).isect()
1
过程控制与SQL协同
除了提供与SQL相当的集合运算能力,SPL还对过程控制和分步计算提供了良好支持,灵活的分支判断语句、循环语句,配合专业的结构化数据对象,可以方便地实现各类业务逻辑。比如找出销售额累计占到一半的前n个大客户,可以这样分步实现:
A
1 =db.query(“select client,sum(amount) amount from sales group by client order by amount desc”)
2 =A1. sum(amount)/2
3 =0
4 =A1.pselect((A3=A3+amount,A3>=A2))
5 =A1(to(A4))
通过SQL先完成分组汇总并按汇总值降序排序,然后SPL承接SQL的计算结果再通过分步方式完成后续计算,SPL与SQL有效结合,这样就很大程度达到了简化复杂计算的目标。
在应用中,SPL很多时候都要与SQL交互协同,充分发挥二者的优势,包括数据库读写、事务处理、流程处理、存储过程调用等。
数据库写入(更新/插入):
db.update@u(A7,sales;ORDERID)
1
执行DML语句:
db.execute("insert into DEPARTMENT(DEPT, MANAGER) values(?,?)","TecSupport",9)
1
调用存储过程:
db.proc({db_proc(?,?),,:101:"o":table1,1:0:"i": })
1
丰富的集合运算能力加上过程计算与流程控制(包括指挥SQL),这样就获得了SQL和Java相当的能力,而实现上要比Java更简单。
超越SQL的能力
SPL不仅覆盖了SQL的所有计算能力,还提供了更强大语言功能。基于这些特性可以很方便原来在SQL中不易完成的运算,简化复杂计算可不是开玩笑的。
离散性及其支持下的更彻底的集合化
集合化是SQL的基本特性,即支持数据以集合的形式参与运算。但SQL的离散性很不好,所有集合成员必须作为一个整体参于运算,不能游离在集合之外。而Java等高级语言则支持很好的离散性,数组成员可以单独运算。但是,更彻底的集合化需要离散性来支持,集合成员可以游离在集合之外,并与其它数据随意构成新的集合参与运算 。
SPL兼具了SQL的集合化和Java的离散性,从而可以实现更彻底的集合化。
SPL很容易表达“集合的集合”,适合分组后计算。比如,找到各科成绩均在前10名的学生:
A
1 =db.query(“select * from score”) .group(subject)
2 =A2.(~.rank(score).pselect@a(~<=10))
3 =A1.(~(A3(#)).(name)).isect()
有序支持
有序计算是离散性和集合化的典型结合产物,成员的次序在集合中才有意义,这要求集合化,有序计算时又要将每个成员与相邻成员区分开,会强调离散性。SPL兼具集合化和离散性,天然支持有序计算。
具体来说,SPL可以按绝对位置引用成员,比如,取第3条订单可以写成Orders(3),取第1、3、5条记录可以写成Orders([1,3,5])。
SPL也可以按相对位置引用成员,比如,计算每条记录相对于上一条记录的金额增长率:
Orders.derive(amount/amount[-1]-1)
1
SPL还可以用#代表当前记录的序号,比如把员工按序号分成两组,奇数序号一组,偶数序号一组:
Employees.group(#%2==1)
1
在有序计算的支持下,再处理结构化数据计算中关于次序的运算(诸如比上月、比去年同期、前 20%、排名等)就很方便了。
对象引用
SPL序表的字段可以存储记录或记录集合,这样可以用对象引用的方式,直观地表达关联关系,即使关系再多,也能直观地表达。比如,根据员工表找到女经理下属的男员工:
Employees.select(gender:"male",department.manager.gender:"female")
1
更方便的函数语法
提供大量功能强大的结构化数据计算函数本来是一件好事,但这会让相似功能的函数不容易区分,无形中提高了学习难度。
SPL提供了特有的函数选项语法,功能相似的函数可以共用一个函数名,只用函数选项区分差别。比如select函数的基本功能是过滤,如果只过滤出符合条件的第1条记录,可以使用选项@1:
Orders.select@1(Amount>1000)
1
数据量较大时,用并行计算提高性能,只须改为选项@m:
Orders.select@m(Amount>1000)
1
对排序过的数据,用二分法进行快速过滤,可用@b:
Orders.select@b(Amount>1000)
1
函数选项还可以组合搭配,比如:
Orders.select@1b(Amount>1000)
1
结构化运算函数的参数常常很复杂,比如SQL就需要用各种关键字把一条语句的参数分隔成多个组,但这会动用很多关键字,也使语句结构不统一。SPL支持层次参数,通过分号、逗号、冒号自高而低将参数分为三层,用通用的方式简化复杂参数的表达:
join(Orders:o,SellerId ; Employees:e,EId)
1
方便的编辑调试功能
与SQL难用的编辑调试功能不同,SPL提供了简洁易用的开发环境,单步执行、设置断点,所见即所得的结果预览窗口…,使得开发调试效率更高。
SPL采用了网格式编码方式,代码不仅天然对齐层次清晰,在实施过程计算时可以直接使用格子名称来引用上一步的计算结果而不必费心定义变量(虽然也支持)非常方便。好用的开发环境自然起到事半功倍的效果,进一步简化复杂计算实施难度。
应用集成、低耦合与热切换
SPL提供了标准应用接口(JDBC/ODBC/RESTful)可以很方便与应用集成。尤其对于Java应用可以将SPL作为嵌入引擎集成,使得应用本身就具备强数据计算能力。
JDBC调用SPL 代码示例:
Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local://");
Statement st = connection.();
CallableStatement st = conn.prepareCall("{call splscript(?, ?)}");
st.setObject(1, 3000);
st.setObject(2, 5000);
ResultSet result=st.execute();
1
2
3
4
5
6
7
同时,SPL采用解释执行机制,天然支持热切换。这样对于稳定性差、经常需要新增修改的数据处理需求非常友好。SPL脚本可以与Java程序独立,外置在Java之外,修改和维护都可以独立进行,脚本修改上传后就能实时生效,保证应用可以不中断地提供数据服务。
SPL外置算法不仅能有效降低应用与数据库的耦合性,独立的SPL模块还可以进一步降低应用各个模块间的耦合性,使得结构更为清晰,架构也更为合理。
总结一下,SPL之所以更简单是因为SPL相当于结合了SQL和其他高级语言(如Java)的优点,并在此基础上增加了诸多特性让复杂计算不再难写,同时可以与应用完美结合使得应用架构更为合理。有了SPL的帮助,我们相信,未来的某一天上千行的复杂SQL将不复存在。
————————————————

Answers (1)

yuyuww
yuyuww on 27 Sep 2022
我们做软件开发的,大部分人都离不开跟数据库打交道,特别是erp开发的,跟数据库打交道更是频繁,存储过程动不动就是上千行,如果数据量大,人员流动大,那么我们还能保证下一段时间系统还能流畅的运行吗?我们还能保证下一个人能看懂我们的存储过程吗?
要知道sql语句,我想我们有必要知道sqlserver查询分析器怎么执行我么sql语句的,我么很多人会看执行计划,或者用profile来监视和调优查询语句或者存储过程慢的原因,但是如果我们知道查询分析器的执行逻辑顺序,下手的时候就胸有成竹,那么下手是不是有把握点呢?
查询的逻辑执行顺序
  1. FROM < left_table>
  2. ON < join_condition>
  3. < join_type> JOIN < right_table>
  4. WHERE < where_condition>
  5. GROUP BY < group_by_list>
  6. WITH {cube | rollup}
  7. HAVING < having_condition>
  8. SELECT
  9. DISTINCT
  10. ORDER BY < order_by_list>
  11. < top_specification> < select_list>
标准的SQL 的解析顺序为:
  1. .FROM 子句 组装来自不同数据源的数据
  2. .WHERE 子句 基于指定的条件对记录进行筛选
  3. .GROUP BY 子句 将数据划分为多个分组
  4. .使用聚合函数进行计算
  5. .使用HAVING子句筛选分组
  6. .计算所有的表达式
  7. .使用ORDER BY对结果集进行排序
执行顺序
  1. FROM:对FROM子句中前两个表执行笛卡尔积生成虚拟表vt1
  2. ON:对vt1表应用ON筛选器只有满足< join_condition> 为真的行才被插入vt2
  3. OUTER(join):如果指定了 OUTER JOIN保留表(preserved table)中未找到的行将行作为外部行添加到vt2 生成t3如果from包含两个以上表则对上一个联结生成的结果表和下一个表重复执行步骤和步骤直接结束
  4. WHERE:对vt3应用 WHERE 筛选器只有使< where_condition> 为true的行才被插入vt4
  5. GROUP BY:按GROUP BY子句中的列列表对vt4中的行分组生成vt5
  6. CUBE|ROLLUP:把超组(supergroups)插入vt6 生成vt6
  7. HAVING:对vt6应用HAVING筛选器只有使< having_condition> 为true的组才插入vt7
  8. SELECT:处理select列表产生vt8
  9. DISTINCT:将重复的行从vt8中去除产生vt9
  10. ORDER BY:将vt9的行按order by子句中的列列表排序生成一个游标vc10
  11. TOP:从vc10的开始处选择指定数量或比例的行生成vt11 并返回调用者
看到这里,那么用过linqtosql的语法有点相似啊?如果我们我们了解了sqlserver执行顺序,那么我们就接下来进一步养成日常sql好习惯,也就是在实现功能同时有考虑性能的思想,数据库是能进行集合运算的工具,我们应该尽量的利用这个工具,所谓集合运算实际就是批量运算,就是尽量减少在客户端进行大数据量的循环操作,而用SQL语句或者存储过程代替。只返回需要的数据
返回数据到客户端至少需要数据库提取数据、网络传输数据、客户端接收数据以及客户端处理数据等环节,如果返回不需要的数据,就会增加服务器、网络和客户端的无效劳动,其害处是显而易见的,避免这类事件需要注意:
1. 横向来看:
  1. 不要写SELECT *的语句,而是选择你需要的字段。
  2. 当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。
如有表table1(ID,col1)和table2 (ID,col2)
1Select A.ID, A.col1, B.col2
2 -- Select A.ID, col1, col2 –不要这么写,不利于将来程序扩展
3 from table1 A inner join table2 B on A.ID=B.ID Where
2. 纵向来看:
  1. 合理写WHERE子句,不要写没有WHERE的SQL语句。
  2. SELECT TOP N * --没有WHERE条件的用此替代
尽量少做重复的工作
  1. 控制同一语句的多次执行,特别是一些基础数据的多次执行是很多程序员很少注意的。
  2. 减少多次的数据转换,也许需要数据转换是设计的问题,但是减少次数是程序员可以做到的。
  3. 杜绝不必要的子查询和连接表,子查询在执行计划一般解释成外连接,多余的连接表带来额外的开销。
  4. 合并对同一表同一条件的多次UPDATE,比如:1UPDATE EMPLOYEE SET FNAME='HAIWER'2WHERE EMP_ID=' VPA30890F' UPDATE EMPLOYEE SET LNAME='YANG'3WHERE EMP_ID=' VPA30890F'4这两个语句应该合并成以下一个语句5UPDATE EMPLOYEE SET FNAME='HAIWER',LNAME='YANG' WHERE EMP_ID=' VPA30890F'
  5. UPDATE操作不要拆成DELETE操作+INSERT操作的形式,虽然功能相同,但是性能差别是很大的。
注意临时表和表变量的用法
在复杂系统中,临时表和表变量很难避免,关于临时表和表变量的用法,需要注意:
  1. 如果语句很复杂,连接太多,可以考虑用临时表和表变量分步完成。
  2. 如果需要多次用到一个大表的同一部分数据,考虑用临时表和表变量暂存这部分数据。
  3. 如果需要综合多个表的数据,形成一个结果,可以考虑用临时表和表变量分步汇总这多个表的数据。
  4. 其他情况下,应该控制临时表和表变量的使用。
  5. 关于临时表和表变量的选择,很多说法是表变量在内存,速度快,应该首选表变量,但是在实际使用中发现,主要考虑需要放在临时表的数据量,在数据量较多的情况下,临时表的速度反而更快。执行时间段与预计执行时间(多长)。
  6. 关于临时表产生使用SELECT INTO和CREATE TABLE + INSERT INTO的选择,一般情况下,SELECT INTO会比CREATE TABLE + INSERT INTO的方法快很多,但是SELECT INTO会锁定TEMPDB的系统表SYSOBJECTS、SYSINDEXES、SYSCOLUMNS,在多用户并发环境下,容易阻塞其他进程,所以我的建议是,在并发系统中,尽量使用CREATE TABLE + INSERT INTO,而大数据量的单个语句使用中,使用SELECT INTO。
子查询的用法
子查询是一个 SELECT 查询,它嵌套在 SELECT、INSERT、UPDATE、DELETE 语句或其它子查询中。任何允许使用表达式的地方都可以使用子查询,子查询可以使我们的编程灵活多样,可以用来实现一些特殊的功能。但是在性能上,往往一个不合适的子查询用法会形成一个性能瓶颈。如果子查询的条件中使用了其外层的表的字段,这种子查询就叫作相关子查询。相关子查询可以用IN、NOT IN、EXISTS、NOT EXISTS引入。 关于相关子查询,应该注意:
1. NOT IN、NOT EXISTS的相关子查询可以改用LEFT JOIN代替写法。
比如:
1SELECT PUB_NAME FROM PUBLISHERS WHERE PUB_ID NOT IN (SELECT PUB_ID FROM TITLES WHERE TYPE ='BUSINESS')
可以改写成:
1SELECT A.PUB_NAME FROM PUBLISHERS A LEFT JOIN TITLES B ON B.TYPE = 'BUSINESS' AND A.PUB_ID=B. PUB_ID WHERE B.PUB_ID IS NULL
又比如:
1SELECT TITLE FROM TITLES
2WHERE NOT EXISTS
3 (SELECT TITLE_ID FROM SALES
4WHERE TITLE_ID = TITLES.TITLE_ID)
可以改写成:
1SELECT TITLE
2FROM TITLES LEFT JOIN SALES
3ON SALES.TITLE_ID = TITLES.TITLE_ID
4WHERE SALES.TITLE_ID IS NULL
2. 如果保证子查询没有重复 ,IN、EXISTS的相关子查询可以用INNER JOIN 代替。比如:
1SELECT PUB_NAME
2FROM PUBLISHERS
3WHERE PUB_ID IN
4 (SELECT PUB_ID
5 FROM TITLES
6 WHERE TYPE = 'BUSINESS')
可以改写成:
1SELECT A.PUB_NAME --SELECT DISTINCT A.PUB_NAME
2FROM PUBLISHERS A INNER JOIN TITLES B
3ON B.TYPE = 'BUSINESS' AND
4A.PUB_ID=B. PUB_ID
3. IN的相关子查询用EXISTS代替,比如
1SELECT PUB_NAME FROM PUBLISHERS
2WHERE PUB_ID IN
3(SELECT PUB_ID FROM TITLES WHERE TYPE = 'BUSINESS')
可以用下面语句代替:
1SELECT PUB_NAME FROM PUBLISHERS WHERE EXISTS
2(SELECT 1 FROM TITLES WHERE TYPE = 'BUSINESS' AND
3PUB_ID= PUBLISHERS.PUB_ID)
4. 不要用COUNT(*)的子查询判断是否存在记录,最好用LEFT JOIN或者EXISTS,比如有人写这样的语句:
1SELECT JOB_DESC FROM JOBS
2WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)=0
应该写成:
1SELECT JOBS.JOB_DESC FROM JOBS LEFT JOIN EMPLOYEE
2ON EMPLOYEE.JOB_ID=JOBS.JOB_ID
3WHERE EMPLOYEE.EMP_ID IS NULL
还有
1SELECT JOB_DESC FROM JOBS
2WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)<>0
应该写成:
1SELECT JOB_DESC FROM JOBS
2WHERE EXISTS (SELECT 1 FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)
尽量使用索引
建立索引后,并不是每个查询都会使用索引,在使用索引的情况下,索引的使用效率也会有很大的差别。只要我们在查询语句中没有强制指定索引,索引的选择和使用方法是SQLSERVER的优化器自动作的选择,而它选择的根据是查询语句的条件以及相关表的统计信息,这就要求我们在写SQL。
语句的时候尽量使得优化器可以使用索引。为了使得优化器能高效使用索引,写语句的时候应该注意:
A、不要对索引字段进行运算,而要想办法做变换,比如
01 SELECT ID FROM T WHERE NUM/2=100
02 应改为:
03 SELECT ID FROM T WHERE NUM=100*2
04
05 SELECT ID FROM T WHERE NUM/2=NUM1
06 如果NUM有索引应改为:
07 SELECT ID FROM T WHERE NUM=NUM1*2
08 如果NUM1有索引则不应该改。
09
10发现过这样的语句:
11 SELECT 年,月,金额 FROM 结余表 WHERE 100*年+月=2010*100+10
12 应该改为:
13 SELECT 年,月,金额 FROM 结余表 WHERE 年=2010 AND月=10
B、 不要对索引字段进行格式转换
01日期字段的例子:
02WHERE CONVERT(VARCHAR(10), 日期字段,120)='2010-07-15'
03应该改为
04WHERE日期字段〉='2010-07-15' AND 日期字段<'2010-07-16'
05
06ISNULL转换的例子:
07WHERE ISNULL(字段,'')<>''应改为:WHERE字段<>''
08WHERE ISNULL(字段,'')=''不应修改
09WHERE ISNULL(字段,'F') ='T'应改为: WHERE字段='T'
10WHERE ISNULL(字段,'F')<>'T'不应修改
C、 不要对索引字段使用函数
01WHERE LEFT(NAME, 3)='ABC' 或者WHERE SUBSTRING(NAME,1, 3)='ABC'
02应改为: WHERE NAME LIKE 'ABC%'
03日期查询的例子:
04WHERE DATEDIFF(DAY, 日期,'2010-06-30')=0
05应改为:WHERE 日期>='2010-06-30' AND 日期 <'2010-07-01'
06WHERE DATEDIFF(DAY, 日期,'2010-06-30')>0
07应改为:WHERE 日期 <'2010-06-30'
08WHERE DATEDIFF(DAY, 日期,'2010-06-30')>=0
09应改为:WHERE 日期 <'2010-07-01'
10WHERE DATEDIFF(DAY, 日期,'2010-06-30')<0
11应改为:WHERE 日期>='2010-07-01'
12WHERE DATEDIFF(DAY, 日期,'2010-06-30')<=0
13应改为:WHERE 日期>='2010-06-30'
4. 不要对索引字段进行多字段连接
1比如:
2WHERE FAME+ '. '+LNAME='HAIWEI.YANG'
3应改为:
4WHERE FNAME='HAIWEI' AND LNAME='YANG'
多表连接的连接条件
多表连接的连接条件对索引的选择有着重要的意义,所以我们在写连接条件条件的时候需要特别注意。
  1. 多表连接的时候,连接条件必须写全,宁可重复,不要缺漏。
  2. 连接条件尽量使用聚集索引
  3. 注意ON、WHERE和HAVING部分条件的区别
ON是最先执行, WHERE次之,HAVING最后,因为ON是先把不符合条件的记录过滤后才进行统计,它就可以减少中间运算要处理的数据,按理说应该速度是最快的,WHERE也应该比 HAVING快点的,因为它过滤数据后才进行SUM,在两个表联接时才用ON的,所以在一个表的时候,就剩下WHERE跟HAVING比较了
考虑联接优先顺序:
  1. INNER JOIN
  2. LEFT JOIN (注:RIGHT JOIN 用 LEFT JOIN 替代)
  3. CROSS JOIN
其它注意和了解的地方有:
  1. 在IN后面值的列表中,将出现最频繁的值放在最前面,出现得最少的放在最后面,减少判断的次数
  2. 注意UNION和UNION ALL的区别。--允许重复数据用UNION ALL好
  3. 注意使用DISTINCT,在没有必要时不要用
  4. TRUNCATE TABLE 与 DELETE 区别
  5. 减少访问数据库的次数
还有就是我们写存储过程,如果比较长的话,最后用标记符标开,因为这样可读性很好,即使语句写的不怎么样但是语句工整,C# 有region,sql我比较喜欢用的就是:
1--startof 查询在职人数
2 sql语句
3 --end of
正式机器上我们一般不能随便调试程序,但是很多时候程序在我们本机上没问题,但是进正式系统就有问题,但是我们又不能随便在正式机器上操作,那么怎么办呢?我们可以用回滚来调试我们的存储过程或者是sql语句,从而排错。
1BEGIN TRAN
2 UPDATE a SET 字段=''
3ROLLBACK
作业存储过程我一般会加上下面这段,这样检查错误可以放在存储过程,如果执行错误回滚操作,但是如果程序里面已经有了事务回滚,那么存储过程就不要写事务了,这样会导致事务回滚嵌套降低执行效率,但是我们很多时候可以把检查放在存储过程里,这样有利于我们解读这个存储过程,和排错。
01BEGIN TRANSACTION
02--事务回滚开始
03
04--检查报错
05 IF ( @@ERROR > 0 )
06 BEGIN
07--回滚操作
08 ROLLBACK TRANSACTION
09 RAISERROR('删除工作报告错误', 16, 3)
10 RETURN
11 END
12
13
14--结束事务
15 COMMIT TRANSACTION
大概就写这么多了,有错误的地方欢迎大家拍砖,希望交流和共享。

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!