SQL Server-聚焦深入理解动态SQL查询(三十二)
前言
之前有園友一直關(guān)注著我快點(diǎn)出SQL Server性能優(yōu)化系列,博主我也對(duì)性能優(yōu)化系列也有點(diǎn)小期待,本來(lái)打算利用周末寫死鎖以及避免死鎖系列的接著進(jìn)入SQL Server優(yōu)化系列,但是在工作中長(zhǎng)時(shí)間都是利用EF來(lái)操作SQL,不免對(duì)寫SQL語(yǔ)句有些生疏,在某些場(chǎng)景下還是只能利用底層的SQL語(yǔ)句或者寫存儲(chǔ)過(guò)程來(lái)實(shí)現(xiàn),很久沒(méi)寫存儲(chǔ)過(guò)程都忘記怎么寫了,所以本節(jié)穿插動(dòng)態(tài)SQL查詢的文章,別著急,博主說(shuō)過(guò)不會(huì)爛尾,博主再忙也會(huì)抽空將整個(gè)SQL Server系列梳理完畢,那樣的話,無(wú)論對(duì)初級(jí)還是中級(jí)者都可以從中受益匪淺,至少我是這么認(rèn)為,呵呵。
動(dòng)態(tài)SQL語(yǔ)句查詢
前前篇我們簡(jiǎn)短敘述了利用EXEC和EXECUTE來(lái)進(jìn)行動(dòng)態(tài)SQL語(yǔ)句查詢,并未深入去講解,借博主工作中重新回到寫原生SQL語(yǔ)句的機(jī)會(huì),我們?cè)賮?lái)回顧下動(dòng)態(tài)SQL語(yǔ)句查詢。既然是動(dòng)態(tài)SQL查詢,說(shuō)明在某些場(chǎng)景下利用硬編碼SQL語(yǔ)句查詢的方式是不可行的,比如查詢條件的不固定,這是最常見(jiàn)的情景,那么動(dòng)態(tài)SQL語(yǔ)句查詢有哪幾種方式呢?萬(wàn)變不離其宗,只有以下三種方式,請(qǐng)往下看。
參數(shù)化SQL語(yǔ)句動(dòng)態(tài)查詢
參數(shù)化SQL查詢是動(dòng)態(tài)SQL查詢中最簡(jiǎn)單的一種,因?yàn)槲覀冎恍枰獋鬟f參數(shù)即可,查詢條件是固定的,我們一起來(lái)溫故而知新。
USE AdventureWorks2012DECLARE @AccountNumber AS VARCHAR(200)SET @AccountNumber = 'AW00000002'SELECT StoreID, CustomerID, ModifiedDate, PersonID FROM Sales.Customer WHERE AccountNumber = @AccountNumberEXEC動(dòng)態(tài)SQL語(yǔ)句動(dòng)態(tài)查詢?
這個(gè)相對(duì)來(lái)說(shuō)比上一個(gè)要略微復(fù)雜一點(diǎn),對(duì)于復(fù)雜的SQL語(yǔ)句拼接,我們來(lái)看看。
USE TSQL2012DECLARE @shipcity AS VARCHAR(50) DECLARE @sqlCommand AS VARCHAR(500) DECLARE @columnList AS VARCHAR(200)SET @shipcity = 'Lyon' SET @columnList = 'orderid, custid, orderdate, shipname, shipaddress, shipcity' SET @sqlCommand = 'SELECT '+ @columnList + ' FROM Sales.Orders WHERE shipcity = '+ @shipcityEXEC(@sqlCommand)?
居然出錯(cuò)了,讓人始料未及,當(dāng)設(shè)置參數(shù)值時(shí)我們應(yīng)該將?SET @shipcity = 'Lyon'?進(jìn)行如下修改:
SET @shipcity = '''Lyon'''sp_executesql動(dòng)態(tài)SQL語(yǔ)句查詢(推薦)
USE TSQL2012DECLARE @shipcity AS VARCHAR(50) DECLARE @sqlCommand AS VARCHAR(500) DECLARE @columnList AS VARCHAR(200)SET @shipcity = 'Lyon' SET @columnList = 'orderid, custid, orderdate, shipname, shipaddress, shipcity' SET @sqlCommand = 'SELECT '+ @columnList + ' FROM Sales.Orders WHERE shipcity = @shipcity'EXECUTE sp_executesql @sqlCommand, N'@shipcity VARCHAR(50)', @shipcity = @shipcity之前從未遇到過(guò)這種情況,查此錯(cuò)誤居然發(fā)現(xiàn)要:sp_executesql執(zhí)行的SQL必須定義為NVARCHAR類型即必須定義為UNICODE,這里算是學(xué)習(xí)了。
對(duì)于利用sp_executesql來(lái)執(zhí)行動(dòng)態(tài)sql語(yǔ)句查詢的方式作為推薦最主要是因?yàn)槠湓诓樵儓?zhí)行計(jì)劃中,無(wú)論其變量值是否改變查詢計(jì)劃都會(huì)進(jìn)行重用,當(dāng)然還有其他好處,比如利用EXEC來(lái)執(zhí)行查詢,此時(shí)進(jìn)行拼接非常容易出錯(cuò)。同時(shí)在寫動(dòng)態(tài)SQL時(shí)個(gè)人一般推崇將查詢的列作為一個(gè)列表進(jìn)行定義,對(duì)于參數(shù)也進(jìn)行定義,這樣可維護(hù)性強(qiáng)不至于看起來(lái)亂糟糟的利于后續(xù)排查問(wèn)題。
講到這里是不是敘述完畢了呢,那就太沒(méi)意思了,相信看過(guò)博主所寫的內(nèi)容一般都是由淺入深,下面我們繼續(xù)往下看。
?一直講的動(dòng)態(tài)SQL語(yǔ)句查詢,你難道就沒(méi)有懷疑過(guò)僅僅只能進(jìn)行查詢,難道不能創(chuàng)建表或者創(chuàng)建視圖么,答案當(dāng)然是可以的。我們簡(jiǎn)單看下創(chuàng)建表。
Declare @SQL VarChar(1000) DECLARE @TableName AS VARCHAR(10)SET @TableName = 'Test'SELECT @SQL = 'Create Table ' + @TableName + '(' SELECT @SQL = @SQL + 'ID int NOT NULL Primary Key, FieldName VarChar(10))'Exec (@SQL)利用EXEC或者sp_executesql居然還能創(chuàng)建表,還能最高級(jí)一點(diǎn)么,答案當(dāng)然是可以,請(qǐng)繼續(xù)往下看。
當(dāng)我們?cè)诓皇钱?dāng)前打開(kāi)會(huì)話中的數(shù)據(jù)庫(kù)中去創(chuàng)建另外數(shù)據(jù)庫(kù)的視圖時(shí)可行不可行呢?
Create View AdventureWorks2012.dbo.Auths AS (SELECT StoreID, CustomerID, ModifiedDate, PersonIDFROM Sales.Customerl)此時(shí)創(chuàng)建視圖你會(huì)發(fā)現(xiàn)不能正常創(chuàng)建視圖,說(shuō)明創(chuàng)建視圖必須是在當(dāng)前會(huì)話指定的數(shù)據(jù)庫(kù)中才可以。
但是利用sp_executesql就能解決跨數(shù)據(jù)庫(kù)創(chuàng)建視圖的問(wèn)題,如下:
DECLARE @SQL NVarChar(1000)SELECT @SQL = 'Create View Auths AS (SELECT CustomerID, ModifiedDate FROM Sales.Customer)'EXECUTE AdventureWorks2012.dbo.sp_executesql @sql此時(shí)你會(huì)發(fā)現(xiàn)利用sp_executesql不僅可以重用查詢執(zhí)行計(jì)劃并且可以跨數(shù)據(jù)庫(kù)創(chuàng)建視圖,那么它難道就沒(méi)有限制么,答案當(dāng)然是有的,請(qǐng)繼續(xù)往下看。
存儲(chǔ)過(guò)程執(zhí)行動(dòng)態(tài)SQL語(yǔ)句查詢
當(dāng)在存儲(chǔ)過(guò)程中執(zhí)行動(dòng)態(tài)SQL語(yǔ)句查詢時(shí),此時(shí)動(dòng)態(tài)SQL語(yǔ)句是在當(dāng)前用戶的權(quán)限下進(jìn)行而不是調(diào)用存儲(chǔ)過(guò)程的用戶的權(quán)限,換句話說(shuō)如果我們對(duì)存儲(chǔ)過(guò)程上的表沒(méi)有權(quán)限,那么運(yùn)行存儲(chǔ)過(guò)程將出現(xiàn)問(wèn)題。還是不理解是什么意思么,也就是說(shuō)存儲(chǔ)過(guò)程運(yùn)行是有其作用域的。我們舉一個(gè)例子,我們可以利用如下語(yǔ)句來(lái)設(shè)置查詢的行數(shù)。
SET ROWCOUNT 3所以接下來(lái)我們利用上述語(yǔ)句來(lái)查詢返回的行數(shù)。
EXEC('SET ROWCOUNT 3')SELECT * FROM Sales.CustomersEXEC('SET ROWCOUNT 0')從上我們可以發(fā)現(xiàn)我們?cè)O(shè)置限制返回的行數(shù)根本不起作用,這是因?yàn)樵O(shè)置返回的行數(shù)已經(jīng)超出了查詢的作用域。所以我們必須將其設(shè)置限制行數(shù)放到EXEC中,如下:
EXEC('SET ROWCOUNT 3 SELECT * FROM Sales.Customers SET ROWCOUNT 0')WHERE 1 = 1問(wèn)題?
對(duì)于不確定多條件篩選利用WHERE 1 = 1是否會(huì)帶來(lái)性能問(wèn)題,但是除此之外似乎沒(méi)有什么好的辦法比利用WHERE 1 = 1來(lái)減少條件的判斷從而加長(zhǎng)SQL語(yǔ)句,可能會(huì)導(dǎo)致性能的下降。下面我們來(lái)看一個(gè)簡(jiǎn)單的例子
USE AdventureWorks2012 GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Sales].[GetSalesOrders]') AND type in (N'P', N'PC')) DROP PROCEDURE [Sales].[GetSalesOrders] GOCREATE PROCEDURE [Sales].[GetSalesOrders] (@CustomerID INT = NULL,@CreditCardID INT = NULL) AS SET NOCOUNT ON; DECLARE @SQL NVARCHAR(4000); DECLARE @ParameterDefinition NVARCHAR(4000);SELECT @ParameterDefinition = ' @CustomerParameter INT,@CreditCardIDParameter INT '; SELECT @SQL = N' SELECT [SalesOrderID], [OrderDate], [Status], [CustomerID], [CreditCardID] FROM [Sales].[SalesOrderHeader] WHERE 1 = 1 '; IF @CustomerID IS NOT NULLSELECT @SQL = @SQL + N' AND CustomerID = @CustomerParameter '; IF @CreditCardID IS NOT NULLSELECT @SQL = @SQL + N' AND CreditCardID = @CreditCardIDParameter '; EXECUTE sp_executesql@SQL,@ParameterDefinition,@CustomerParameter = @CustomerID,@CreditCardIDParameter = @CreditCardID; GOSET NOCOUNT OFF;再?gòu)?fù)雜也不過(guò)就是多表查詢和多條件篩選罷了,還是比較簡(jiǎn)單。得出如下結(jié)果。
上述利用存儲(chǔ)過(guò)程創(chuàng)建動(dòng)態(tài)SQL語(yǔ)句沒(méi)什么毛病。
那么問(wèn)題來(lái)了,要是如果我們想調(diào)試創(chuàng)建過(guò)程生成的SQL語(yǔ)句是否有沒(méi)有錯(cuò)誤,我們此時(shí)該如何做呢?為了利于調(diào)試我們可以將上述修改如下,添加一個(gè)debug標(biāo)識(shí)來(lái)打印SQL。
USE AdventureWorks2012 GO IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Sales].[GetSalesOrders]') AND type in (N'P', N'PC')) DROP PROCEDURE [Sales].[GetSalesOrders] GOCREATE PROCEDURE [Sales].[GetSalesOrders] (@CustomerID INT = NULL,@CreditCardID INT = NULL,@debug bit = 0) AS SET NOCOUNT ON; DECLARE @SQL NVARCHAR(4000); DECLARE @ParameterDefinition NVARCHAR(4000);SELECT @ParameterDefinition = ' @CustomerParameter INT,@CreditCardIDParameter INT '; SELECT @SQL = N' SELECT [SalesOrderID], [OrderDate], [Status], [CustomerID], [CreditCardID] FROM [Sales].[SalesOrderHeader] WHERE 1 = 1 '; IF @CustomerID IS NOT NULLSELECT @SQL = @SQL + N' AND CustomerID = @CustomerParameter '; IF @CreditCardID IS NOT NULLSELECT @SQL = @SQL + N' AND CreditCardID = @CreditCardIDParameter '; IF @debug = 1PRINT @SQLEXECUTE sp_executesql@SQL,@ParameterDefinition,@CustomerParameter = @CustomerID,@CreditCardIDParameter = @CreditCardID; GOEXEC [Sales].[GetSalesOrders] @debug = 1, @CustomerID = 29565SET NOCOUNT OFF;此時(shí)我們調(diào)試發(fā)現(xiàn)SQL語(yǔ)句沒(méi)有寫錯(cuò)并且生成的結(jié)果如下:
同時(shí)我們?cè)谙⒅锌梢钥吹缴傻腟QL語(yǔ)句如下,這樣能夠準(zhǔn)確無(wú)語(yǔ)的保證所寫動(dòng)態(tài)SQL沒(méi)有問(wèn)題。
總結(jié)
本節(jié)我們闡述了動(dòng)態(tài)SQL以及要注意的地方,同時(shí)為了便于調(diào)試保證所生成的SQL語(yǔ)句不會(huì)出現(xiàn)問(wèn)題給出的建議。下節(jié)我們開(kāi)始講死鎖問(wèn)題,歡迎大家持續(xù)關(guān)注博客和公眾號(hào),對(duì)于.NET Core也會(huì)持續(xù)更新。See u!
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的SQL Server-聚焦深入理解动态SQL查询(三十二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Count SIN Numbers
- 下一篇: Mysql表分区