整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

SQL Server 存储过程的几种常见写法分析

SQL Server 存储过程的几种常见写法分析

文出处: 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开发人员管理数据库,在流行的源代码控制系统中进行版本控制的数据库更改,加快日常任务的速度以及进行复杂的数据库更改。

二、功能介绍

2.1 SQL 编码辅助

在 SQL 编辑器中编写代码需要花费大量时间,而您可以通过以下方式提高工作效率:

代码自动完成 — 通过几个按键即可创建完整的 SQL 语句。

SQL 格式化 — 遵循常见的编码风格(标准)。

T-SQL 分析器 — 使用可配置的规则提高代码质量。

SQL 代码片段 — 存储和重用重复代码片段,节省时间。

快速对象信息 — 在数据库对象上显示提示,提供相关信息。

代码导航 — 快速跳转到变量声明、对象编辑器等地方,按 F12 键即可。

2.2 表设计器

● 表是任何数据库中的关键对象,也是最难配置的对象之一。精心设计的 SQL 表设计器允许您:在可视化编辑器中快速设置表属性。

● 编辑创建表的脚本。

● 当引入复杂更改时重新构建表。

● 在修改 SQL 数据库对象之前预览更改。

2.3 数据库设计器

● 允许你在不编写代码的情况下可视化、创建和编辑 SQL Server 数据库。将数据库拖放到图表上,即可清晰显示其基本对象及其关联。数据库设计器功能包括:数据库图表和缩放。

● 打印大型图表。

● 虚拟连接。

● 可视化数据库编辑器。

2.4 模式比较

● 尝试一下模式比较工具,你将会在数据库结构上的任何重大更改中使用此工具。我们的工具将帮助您:同步具有复杂对象依赖关系的数据库模式。

● 提前检测开发错误。

● 创建模式快照以捕捉数据库结构。

● 检测生产数据库上的偏移。

● 创建针对不同 SQL Server 版本的部署脚本。

2.5 文档生成工具

● 一个方便的用于文档化 SQL Server 数据库的工具。文档生成工具允许您:获取数据库架构概览

● 包括自描述的 SQL 对象

● 查看对象间和数据库间的依赖关系

● 自定义文档样式

● 生成 HTML 和 PDF 格式的文档

2.6 数据导出和数据导入

支持 10 多种常用数据格式,提供多种高级选项,针对重复场景提供模板,支持可定制的常规命令行导入和导出。

2.7 源代码控制

将源代码控制系统整合到您的数据库开发和部署流程中。通过 dbForge Studio,SQL 数据库的版本控制变得顺畅而无缝。该功能允许用户:

将数据库链接到最流行的源代码控制系统

对工作文件夹进行源代码控制

在方便的界面中可视化执行所有源代码控制任务

跟踪变更历史记录

解决冲突

2.8 监控工具

● 这是一个用于监视和审查 SQL Server 性能和活动的出色工具。通过这个工具,分析和管理 SQL Server 上的任何变化变得更加轻松和舒适。该工具允许用户:监视 SQL Server 和数据库活动,如 CPU 和内存工作负载、死锁、读/写和 IO 延迟、等待任务、批处理请求等等

● 获取与数据输入/输出相关的统计信息

● 查看数据库指标

● 使用等待统计信息分析与 SQL Server 和查询相关的资源

● 按照消耗时间对最复杂和耗时的查询进行排序

● 接收有关活动用户连接的宝贵信息

● 监视存储位置、大小和备份日期

2.9 索引管理器

用于分析 SQL 索引的状态并解决索引碎片化问题。索引管理器允许您快速收集索引碎片化统计信息,检测需要维护的数据库。您可以在可视化模式下立即重建和重组 SQL 索引,或生成 SQL 脚本以供将来使用。

2.10 T-SQL 调试器

作为服务器端逻辑的必备 SQL 数据库开发工具。它集成在存储过程编辑器中。通过单击数据库资源管理器树开始调试。

2.11 单元测试

一款直观且便捷的工具,用于实现自动化单元测试。该工具基于开源的 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