Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >SQL Server 存储过程的几种常见写法分析

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

作者头像
逸鹏
发布于 2018-04-11 03:12:47
发布于 2018-04-11 03:12:47
1.5K00
代码可运行
举报
文章被收录于专栏:逸鹏说道逸鹏说道
运行总次数:0
代码可运行

最近发现还有不少做开发的小伙伴,在写存储过程的时候,在参考已有的不同的写法时,往往很迷茫, 不知道各种写法孰优孰劣,该选用那种写法,以及各种写法优缺点,本文以一个简单的查询存储过程为例,简单说一下各种写法的区别,以及该用那种写法 专业DBA以及熟悉数据库的同学请无视。

废话不多,上代码说明,先造一个测试表待用,简单说明一下这个表的情况

类似订单表,订单表有订单ID,客户ID,订单创建时间等,查询条件是常用的订单ID,客户ID,以及订单创建时间

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
create table SaleOrder
(
    id       int identity(1,1),
    OrderNumber  int         ,
    CustomerId   varchar(20)      ,
    OrderDate    datetime         ,
    Remark       varchar(200)
)
GO
declare @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的方式执行这个拼凑出来的字符串,不推荐

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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这段时间内的订单信息,如下,带入参数执行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
exec pr_getOrederInfo_1    @p_OrderNumber      = null      ,    @p_CustomerId       = 'C88'     ,    @p_OrderDateBegin   = '2016-10-1' ,    @p_OrderDateEnd     = '2016-10-3'

  首先说明,这种方式执行查询是完全没有问题的如下截图,结果也查出来了(当然结果也是没问题的)

我们把执行的SQL打印出来,执行的SQL语句本身就是就是存储过程中拼凑出来的字符串,这么一个查询SQL字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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就变成了这个样子了,语法就不通过,更别提执行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
          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条件中,非常不推荐

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,有可能是一个具体的值

实话说,为什么抑制到到索引的使用,我之前也是没有弄清楚的,评论中10楼Uest 给出了解释,这里非常感谢Uest

      SQL Server为了保险起见,采用了全表扫描的方式,举个简单的例子

      如果我直接带入CustomerId=‘C88’,再来看执行计划,结果跟上面一样,但是执行计划是完全不一样的,这就是所谓的抑制到索引的使用。

   2,非常非常致命的逻辑错误

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        /*
            这是另外一种类似的奇葩的写法,需要重点关注,真的就能满足“不管参数是否为空都满足”
            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,推荐

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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查询引擎一个模棱两个的结果,    避免了对索引的抑制行为,是一种比较好的处理查询条件的方式。

缺点,1,对于这种方式,也有一点不好的地方,就是拼凑的字符串处理过程中,     调试具体的SQL语句的时候,参数是直接拼凑在SQL文本中的,不能直接执行,要手动将占位参数替换成具体的参数值

  2,可能存在parameter sniff问题,但是对于parameter sniff问题,不是否定参数化SQL的重点,当然解决parameter sniff问题的办法还是有的,

       参考:http://www.cnblogs.com/wy123/p/5645485.html

总结:

  以上总结了三种在开发中比较常见的存储过程的写法,每种存储过程的写法可能在不同的公司都用应用,   是不是有人挑个最简单最快捷(第二种)写法,写完不是完事了,而是埋雷了。   不是太熟悉SQL Server的同学可能会有点迷茫,有很多种写法,究竟要用哪种写法这些写法之间有什么区别。   本文通过一个简单的示例,说了常见的几种写法之间的区别,每种方式存在的问题,以及孰优孰劣,请小伙伴们明辨。   数据库大神请无视,谢谢。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我为Net狂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
ASP.NET 存储过程操作
存储过程是存放在数据库服务器上的预先编译好的sql语句。使用存储过程,可以直接在数据库中存储并运行功能强大的任务。存储过程在第一应用程序执行时进行语法检查和编译,编译好的版本保存在高速缓存中。在执行重复任务时,存储过程可以提高性能和一致性。由于存储过程可以将一系列对数据库的操作放在数据库服务器上执行,因而可以降低Web服务器的负载,提高整个系统的性能。
跟着阿笨一起玩NET
2018/09/19
1.2K0
MSSQL之二十一 存储过程案例
张哥编程
2024/12/17
990
Sql Server 存储过程分页
  在企业级项目开发中,分页查询,获取某一类数据的List列表,这一功能是最普遍也是最重要的功能。其做法有很多种,例如ORM中自定义分页查询,一般情况下是拼接强类型的查询条件,然后转换成sql语句,查出出分页结果。在ORM转换过程中会稍微损失性能,效率会降低。对于百万级以上的大数据量,要求查询界面显示速度快,此时手动写存储过程,并且在存储过程中分页是最佳选择。下面给出具体的示例与说明:
张传宁IT讲堂
2019/09/17
1.6K0
SQLSERVER 存储过程 语法
*****************************************************
全栈程序员站长
2022/09/13
2.7K0
SQL Server中的sp_executesql系统存储过程
{, [@params =] N’@parameter_name data_type [,…n]’ }
全栈程序员站长
2022/08/31
1.9K0
《MSSQL2008技术内幕:T-SQL语言基础》读书笔记(下)
  所谓透视(Pivoting)就是把数据从行的状态旋转为列的状态的处理。其处理步骤为:
Edison Zhou
2018/08/20
9K0
《MSSQL2008技术内幕:T-SQL语言基础》读书笔记(下)
一步一步学Linq to sql(五):存储过程
首先在查询分析器运行下面的代码来创建一个存储过程sp_singleresultset。然后打开IDE的服务器资源管理器,我们从存储过程中找到刚才创建的存储过程,然后拖动到设计视图。在方法面板中可以看到已经创建了一个sp_singleresultset的方法,如下图:
aehyok
2018/09/11
4430
一步一步学Linq to sql(五):存储过程
SQLSERVER存储过程语法详解
@parameter 过程中的参数。在 Create PROCEDURE 语句中可以声明一个或多个参数。用户必须在执行过程时提供每个所声明参数的值(除非定义了该参数的默认值)。
全栈程序员站长
2022/09/13
1.7K0
sql server 与mysql的区别_sql server的优缺点
最近在自学jsp,这就少不了和数据库打交道啊,相信大家对SQLserver和MySQL不陌生吧。 在视频上老师用的是sqlserver数据库,但是我用的时候却是mysql数据库,可真的是吃了不少的苦头啊 。 直接上代码吧
全栈程序员站长
2022/11/09
2.5K0
sql server 与mysql的区别_sql server的优缺点
Sql Server 存储过程使用技巧
Copy下面的代码,然后新建查询,就可以写sql语句,执行完后,一个你自己的存储过程就建立好了!
跟着阿笨一起玩NET
2018/09/19
1.3K0
那些年我们写过的T-SQL(上篇)
在当今这个多种不同数据库混用,各种不同语言不同框架融合的年代(一切为了降低成本并高效的提供服务),知识点多如牛毛。虽然大部分SQL脚本可以使用标准SQL来写,但在实际中,效率就是一切,因而每种不同厂商的SQL新特性有时还是会用到,这部分内容更是让人抓瞎,常常会由于一些很简单的问题花很久来搜索准确答案。赶脚俺弱小的智力已经完全无法记清楚常见的命令了,即使是用的最熟悉的T-SQL(SQL Server)。因此将最常见的T-SQL操作做个简单的总结,包括一些容易忽视的知识点和常见的开发样例。实话实说,现在开发中较
用户1216676
2018/01/24
3.3K0
那些年我们写过的T-SQL(上篇)
SQL知识整理一:触发器、存储过程、表变量、临时表
说明:   1 tr_name :触发器名称   2 on table/view :触发器所作用的表。一个触发器只能作用于一个表   3 for 和after :同义   4 after 与instead of :sql 2000新增项目afrer 与 instead of 的区别     After       在触发事件发生以后才被激活,只可以建立在表上     Instead of       代替了相应的触发事件而被执行,既可以建立在表上也可以建立在视图上   5 insert、update、delete:激活触发器的三种操作,可以同时执行,也可选其一   6 if update (col_name):表明所作的操作对指定列是否有影响,有影响,则激活触发器。此外,因为delete 操作只对行有影响, 所以如果使用delete操作就不能用这条语句了(虽然使用也不出错,但是不能激活触发器,没意义)。   7 触发器执行时用到的两个特殊表:deleted ,inserted     deleted 和inserted 可以说是一种特殊的临时表,是在进行激活触发器时由系统自动生成的,其结构与触发器作用的表结构是一样的,只是存放 的数据有差异。   8 说明deleted 与inserted 数据的差异     deleted 与inserted 数据的差异     Inserted 存放进行insert和update 操作后的数据     Deleted 存放进行delete 和update操作前的数据     注意:update 操作相当于先进行delete 再进行insert ,所以在进行update操作时,修改前的数据拷贝一条到deleted 表中,修改后的数据在存到触发器作用的表的同时,也同时生成一条拷贝到insered表中
全栈程序员站长
2022/07/05
1K0
SQL Server-命令速查-CheatSheet
然后字符串部分可以写各种格式## String Replace 以下示例使用 xxx 替换 abcdefghi 中的字符串 cde。
szhshp
2022/08/15
9730
sp_executesql介绍和使用
execute相信大家都用的用熟了,简写为exec,除了用来执行存储过程,一般都用来执行动态Sql
全栈程序员站长
2022/08/25
1.2K0
SQL Server数据库存储过程中拼接字符串注意的问题
  在SQL Server数据库中书写复杂的存储过程时,一般的做法是拼接字符串,最后使用EXEC sp_executesql '拼接的字符串' 查询出结果。
张传宁IT讲堂
2019/09/17
2.4K0
SQL Server数据库存储过程中拼接字符串注意的问题
使用VS.NET2003编写存储过程
上述代码不符合要求的原因有以下几个。首先,如果将 SQL 查询语句嵌套在代码中,那么只要数据层发生任何变化,都必须编辑并重新编译代码层。这样就会带来诸多不便。还可能会导致其他错误,而且通常会造成数据服务和代码之间的混乱。
Java架构师必看
2021/03/22
2.3K0
StoredProcedure — 存储过程
DECLARE @F001 SMALLINT ,           @F002 INTEGER ,           @F003 VARCHAR ( 20 ), @F004 CHAR ( 20 ),           @F002 MONEY 2.赋值语句
全栈程序员站长
2022/09/18
3790
mysql存储过程实例_sql存储过程创建实例详解
我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。
全栈程序员站长
2022/09/27
2.4K0
SQL参数化查询
一个简单理解参数化查询的方式是把它看做只是一个T-SQL查询,它接受控制这个查询返回什么的参数。通过使用不同的参数,一个参数化查询返回不同的结果。要获得一个参数化查询,你需要以一种特定的方式来编写你的代码,或它需要满足一组特定的标准。 有两种不同的方式来创建参数化查询。第一个方式是让查询优化器自动地参数化你的查询。另一个方式是通过以一个特定方式来编写你的T-SQL代码,并将它传递给sp_executesql系统存储过程,从而编程一个参数化查询。 这样的解释还是有点模糊,先看一例:
全栈程序员站长
2022/08/31
2.4K0
SQL Server 百万数据查询优化技巧三十则
互联网时代的进程越走越深,使用MySQL的人也越来越多,关于MySQL的数据库优化指南很多,而关于SQL SERVER的T-SQL优化指南看上去比较少,近期有学习SQLSERVER的同学问到SQL SERVER数据库有哪些优化建议?本文列举了部分常见的优化建议,具体内容如下:
俊才
2023/11/24
1.5K0
SQL Server 百万数据查询优化技巧三十则
相关推荐
ASP.NET 存储过程操作
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验