文出处: http://www.cnblogs.com/wy123/p/5958047.html
最近发现还有不少做开发的小伙伴,在写存储过程的时候,在参考已有的不同的写法时,往往很迷茫,
不知道各种写法孰优孰劣,该选用那种写法,以及各种写法优缺点,本文以一个简单的查询存储过程为例,简单说一下各种写法的区别,以及该用那种写法
专业DBA以及熟悉数据库的同学请无视。
废话不多,上代码说明,先造一个测试表待用,简单说明一下这个表的情况
类似订单表,订单表有订单ID,客户ID,订单创建时间等,查询条件是常用的订单ID,客户ID,以及订单创建时间
create table SaleOrder
(
id int identity(1,1),
OrderNumber int ,
CustomerId varchar(20) ,
OrderDate datetime ,
Remark varchar(200)
)
GOdeclare @i int=0while @i<100000begin
insert into SaleOrder values (@i,CONCAT('C',cast(RAND()*1000 as int)),GETDATE()-RAND()*100,NEWID()) set @i=@i+1endcreate index idx_OrderNumber on SaleOrder(OrderNumber)create index idx_CustomerId on SaleOrder(CustomerId)create index idx_OrderDate on SaleOrder(OrderDate)
生成的测试数据大概就是这个样子的
下面演示说明几种常见的写法以及每种写法潜在的问题
第一种常见的写法:拼凑字符串,用EXEC的方式执行这个拼凑出来的字符串,不推荐
create proc pr_getOrederInfo_1
( @p_OrderNumber int , @p_CustomerId varchar(20) , @p_OrderDateBegin datetime , @p_OrderDateEnd datetime)asbegin
set nocount on; declare @strSql nvarchar(max); set @strSql='SELECT [id]
,[OrderNumber]
,[CustomerId]
,[OrderDate]
,[Remark]
FROM [dbo].[SaleOrder] where 1=1 '; /*
这种写法的特点在于将查询SQL拼凑成一个字符串,最后以EXEC的方式执行这个SQL字符串 */
if(@p_OrderNumber is not null) set @strSql=@strSql + ' and OrderNumber=' + @p_OrderNumber
if(@p_CustomerId is not null) set @strSql=@strSql + ' and CustomerId='+ ''''+ @p_CustomerId + ''''
if(@p_OrderDateBegin is not null) set @strSql=@strSql + ' and OrderDate >=' + '''' + cast(@p_OrderDateBegin as varchar(10)) + ''''
if(@p_OrderDateEnd is not null) set @strSql=@strSql + ' and OrderDate <=' + '''' + cast(@p_OrderDateEnd as varchar(10)) + ''''
print @strSql
exec(@strSql);end
假如我们查询CustomerId为88,在2016-10-1至2016-10-3这段时间内的订单信息,如下,带入参数执行
exec pr_getOrederInfo_1 @p_OrderNumber=null , @p_CustomerId='C88' , @p_OrderDateBegin='2016-10-1' , @p_OrderDateEnd='2016-10-3'
首先说明,这种方式执行查询是完全没有问题的如下截图,结果也查出来了(当然结果也是没问题的)
我们把执行的SQL打印出来,执行的SQL语句本身就是就是存储过程中拼凑出来的字符串,这么一个查询SQL字符串
SELECT [id]
,[OrderNumber]
,[CustomerId]
,[OrderDate]
,[Remark]FROM [dbo].[SaleOrder] where 1=1
and CustomerId='C88'
and OrderDate >='2016-10-1'
and OrderDate <='2016-10-3'
那么这种存储过程的有什么问题,或者直接一点说,这种方式有什么不好的地方
其一,绕不过转移符(以及注入问题)
在拼凑字符串时,把所有的参数都当成字符串处理,当查询条件本身包含特殊字符的时候,比如 ' 符号,
或者其他需要转义的字符???,你拼凑的SQL就被打断了
举个不恰当的例子,比如字符串中 @p_CustomerId中包含 ' 符号,直接就把你拼SQL的节凑给打乱了
拼凑的SQL就变成了这个样子了,语法就不通过,更别提执行
SELECT [id]
,[OrderNumber]
,[CustomerId]
,[OrderDate]
,[Remark]
FROM [dbo].[SaleOrder]
where 1=1 and CustomerId='C'88'
一方面需要处理转移符,另一方面需要要防止SQL注入
其二,参数不同就必须重新编译
这种拼凑SQL的方式,如果每次查询的参数不同,拼凑出来的SQL字符串也不一样,
如果熟悉SQL Server的同学一定知道,只要你执行的SQL文本不一样,
比如
第一次是执行查询 *** where CustomerId='C88' ,
第二次是执行查询 *** where CustomerId='C99' ,因为两次执行的SQL文本不同
每次执行之前必然需要对其进行编译,编译的话就需要CPU,内存资源
如果存在大批量的SQL编译,无疑要消耗更多的CPU资源(当然需要内存资源)
第二种常见的写法:对所有查询条件用OR的方式加在where条件中,非常不推荐
create proc pr_getOrederInfo_2
( @p_OrderNumber int , @p_CustomerId varchar(20) , @p_OrderDateBegin datetime , @p_OrderDateEnd datetime)asbegin
set nocount on; declare @strSql nvarchar(max); SELECT [id]
,[OrderNumber]
,[CustomerId]
,[OrderDate]
,[Remark]
FROM [dbo].[SaleOrder]
where 1=1
and (@p_OrderNumber is null or OrderNumber=@p_OrderNumber) and (@p_CustomerId is null or CustomerId=@p_CustomerId) /*
这是另外一种类似的奇葩的写法,下面会重点关注
and OrderNumber=ISNULL( @p_OrderNumber,OrderNumber)
and CustomerId=ISNULL( @p_CustomerId,CustomerId) */
and (@p_OrderDateBegin is null or OrderDate >=@p_OrderDateBegin) and (@p_OrderDateEnd is null or OrderDate <=@p_OrderDateEnd)
end
首先看这种方式的执行结果,带入同样的参数,跟上面的结果一样,查询(结果)本身是没有任何问题的
这种写法写起来避免了拼凑字符串的处理,看起来很简洁,写起来也很快,稀里哗啦一个存储过程就写好了,
发布到生产环境之后就相当于埋了一颗雷,随时引爆。
因为一条低效而又频繁执行的SQL,拖垮一台服务器也是司空见惯
但是呢,问题非常多,也非常非常不推荐,甚至比第一种方式更糟糕。
分析一下这种处理方式的逻辑:
这种处理方式,因为不确定查询的时候到底有没有传入参数,也就数说不能确定某一个查询条件是否生效,
于是就采用类似 and (@p_OrderNumber is null or OrderNumber=@p_OrderNumber)这种方式,来处理参数,
这样的话
如果@p_OrderNumber为null,or的前者(@p_OrderNumber is null)成立,后者不成立,查询条件不生效
如果@p_OrderNumber为非null,or的后者(OrderNumber=@p_OrderNumber)成立而前者不成立,查询条件生效
总之来说,不管参数是否为空,都可以有效地拼凑到查询条件中去。
避免了拼SQL字符串,既做到让参数非空的时候生效,有做到参数为空的时候不生效,看起来不错,是真的吗?
那么这种存储过程的有什么问题?
1,可能会抑制索引的情况
为什么说可能会抑制到索引的时候?上面提到过,SQL在执行之前是需要编译的,
因为在编译的时候并不知道查询条件是否传入了值,有可能为null,有可能是一个具体的值
SQL Server为了保险起见,采用了全表扫描的方式,举个简单的例子
如果我直接带入CustomerId=‘C88’,再来看执行计划,结果跟上面一样,但是执行计划是完全不一样的,这就是所谓的抑制到索引的使用。
2,非常非常致命的逻辑错误
/*
这是另外一种类似的奇葩的写法,需要重点关注,真的就能满足“不管参数是否为空都满足”
and OrderNumber=ISNULL( @p_OrderNumber,OrderNumber)
and CustomerId=ISNULL( @p_CustomerId,CustomerId)
*/
对于如下这种写法:OrderNumber=ISNULL( @p_OrderNumber,OrderNumber),
一部分人非常推崇,认为这种方式简单、清晰,我也是醉了,有可能产生非常严重的逻辑错误
如果参数为null,就转换成这种语义 where 1=1 and OrderNumber=OrderNumber
目的是查询参数为null,查询条件不生效,让这个查询条件恒成立,恒成立吗,不一定,某些情况下就会有严重的语义错误
博主发现这个问题也是因为某些实际系统中的bug,折腾了好久才发现这个严重的逻辑错误 http://www.cnblogs.com/wy123/p/5580821.html
对于这种写法,
不管是第一点说的抑制索引的问题,数据量大的时候是非常严重的,上述写法会造成全表扫描,有索引页用不上,至于全表扫描的坏处就不说了
还是第二点说的造成的逻辑错误,都是非常致命的
所以这种方式是最最不推荐的。
第三种常见的写法:参数化SQL,推荐
create proc pr_getOrederInfo_3
( @p_OrderNumber int , @p_CustomerId varchar(20) , @p_OrderDateBegin datetime , @p_OrderDateEnd datetime)asbegin
set nocount on;
DECLARE @Parm NVARCHAR(MAX)=N'',
@sqlcommand NVARCHAR(MAX)=N''
SET @sqlcommand='SELECT [id]
,[OrderNumber]
,[CustomerId]
,[OrderDate]
,[Remark]
FROM [dbo].[SaleOrder]
where 1=1 '
IF(@p_OrderNumber IS NOT NULL) SET @sqlcommand=CONCAT(@sqlcommand,' AND OrderNumber=@p_OrderNumber') IF(@p_CustomerId IS NOT NULL) SET @sqlcommand=CONCAT(@sqlcommand,' AND CustomerId=@p_CustomerId') IF(@p_OrderDateBegin IS NOT NULL) SET @sqlcommand=CONCAT(@sqlcommand,' AND OrderDate>=@p_OrderDateBegin ') IF(@p_OrderDateEnd IS NOT NULL) SET @sqlcommand=CONCAT(@sqlcommand,' AND OrderDate<=@p_OrderDateEnd ') SET @Parm='@p_OrderNumber int,
@p_CustomerId varchar(20),
@p_OrderDateBegin datetime,
@p_OrderDateEnd datetime '
PRINT @sqlcommand
EXEC sp_executesql @sqlcommand,@Parm, @p_OrderNumber=@p_OrderNumber, @p_CustomerId=@p_CustomerId, @p_OrderDateBegin=@p_OrderDateBegin, @p_OrderDateEnd=@p_OrderDateEnd
end
首先我们用同样的参数来执行一下查询,当然没问题,结果跟上面是一样的。
所谓的参数化SQL,就是用变量当做占位符,通过 EXEC sp_executesql执行的时候将参数传递进去SQL中,在需要填入数值或数据的地方,使用参数 (Parameter) 来给值,
这样的话,
第一,既能避免第一种写法中的SQL注入问题(包括转移符的处理),
因为参数是运行时传递进去SQL的,而不是编译时传递进去的,传递的参数是什么就按照什么执行,参数本身不参与编译
第二,保证执行计划的重用,因为使用占位符来拼凑SQL的,SQL参数的值不同并导致最终执行的SQL文本不同
同上面,参数本身不参与编译,如果查询条件一样(SQL语句就一样),而参数不一样,并不会影响要编译的SQL文本信息
第三,还有就是避免了第二种情况(and (@p_CustomerId is null or CustomerId=@p_CustomerId)
或者 and OrderNumber=ISNULL( @p_OrderNumber,OrderNumber))
这种写法,查询条件有就是有,没有就是没有,不会丢给SQL查询引擎一个模棱两个的结果,
避免了对索引的抑制行为,是一种比较好的处理查询条件的方式。
缺点,对于这种方式,也有一点不好的地方,就是拼凑的字符串处理过程中,
调试具体的SQL语句的时候,参数是直接拼凑在SQL文本中的,不能直接执行,要手动将占位参数替换成具体的参数值
总结:
以上总结了三种在开发中比较常见的存储过程的写法,每种存储过程的写法可能在不同的公司都用应用,
是不是有人挑个最简单最快捷(第二种)写法,写完不是完事了,而是埋雷了。
不是太熟悉SQL Server的同学可能会有点迷茫,有很多种写法,究竟要用哪种写法这些写法之间有什么区别。
本文通过一个简单的示例,说了常见的几种写法之间的区别,每种方式存在的问题,以及孰优孰劣,请小伙伴们明辨。
数据库大神请无视,谢谢。
dbForge Studio 2019-2022 for SQL Server是针对SQL Server数据库而开发的一款强大的集成开发环境,主要用于SQL Server管理,管理,开发,数据报告和分析。可以帮助SQL开发人员管理数据库,在流行的源代码控制系统中进行版本控制的数据库更改,加快日常任务的速度以及进行复杂的数据库更改。
在 SQL 编辑器中编写代码需要花费大量时间,而您可以通过以下方式提高工作效率:
● 代码自动完成 — 通过几个按键即可创建完整的 SQL 语句。
● SQL 格式化 — 遵循常见的编码风格(标准)。
● T-SQL 分析器 — 使用可配置的规则提高代码质量。
● SQL 代码片段 — 存储和重用重复代码片段,节省时间。
● 快速对象信息 — 在数据库对象上显示提示,提供相关信息。
● 代码导航 — 快速跳转到变量声明、对象编辑器等地方,按 F12 键即可。
● 表是任何数据库中的关键对象,也是最难配置的对象之一。精心设计的 SQL 表设计器允许您:在可视化编辑器中快速设置表属性。
● 编辑创建表的脚本。
● 当引入复杂更改时重新构建表。
● 在修改 SQL 数据库对象之前预览更改。
● 允许你在不编写代码的情况下可视化、创建和编辑 SQL Server 数据库。将数据库拖放到图表上,即可清晰显示其基本对象及其关联。数据库设计器功能包括:数据库图表和缩放。
● 打印大型图表。
● 虚拟连接。
● 可视化数据库编辑器。
● 尝试一下模式比较工具,你将会在数据库结构上的任何重大更改中使用此工具。我们的工具将帮助您:同步具有复杂对象依赖关系的数据库模式。
● 提前检测开发错误。
● 创建模式快照以捕捉数据库结构。
● 检测生产数据库上的偏移。
● 创建针对不同 SQL Server 版本的部署脚本。
● 一个方便的用于文档化 SQL Server 数据库的工具。文档生成工具允许您:获取数据库架构概览
● 包括自描述的 SQL 对象
● 查看对象间和数据库间的依赖关系
● 自定义文档样式
● 生成 HTML 和 PDF 格式的文档
支持 10 多种常用数据格式,提供多种高级选项,针对重复场景提供模板,支持可定制的常规命令行导入和导出。
将源代码控制系统整合到您的数据库开发和部署流程中。通过 dbForge Studio,SQL 数据库的版本控制变得顺畅而无缝。该功能允许用户:
● 将数据库链接到最流行的源代码控制系统
● 对工作文件夹进行源代码控制
● 在方便的界面中可视化执行所有源代码控制任务
● 跟踪变更历史记录
● 解决冲突
● 这是一个用于监视和审查 SQL Server 性能和活动的出色工具。通过这个工具,分析和管理 SQL Server 上的任何变化变得更加轻松和舒适。该工具允许用户:监视 SQL Server 和数据库活动,如 CPU 和内存工作负载、死锁、读/写和 IO 延迟、等待任务、批处理请求等等
● 获取与数据输入/输出相关的统计信息
● 查看数据库指标
● 使用等待统计信息分析与 SQL Server 和查询相关的资源
● 按照消耗时间对最复杂和耗时的查询进行排序
● 接收有关活动用户连接的宝贵信息
● 监视存储位置、大小和备份日期
用于分析 SQL 索引的状态并解决索引碎片化问题。索引管理器允许您快速收集索引碎片化统计信息,检测需要维护的数据库。您可以在可视化模式下立即重建和重组 SQL 索引,或生成 SQL 脚本以供将来使用。
作为服务器端逻辑的必备 SQL 数据库开发工具。它集成在存储过程编辑器中。通过单击数据库资源管理器树开始调试。
一款直观且便捷的工具,用于实现自动化单元测试。该工具基于开源的 tSQLt 框架,因此 SQL 开发人员可以从在普通 T-SQL 中编写单元测试中受益。单元测试丰富的功能使得开发稳定可靠的代码成为可能,可以在单元级别进行适当的回归测试。
下载安装包,双击安装程序,打开第一个安装界面
点击安装后进入安装目录选择,这里选择D盘。
然后进入文件关联的选择,大家可以根据自己的需要进行选择。
接着安装模式进行选择,这里按照推荐选择第一项
安装过程如下图
安装本地镜像过程如下图:
安装成功界面如下:
四、使用说明
打开软件,首先创建数据库链接
数据库属性配置如下图:
点击测试提示成功
打开数据库,可以展示表、视图等的数量
点击编辑表,打开数据表设计器界面
数据表索引管理界面
切换到Data标签页查询数据表数据
可以自定义查询条件,如下图:
查询条件会展示在查询界面下方
数据库新增age列,联动sql直接变化
数据导出支持格式有十多种,基本满足需求
导出成功界面如下
查看导出数据
查询效果如下:
软件获取:私信回复【数据库工具】获取
、SQL Server2016安装
1、处理器、内存和操作系统要求:
内存 | 最小值: Express 版本:512 MB 所有其他版本:1 GB 建议: Express 版本:1 GB 所有其他版本:至少 4 GB 并且应该随着数据库大小的增加而增加,以便确保最佳的性能。 |
处理器速度 | 最低要求:x64 处理器:1.4 GHz 建议:2.0 GHz 或更快 |
处理器类型 | x64 处理器:AMD Opteron、AMD Athlon 64、支持 Intel EM64T 的 Intel Xeon、支持 EM64T 的 Intel Pentium IV |
SQL Server Enterprise | Windows Server 2012、 Windows Server 2012 R2 |
SQL Server Business Intelligence | Windows Server 2012、 Windows Server 2012 R2 |
SQL Server Standard SQL Server Developer SQL Server Express | Windows 8、Windows 8.1 Windows 10 Windows Server 2012、 Windows Server 2012 R2 |
SQL Server Web | Windows Server 2012、 Windows Server 2012 R2 |
2、SQL Server2016下载地址:
https://www.microsoft.com/zh-cn/evalcenter/evaluate-sql-server-2016
3、安装需要的组件:
(1)NET Framework
默认在安装sqlserver2016时会自动安装
下载地址:https://support.microsoft.com/zh-cn/kb/3045560
(2)Oracle JRE7
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
作者:shy
*请认真填写需求信息,我们会在24小时内与您取得联系。