存儲過程中執行動態Sql語句

来源:http://www.cnblogs.com/2719-feng/archive/2017/08/29/7448229.html
-Advertisement-
Play Games

在最近的項目中,有用到動態執行sql語句,SQL為我們提供了兩種動態執行SQL語句的命令,分別是EXEC和sp_executesql;通常,sp_executesql則更具有優勢,它提供了輸入輸出介面,而EXEC沒有。還有一個最大的好處就是利用sp_executesql,能夠重用執行計劃,這就大大提 ...


  在最近的項目中,有用到動態執行sql語句,SQL為我們提供了兩種動態執行SQL語句的命令,分別是EXEC和sp_executesql;通常,sp_executesql則更具有優勢,它提供了輸入輸出介面,而EXEC沒有。還有一個最大的好處就是利用sp_executesql,能夠重用執行計劃,這就大大提供了執行性能,還可以編寫更安全的代碼。EXEC在某些情況下會更靈活。除非您有令人信服的理由使用EXEC,否側儘量使用sp_executesql。

1.EXEC的使用

     EXEC命令有兩種用法,一種是執行一個存儲過程,另一種是執行一個動態的批處理。以下所講的都是第二種用法。

  下麵先使用EXEC演示一個例子,代碼1

1 DECLARE @TableName VARCHAR(50),@Sql NVARCHAR (MAX),@OrderID INT;
2 SET @TableName = 'Orders';
3 SET @OrderID = 10251;
4 SET @sql = 
5      'SELECT * FROM '+QUOTENAME(@TableName) +'WHERE OrderID = '+
6      CAST(@OrderID AS VARCHAR(10))+' ORDER BY ORDERID DESC'
7 EXEC(@sql);
View Code

註:這裡的EXEC括弧中只允許包含一個字元串變數,但是可以串聯多個變數,如果我們這樣寫EXEC:

  

EXEC('SELECT TOP('+ CAST(@TopCount AS VARCHAR(10)) +')* FROM '+            QUOTENAME(@TableName) +' ORDER BY ORDERID DESC'); 

      SQL編譯器就會報錯,編譯不通過,而如果我們這樣:

  

EXEC(@sql+@sql2+@sql3);

編譯器就會通過;
 
      所以最佳的做法是把代碼構造到一個變數中,然後再把該變數作為EXEC命令的輸入參數,這樣就不會受限制了。
 
      EXEC的缺點是不提供介面,這裡的介面是指,它不能執行一個包含一個帶變數符的批處理,如下:

1 DECLARE @TableName VARCHAR(50),@Sql NVARCHAR(MAX),@OrderID INT;
2 SET @TableName = 'Orders';
3 SET @OrderID = 10251;
4 SET @sql = 'SELECT * FROM '+QUOTENAME(@TableName) +
5      'WHERE OrderID = @OrderID ORDER BY ORDERID DESC'
6 EXEC(@sql);
View Code

關鍵就在SET @sql這一句話中,如果我們運行這個批處理,編譯器就會產生一下錯誤

  Msg 137, Level 15, State 2, Line 1 
  必須聲明標量變數 "@OrderID"。

      使用EXEC時,如果您想訪問變數,必須把變數內容串聯到動態構建的代碼字元串中,如:

SET @sql = 'SELECT * FROM '+QUOTENAME(@TableName) +    'WHERE OrderID = '+CAST(@OrderID AS VARCHAR(10))+' ORDER BY ORDERID DESC'

      串聯變數的內容也存在性能方面的弊端。SQL Server為每一個的查詢字元串創建新的執行計劃,即使查詢模式相同也是這樣。為演示這一點,先清空緩存中的執行計劃

  DBCC FREEPROCCACHE (這個不是本文所涉及的內容,您可以查看MS的MSDN)

      將代碼1運行3次,分別對@OrderID 賦予下麵3個值,10251,10252,10253。然後使用下麵的代碼查詢

SELECT cacheobjtype,objtype,usecounts,sql FROM sys.syscacheobjects WHERE sql NOT LIKE '%cach%' AND sql NOT LIKE '%sys.%' 

      點擊F5運行,我們可以看到,每執行一次都要產生一次的編譯,執行計劃沒有得到充分重用。


      EXEC除了不支持動態批處理中的輸入參數外,他也不支持輸出參數。預設情況下,EXEC把查詢的輸出返回給調用者。例如下麵代碼返回Orders表中所有的記錄數

1 DECLARE @sql NVARCHAR(MAX)
2 SET @sql = 'SELECT COUNT(ORDERID) FROM Orders';
3 EXEC(@sql);
View Code

 然而,如果你要把輸出返回給調用批處理中的變數,事情就沒有那麼簡單了。為此,你必須使用INSERT EXEC語法把輸出插入到一個目標表中,然後從這表中獲取值後賦給該變數,就像這樣:

1 DECLARE @sql NVARCHAR(MAX),@RecordCount INT
2 SET @sql = 'SELECT COUNT(ORDERID) FROM Orders';
3 CREATE TABLE #T(TID INT);
4 INSERT INTO #T EXEC(@sql);
5 SET @RecordCount = (SELECT TID FROM #T)
6 SELECT @RecordCount
7 DROP TABLE #T2
View Code

2.sp_executesql的使用

sp_executesql命令在SQL Server中引入的比EXEC命令晚一些,它主要為重用執行計劃提供更好的支持。

為了和EXEC作一個鮮明的對比,我們看看如果用代碼1的代碼,把EXEC換成sp_executesql,看看是否得到我們所期望的結果

1 DECLARE @TableName VARCHAR(50),@sql NVARCHAR(MAX),@OrderID INT ,@sql2 NVARCHAR(MAX);
2 SET @TableName = 'Orders ';
3 SET @OrderID = 10251;
4 SET @sql = 'SELECT * FROM '+QUOTENAME(@TableName) + ' WHERE OrderID = '+CAST(@OrderID AS VARCHAR(50)) + ' ORDER BY ORDERID DESC'
5 EXEC sp_executesql @sql
View Code

註意最後一行;事實證明可以運行;

sp_executesql提供介面

    sp_executesql命令比EXEC命令更靈活,因為它提供一個介面,該介面及支持輸入參數也支持輸出參數。這功能使你可以創建帶參數的查詢字元串,這樣就可以比EXEC更好的重用執行計劃,sp_executesql的構成與存儲過程非常相似,不同之處在於你是動態構建代碼。它的構成包括:代碼快,參數聲明部分,參數賦值部分。說了這麼多,還是看看它的語法:

複製代碼 EXEC sp_executesql

@stmt= <statement>,--類似存儲過程主體

@params = <params>, --類似存儲過程參數部分,聲明參數類型

<params assignment> --類似存儲過程調用,為參數賦值,參數值要和參數順序要一一對應,也可以通過為參數指明參數值的方式為其賦值

@stmt參數是輸入的動態批處理,它可以引入輸入參數或輸出參數,和存儲過程的主體語句一樣,只不過它是動態的,而存儲過程是靜態的,不過你也可以在存儲過程中使用sp_executesql;

 @params參數與定義輸入/輸出參數的存儲過程頭類似,實際上和存儲過程頭的語法完全一樣;

 @<params assignment> 與調用存儲過程的EXEC部分類似。

     其實@stmt,@params可以省略,那麼exec sp_executesql的語法就可以簡寫成如下格式:

複製代碼 EXEC sp_executesql
<statement>,
<params>,
<params assignment> 複製代碼

     

     為了說明sp_executesql對執行計劃的管理優於EXEC,我將使用前面討論EXEC時用到的代碼。

複製代碼 代碼 DECLARE @TableName VARCHAR(50),@sql NVARCHAR(MAX),@OrderID INT; 
SET @TableName = 'Orders ';
SET @OrderID = 10251; 
SET @sql = 'SELECT * FROM '+@TableName + ' WHERE OrderID = @OID ORDER BY ORDERID DESC' 
--註意當要對動態sql語句的表名實行參數化時,不可以如下表示:
--set @sql='select * from @TableName where OrderID=@OID ORDER BY Orderid desc',
--如果這樣會提示必須聲明標量變數@TableName,只可以如上面所寫的一樣,將表名@TableName作為變數名進行拼接

EXEC sp_executesql 
@sql, 
N'@OID int ', 
@OID = @OrderID 複製代碼

     下麵我們看看exec sp_executesql的執行效率,在調用該代碼和檢查它生成的執行計劃前,先清空緩存中的執行計劃;

  DBCC FREEPROCCACHE

     將上面的動態代碼執行3次,每次執行都賦予@OrderID 不同的值,然後查詢sys.syscacheobjects表,並註意它的輸出,優化器只創建了一個備用計劃,而且該計劃被重用的3次

SELECT cacheobjtype,objtype,usecounts,sql FROM sys.syscacheobjects   WHERE sql NOT LIKE '%cache%' AND sql NOT LIKE '%sys.%' AND sql NOT LIKE '%sp_executesql%'


     點擊F5運行。

     sq_executesql的另一個與其介面有關的強大功能是,你可以使用輸出參數為調用批處理中的變數返回值。利用該功能可以避免用臨時表返回數據,從而得到更高效的代碼和更少的重新編譯。定義和使用輸出參數的語法與存儲過程類似。也就是說,你需要在聲明參數時指定OUTPUT子句。例如,下麵的靜態代碼簡單的演示瞭如何從動態批處理中利用輸出參數@p把值返回到外部批處理中的變數@i.

複製代碼 DECLARE @sql AS NVARCHAR(12),@i AS INT;
SET @sql = N' SET @p = 10';
EXEC sp_executesql
@sql, 
N'@p AS INT OUTPUT',
@p = @i OUTPUT
SELECT @i --該代碼返回輸出10 複製代碼

  以字母 N 為首碼標識 Unicode 字元串常量

      總結以下幾點:
  一.使用exce sp_executesql效率比exec要高,同一類型的語句,只需編譯一次即可,而exec執行幾次就需要編譯幾次。
  二.構造動態sql的where子句,也就是條件子句時,exec無法使用變數來進行站位,需要將變數轉換成字元串,然後和動態sql進行拼接,這就可能引起Sql註入問題,如下:
    

SET @sql = 'SELECT * FROM '+QUOTENAME(@TableName) +    ' WHERE OrderID = '+CAST(@OrderID AS VARCHAR(50)) + ' ORDER BY ORDERID DESC'

        而若使用exec sp_executesql則可以使用變數來進行站位,以後再給這個參數傳值的放式構造動態sql,就避免的Sql註入的問題,如下:

SET @sql = 'SELECT * FROM '+@TableName + ' WHERE OrderID = @OID ORDER BY ORDERID DESC'


  三.無論是Exec還是Exec sp_executesql,如果想要將表名和列名進行動態參數化,不可以使用表名參數和列名參數來進行站位,而且表名參數和列名參數需要使用存儲過程的參數.對     於exec sp_executesql來說,不可以將表名參數和列名參數在指定為在exec sp_executesql參數聲明部分聲明的參數,如:

複製代碼 代碼 create PROCEDURE GetData
@tbName nvarchar(10),
@colName nvarchar(10),
@Name nvarchar(10)
AS
BEGIN
declare @sql nvarchar(50);
set @sql='select '+ @colName+' from ' +@tbName+ ' where name=@whereName';
--註意此句不可以寫成如下:
-- set @sql='select @colName from @tbName where name=@whereName';
exec sp_executesql 
@sql,
N'@whereName nvarchar(10)',
@Name
END 複製代碼

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 利用SQLite在android上創建資料庫 方法: 1、創建我們的資料庫類繼承SQLiteOpenHelper類 完成相關函數的重寫和資料庫對象的初始化 public MySQLiteOpenHelper(Context context,int version) super(context, "f ...
  • 1. 簡述 在實際開發中,常常需要進行不同應用程式之間的數據通信,例如讀取聯繫人列表等等,ContentProvider就是Android提供的用於實現不同進程之間進行數據通信的類。 ContentProvider的作用是對外提供對本應用的數據進行“增刪改查”的介面,而後在其它程式可通過Conten ...
  • 有時候在優化UI界面時,會惹怒用戶。那麼,如何有效的去避免呢,我們來一起看看有哪些優化UI界面的方法。 ...
  • 從github下載資料過程中,有些項目含有子模塊,有時通過git clone 或者下載zip方式項目可能會缺少文件,因此需要執行git submodule update --init --recursive 命令來將子模塊也下載到項目中 ...
  • 1.創建存儲過程 CREATE PROCEDURE updateGatwxLiveStatus() BEGIN UPDATE gatwxlive SET STATUS = '3'WHERE 1=1and `status`='1'and liveurl='404'; END;2.查看job是否已開 s ...
  • 原文地址: https://zhuanlan.zhihu.com/p/23309693 https://zhuanlan.zhihu.com/p/23293860 CTC:前向計算例子 這裡我們直接使用warp-ctc中的變數進行分析。我們定義T為RNN輸出的結果的維數,這個問題的最終輸出維度為al ...
  • 之前項目中需要寫一個sql,就是查出某個調研詳情中,選A答案,B答案,C答案...F答案的人各有多少人,這個sql也是費了很大的力氣才寫出來,故記下來,方便以後使用。 其中tbl_research_item為調研詳情表,tbl_user_research_item為用戶答題詳情表 由於每道調研的題目 ...
  • Myeclipse 新建數據源 一、打開myeclipse(打開了當我沒說) 二、在window選項中找到show view ,點擊other,輸入db,選擇DB Browser 三、在DB Browser 視窗 右鍵點擊 選擇new 四、出現下麵界面,填寫所需的信息 jia包下載地址:https: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...