アヒル、タむ、およびT-SQL ...たたはSQL Serverを䜿甚する際にプログラマヌに䜕を朜たせるこずができたすか


それはすべお平凡に始たった...リヒタヌによっお読たれ、集䞭的にシルトを研究した。 私は.NETの開発に携わるず思ったが、運呜は仕事の最初の1か月で別の方法で決定した。 埓業員の1人が予期せずプロゞェクトを去り、新しく圢成された穎に圌らは新鮮な人間の玠材を投げたした。 それから、 SQL Serverに぀いお知り合いになりたした 。

それ以来、6幎匱が経過し、倚くのこずを思い出すこずができたす...

タむでの䌑暇䞭に人生を再考したむギリスからのゞョセフの前のクラむアントに぀いお、ゞョセフィンはスカむプで私に眲名し始めたした。 同じ郚屋に座っおいなければならなかった陜気なオフィスの隣人に぀いお1぀は新鮮な空気に察するアレルギヌに苊しみ、もう1぀は日光に察するアレルギヌで補完するC ++ぞの片思いの愛に苊しんでいたした。 か぀お、䞊からの呜什で、私はスキルで草に芆われたJSを装うために、䞀時的に2人の子䟛の父アレクサンダヌにならなければなりたせんでした。

しかし、最も激しいゎミは、おそらくゎム補のアヒルの鳎き声の物語に関係しおいたす。 ある同僚は圌女をストレスから解攟し、か぀おは感情にふけっお頭をかじった。 それ以来、アヒルは以前の光沢を倱い、すぐにボヌルに取っお代わられたした。

なぜこれが蚀われたのですか デヌタベヌスの操䜜に人生を捧げたい堎合、最初に孊ぶ必芁があるのは...ストレス耐性です。 2぀目は、初心者開発者の倚くが知らない、たたは単に無芖するT-SQLク゚リを䜜成するずきにいく぀かのルヌルを採甚し、座っお頭を悩たすこずです...なぜ機胜しないのですか

内容


1. デヌタ型
2. *
3. ゚むリアス
4. 列の順序
5. NOT IN vs NULL
6. 日付圢匏
7. 日付フィルタヌ
8. 蚈算
9. 暗黙の倉換
10. LIKEおよび抑制されたむンデックス
11. UnicodeずANSI
12. コレヌト
13. バむナリコレヌト
14. コヌドスタむル
15. [var] char
16. デヌタ長
17. ISNULL察COALESCE
18. æ•°å­Š
19. UNION vs UNION ALL
20. 再読み蟌み
21. サブク゚リ
22. ケヌス
23. スカラヌ関数
24. ビュヌ
25. カヌ゜ル
26. STRING_CONCAT
27. SQLむンゞェクション
䟋付きのビデオ

1.デヌタ型


SQL Serverで䜜業するずきにほずんどの問題を匕き起こす最も基本的なこずは、デヌタ型の遞択の誀りです。 2぀の基本的に同䞀のテヌブルを䜿甚した架空の䟋を考えたす

DECLARE @Employees1 TABLE ( EmployeeID BIGINT PRIMARY KEY , IsMale VARCHAR(3) , BirthDate VARCHAR(20) ) INSERT INTO @Employees1 VALUES (123, 'YES', '2012-09-01') 

 DECLARE @Employees2 TABLE ( EmployeeID INT PRIMARY KEY , IsMale BIT , BirthDate DATE ) INSERT INTO @Employees2 VALUES (123, 1, '2012-09-01') 

リク゚ストを実行しお、違いを芋おみたしょう。

 DECLARE @BirthDate DATE = '2012-09-01' SELECT * FROM @Employees1 WHERE BirthDate = @BirthDate SELECT * FROM @Employees2 WHERE BirthDate = @BirthDate 


前者の堎合、デヌタ型は必芁以䞊に冗長です。 なぜビット蚘号をYES / NO文字列ずしお保存するのですか なぜ日付を文字列ずしお保存するのですか 埓業員がいるテヌブルでBIGINTを䜿甚する理由 単玔なINTが収たらなかったのですか

これにはいく぀かの理由がありたす。テヌブルがより倚くのディスク領域を占有するため、ディスクからより倚くのペヌゞを読み蟌む必芁があり、このデヌタを凊理するためにBufferPoolにより倚くのペヌゞを配眮する必芁がある さらに、深刻なパフォヌマンスの問題がある可胜性がありたす-疑問笊はこれを簡単に瀺唆したすが、これに぀いおは埌で説明したす。

2. *


倚くの堎合、「油絵」を満たす必芁がありたした。すべおのデヌタはテヌブルから取埗され、その埌、実際に必芁な列のみがDataReaderを介しおクラむアントで遞択されたす。 これは非垞に非効率的であるため、このプラクティスを䜿甚しないこずをお勧めしたす。

 USE AdventureWorks2014 GO SET STATISTICS TIME, IO ON SELECT * FROM Person.Person SELECT BusinessEntityID , FirstName , MiddleName , LastName FROM Person.Person SET STATISTICS TIME, IO OFF 

違いは、ク゚リ実行の時間ず、カバヌするむンデックスにより論理的な読み取りを少なくするこずができるずいう事実の䞡方にありたす。

 Table 'Person'. Scan count 1, logical reads 3819, physical reads 3, ... SQL Server Execution Times: CPU time = 31 ms, elapsed time = 1235 ms. Table 'Person'. Scan count 1, logical reads 109, physical reads 1, ... SQL Server Execution Times: CPU time = 0 ms, elapsed time = 227 ms. 

3.゚むリアス


テヌブルを䜜成したす。

 USE AdventureWorks2014 GO IF OBJECT_ID('Sales.UserCurrency') IS NOT NULL DROP TABLE Sales.UserCurrency GO CREATE TABLE Sales.UserCurrency ( CurrencyCode NCHAR(3) PRIMARY KEY ) INSERT INTO Sales.UserCurrency VALUES ('USD') 

䞡方のテヌブルにある同䞀の行の数を返すク゚リがあるずしたす

 SELECT COUNT_BIG(*) FROM Sales.Currency WHERE CurrencyCode IN ( SELECT CurrencyCode FROM Sales.UserCurrency ) 

予想どおり、テヌブルSales.UserCurrencyの列の名前を倉曎するたで、すべおが機胜したす。

 EXEC sys.sp_rename 'Sales.UserCurrency.CurrencyCode', 'Code', 'COLUMN' 

ク゚リを実行するず、返されるのは1行ではなく、すべおがSales.Currencyにあるこずがわかりたす。 実行プランを構築するずき、 SQL Serverはバむンド段階でSales.UserCurrency列を調べ、 そこで CurrencyCodeを芋぀けず、この列がSales.Currencyテヌブルに属しおいるずは考えたせん。その埌、オプティマむザヌはCurrencyCode = CurrencyCode条件を削陀したす。

道埳-゚むリアスを䜿甚

 SELECT COUNT_BIG(*) FROM Sales.Currency c WHERE c.CurrencyCode IN ( SELECT u.CurrencyCode FROM Sales.UserCurrency u ) 

4.列の順序


䜕らかの皮類のテヌブルがあるずしたす

 IF OBJECT_ID('dbo.DatePeriod') IS NOT NULL DROP TABLE dbo.DatePeriod GO CREATE TABLE dbo.DatePeriod ( StartDate DATE , EndDate DATE ) 

そしお、列がどのように順序付けられおいるかを知っおいるずいう仮定から、垞にデヌタを挿入したす。

 INSERT INTO dbo.DatePeriod SELECT '2015-01-01', '2015-01-31' 

その埌、誰かが列を䞊べ替えたす

 CREATE TABLE dbo.DatePeriod ( EndDate DATE , StartDate DATE ) 

そしお、デヌタはすでに開発者が期埅する間違った列に挿入されたす。 したがっお、 INSERT構造で列を明瀺的に指定するこずが垞に掚奚されたす。

 INSERT INTO dbo.DatePeriod (StartDate, EndDate) SELECT '2015-01-01', '2015-01-31' 

別の興味深い䟋がありたす

 SELECT TOP(1) * FROM dbo.DatePeriod ORDER BY 2 DESC 

どの列が゜ヌトされたすか そしお、それはすべおテヌブル内の珟圚の順序に䟝存したす。 誰かがそれを倉曎するず、リク゚ストは期埅したものを衚瀺したせん。

5. NOT IN vs NULL


ゞュニアDB開発者むンタビュヌの質問の䞭で議論の䜙地のないリヌダヌは、 NOT IN構造です。

たずえば、いく぀かのク゚リを䜜成する必芁がありたす。2番目のテヌブルにない最初のテヌブルからすべおのレコヌドを返したす。 非垞に倚くの堎合、初心者の開発者はわざわざINずNOT INを䜿甚したせん 。

 DECLARE @t1 TABLE (t1 INT, UNIQUE CLUSTERED(t1)) INSERT INTO @t1 VALUES (1), (2) DECLARE @t2 TABLE (t2 INT, UNIQUE CLUSTERED(t2)) INSERT INTO @t2 VALUES (1) SELECT * FROM @t1 WHERE t1 NOT IN (SELECT t2 FROM @t2) SELECT * FROM @t1 WHERE t1 IN (SELECT t2 FROM @t2) 

最初のク゚リはデュヌスを返し、2番目のク゚リはナニットを返したした。 次に、2番目のテヌブルに別の倀-NULLを远加したす 。

 INSERT INTO @t2 VALUES (1), (NULL) 

NOT INを䜿甚しおク゚リを実行するず、結果が埗られたせん。 本圓にある皮の魔法が介入したした-INは動䜜したすが、 NOT INは拒吊したす。 これは、比范操䜜䞭の3次ロゞックによっお導かれる、 TRUE 、 FALSE 、 UNKNOWNの SQL Serverを䜿甚する堎合に最初に「理解しお蚱す」こずです。

実行されるず、 SQL ServerはIN句を解釈したす 。

 a IN (1, NULL) == a=1 OR a=NULL 

入っおいない 

 a NOT IN (1, NULL) == a<>1 AND a<>NULL 

倀をNULLず比范するずUNKNOWNが返されたす。 1 = NULL 、 NULL = NULL 。 結果は1- UNKNOWNになりたす。 たた、条件でAND挔算子が䜿甚されおいるため、匏党䜓が未定矩の倀を返し、結果ずしお空になりたす。

少し退屈に曞かれおいたす。 しかし、そのような状況は十分に䞀般的であるこずを理解するこずが重芁です。 たずえば、列がNOT NULLずしお宣蚀される前に、ある皮の人がその列にNULL倀を曞き蟌むこずを蚱可したした。 結論クラむアントは、少なくずも1぀のNULL倀がテヌブルに入った埌にレポヌトの動䜜を停止したす。

どうする NULL倀を明瀺的に削陀できたす。

 SELECT * FROM @t1 WHERE t1 NOT IN ( SELECT t2 FROM @t2 WHERE t2 IS NOT NULL ) 

EXCEPTを䜿甚できたす

 SELECT * FROM @t1 EXCEPT SELECT * FROM @t2 

倚くのこずを考えたくない堎合は、 NOT EXISTSを䜿甚する方が簡単です。

 SELECT * FROM @t1 WHERE NOT EXISTS( SELECT 1 FROM @t2 WHERE t1 = t2 ) 

どのク゚リオプションが最適ですか NOT EXISTSを䜿甚した最埌のオプションは、2番目のテヌブルのデヌタにアクセスするずきに、より最適な述語プッシュダりンステヌトメントを生成するこずが望たしいようです。

䞀般に、 NULL倀では倚くの冗談がありたす。 次のリク゚ストで遊ぶこずができたす

 USE AdventureWorks2014 GO SELECT COUNT_BIG(*) FROM Production.Product SELECT COUNT_BIG(*) FROM Production.Product WHERE Color = 'Grey' SELECT COUNT_BIG(*) FROM Production.Product WHERE Color <> 'Grey' 

NULL倀に察しお個別の比范挔算子が提䟛されおいるずいう理由だけで、期埅される結果が埗られたせん。

 SELECT COUNT_BIG(*) FROM Production.Product WHERE Color IS NULL SELECT COUNT_BIG(*) FROM Production.Product WHERE Color IS NOT NULL 

CHECK定数のある状況はさらに奇劙に芋えたす

 IF OBJECT_ID('tempdb.dbo.#temp') IS NOT NULL DROP TABLE #temp GO CREATE TABLE #temp ( Color VARCHAR(15) --NULL , CONSTRAINT CK CHECK (Color IN ('Black', 'White')) ) 

癜ず黒の色のみを蚘録できるテヌブルを䜜成したす。

 INSERT INTO #temp VALUES ('Black') 

 (1 row(s) affected) 

すべおが期埅どおりに機胜したす。

 INSERT INTO #temp VALUES ('Red') 

 The INSERT statement conflicted with the CHECK constraint... The statement has been terminated. 

しかし、 NULLを挿入したしょう

 INSERT INTO #temp VALUES (NULL) 

 (1 row(s) affected) 

曞き蟌みにはNOT FALSE条件で十分なので、 CHECK定数は機胜したせんでした。 TRUEずUNKNOWNは甘い魂に適しおいたす。 この動䜜を回避するためのいく぀かのオプションがありたす。明瀺的に列をNOT NULLずしお宣蚀するか、制玄でNULLを考慮したす 。

6.日付圢匏


それでも、デヌタ型のさたざたなニュアンスに぀たずくこずがよくありたす。 たずえば、珟圚の時刻を取埗する必芁がありたす。 GETDATE関数を実行したす。

 SELECT GETDATE() 

結果をコピヌし、ク゚リにそのたた貌り付けお、時間を削陀したした。

 SELECT * FROM sys.objects WHERE create_date < '2016-11-14' 

これは正しいですか

日付は文字列定数で指定されたすが、 SQL Serverでは、曞き蟌み時にある皋床の自由床が蚱可されたす。

 SET LANGUAGE English SET DATEFORMAT DMY DECLARE @d1 DATETIME = '05/12/2016' , @d2 DATETIME = '2016/12/05' , @d3 DATETIME = '2016-12-05' , @d4 DATETIME = '05-dec-2016' SELECT @d1, @d2, @d3, @d4 

すべおの倀はほがどこでも明確に解釈されたす。

 ----------- ----------- ----------- ----------- 2016-12-05 2016-05-12 2016-05-12 2016-12-05 

そしお、このようなビゞネスロゞックのリク゚ストが別のサヌバヌで開始されるたで、問題は発生したせん。別のサヌバヌでは、蚭定が異なる堎合がありたす。

 SET DATEFORMAT MDY DECLARE @d1 DATETIME = '05/12/2016' , @d2 DATETIME = '2016/12/05' , @d3 DATETIME = '2016-12-05' , @d4 DATETIME = '05-dec-2016' SELECT @d1, @d2, @d3, @d4 

これらのオプションはすべお、日付の誀った解釈に぀ながる可胜性がありたす。

 ----------- ----------- ----------- ----------- 2016-05-12 2016-12-05 2016-12-05 2016-12-05 

さらに、そのようなコヌドは、明瀺的および非衚瀺の䞡方の゚ラヌに぀ながる可胜性がありたす。 たずえば、テヌブルにデヌタを挿入する必芁がありたす。 テストサヌバヌではすべお正垞に動䜜したす。

 DECLARE @t TABLE (a DATETIME) INSERT INTO @t VALUES ('05/13/2016') 

そしお、クラむアントは、サヌバヌ蚭定の違いにより、そのようなリク゚ストは問題に぀ながりたす

 DECLARE @t TABLE (a DATETIME) SET DATEFORMAT DMY INSERT INTO @t VALUES ('05/13/2016') 

 Msg 242, Level 16, State 3, Line 28 The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. 

では、日付の定数を蚭定する圢匏は䜕ですか 別の䟋を芋おみたしょう。

 SET DATEFORMAT YMD SET LANGUAGE English DECLARE @d1 DATETIME = '2016/01/12' , @d2 DATETIME = '2016-01-12' , @d3 DATETIME = '12-jan-2016' , @d4 DATETIME = '20160112' SELECT @d1, @d2, @d3, @d4 GO SET LANGUAGE Deutsch DECLARE @d1 DATETIME = '2016/01/12' , @d2 DATETIME = '2016-01-12' , @d3 DATETIME = '12-jan-2016' , @d4 DATETIME = '20160112' SELECT @d1, @d2, @d3, @d4 

むンストヌルされおいる蚀語に応じお、定数はさたざたな方法で解釈するこずもできたす。

 ----------- ----------- ----------- ----------- 2016-01-12 2016-01-12 2016-01-12 2016-01-12 ----------- ----------- ----------- ----------- 2016-12-01 2016-12-01 2016-01-12 2016-01-12 

そしお、結論は最埌の2぀のオプションを䜿甚するこずを芁求したす。 月を明瀺的に蚭定するこずは、「No crap sis jour」゚ラヌに遭遇する良い機䌚であるずすぐに蚀わなければなりたせん。

 SET LANGUAGE French DECLARE @d DATETIME = '12-jan-2016' 

 Msg 241, Level 16, State 1, Line 29 Échec de la conversion de la date et/ou de l'heure à partir d'une chaîne de caractÚres. 

合蚈-最埌のオプションが残りたす。 月の蚭定や䜍盞に関係なく、日付を含む定数をシステムで明確に解釈する堎合は、チルダ、匕甚笊、スラッシュなしでYYYYMMDD圢匏で指定したす。

たた、䞀郚のデヌタ型の動䜜の違いに泚意する䟡倀がありたす。

 SET LANGUAGE English SET DATEFORMAT YMD DECLARE @d1 DATE = '2016-01-12' , @d2 DATETIME = '2016-01-12' SELECT @d1, @d2 GO SET LANGUAGE Deutsch SET DATEFORMAT DMY DECLARE @d1 DATE = '2016-01-12' , @d2 DATETIME = '2016-01-12' SELECT @d1, @d2 

DATETIMEずは異なり、 DATEタむプはサヌバヌのさたざたな蚭定で正しく解釈されたす。

 ---------- ---------- 2016-01-12 2016-01-12 ---------- ---------- 2016-01-12 2016-12-01 

しかし、このニュアンスを念頭に眮く必芁がありたすか ほずんどない。 芚えおおくべき䞻なこずは、 YYYYMMDDの圢匏で日付を蚭定する必芁があり、問題がないこずです。

7.日付フィルタヌ


次に、デヌタを効率的にフィルタリングする方法を怜蚎したす。 䜕らかの理由で、 DATETIME / DATE列の束葉杖の数が最も倚いため、このデヌタ型から始めたす。

 USE AdventureWorks2014 GO UPDATE TOP(1) dbo.DatabaseLog SET PostTime = '20140716 12:12:12' 

次に、特定の日にク゚リが返す行数を調べおみたしょう。

 SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE PostTime = '20140716' 

芁求は0を返したす。なぜですか プランを䜜成する際、 SQL Serverは文字列定数をフィルタヌ凊理する列のデヌタ型に倉換しようずしたす 。


むンデックスを䜜成したす。

 CREATE NONCLUSTERED INDEX IX_PostTime ON dbo.DatabaseLog (PostTime) 

必芁なデヌタを衚瀺するための正しいオプションず間違ったオプションがありたす。 たずえば、切り取り時間

 SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE CONVERT(CHAR(8), PostTime, 112) = '20140716' SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE CAST(PostTime AS DATE) = '20140716' 

たたは、範囲を蚭定したす。

 SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE PostTime BETWEEN '20140716' AND '20140716 23:59:59.997' SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE PostTime >= '20140716' AND PostTime < '20140717' 

最適化に関しおより正確なのは、最埌の2぀のク゚リです。 実際、怜玢察象のむンデックス列でのすべおの倉換ず蚈算は、パフォヌマンスを倧幅に䜎䞋させ、論理読み取り倀を増やしたす最初ず最埌の3぀のク゚リオプション。

 Table 'DatabaseLog'. Scan count 1, logical reads 7, ... Table 'DatabaseLog'. Scan count 1, logical reads 2, ... 

PostTimeフィヌルドは以前はむンデックスに含たれおいなかったため、フィルタリング時に「正しい」アプロヌチを䜿甚しおも特別な効果は芋られたせんでした。 もう1぀は、1か月間のデヌタを衚瀺する必芁がある堎合です。 ただ芋る必芁がなかったもの

 SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE CONVERT(CHAR(8), PostTime, 112) LIKE '201407%' SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE DATEPART(YEAR, PostTime) = 2014 AND DATEPART(MONTH, PostTime) = 7 SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE YEAR(PostTime) = 2014 AND MONTH(PostTime) = 7 SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE EOMONTH(PostTime) = '20140731' SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE PostTime >= '20140701' AND PostTime < '20140801' 

繰り返したすが、最埌のオプションは他のすべおのオプションよりも受け入れられたす。


さらに、い぀でも蚈算フィヌルドを䜜成し、それに基づいおむンデックスを䜜成できたす。

 IF COL_LENGTH('dbo.DatabaseLog', 'MonthLastDay') IS NOT NULL ALTER TABLE dbo.DatabaseLog DROP COLUMN MonthLastDay GO ALTER TABLE dbo.DatabaseLog ADD MonthLastDay AS EOMONTH(PostTime) --PERSISTED GO CREATE INDEX IX_MonthLastDay ON dbo.DatabaseLog (MonthLastDay) 

前のク゚リず比范しお、論理読み取り倀の違いは重芁です倧きなテヌブルに぀いお話しおいる堎合。

 SET STATISTICS IO ON SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE PostTime >= '20140701' AND PostTime < '20140801' SELECT COUNT_BIG(*) FROM dbo.DatabaseLog WHERE MonthLastDay = '20140731' SET STATISTICS IO OFF 

 Table 'DatabaseLog'. Scan count 1, logical reads 7, ... Table 'DatabaseLog'. Scan count 1, logical reads 3, ... 

8.蚈算


先ほど蚀ったように、むンデックスフィヌルドの蚈算はパフォヌマンスを䜎䞋させ、論理読み取り倀の増加に぀ながりたす。

 USE AdventureWorks2014 GO SET STATISTICS IO ON SELECT BusinessEntityID FROM Person.Person WHERE BusinessEntityID * 2 = 10000 SELECT BusinessEntityID FROM Person.Person WHERE BusinessEntityID = 2500 * 2 SELECT BusinessEntityID FROM Person.Person WHERE BusinessEntityID = 5000 

 Table 'Person'. Scan count 1, logical reads 67, ... Table 'Person'. Scan count 0, logical reads 3, ... 

実行蚈画を芋るず、最初のケヌスでは、 SQL ServerはIndexScanを実行する必芁がありたす 。


2番目ず3番目のケヌスでは、むンデックスフィヌルドに蚈算がない堎合、 IndexSeekが衚瀺されたす 。


9.暗黙の倉換


最初に、同じ倀でフィルタリングするこれら2぀のク゚リを芋おください。

 USE AdventureWorks2014 GO SELECT BusinessEntityID, NationalIDNumber FROM HumanResources.Employee WHERE NationalIDNumber = 30845 SELECT BusinessEntityID, NationalIDNumber FROM HumanResources.Employee WHERE NationalIDNumber = '30845' 

実装蚈画を芋るず


前者の堎合-譊告ずIndexScan、埌者の堎合-IndexSeek 

 Table 'Employee'. Scan count 1, logical reads 4, ... Table 'Employee'. Scan count 0, logical reads 2, ... 

どうした NationalIDNumber列のデヌタ型はNVARCHAR15です。 INTずしおデヌタをフィルタヌ凊理する必芁がある倀で定数を枡すず、結果ずしお暗黙的な型倉換が行われ、パフォヌマンスが䜎䞋する可胜性がありたす。 これは、誰かが列のデヌタ型を倉曎したずきに頻繁に発生したすが、ク゚リは同じたたです。

ただし、パフォヌマンスの問題だけが埅っおいるわけではないこずを理解するこずが重芁です。 暗黙的な型倉換は、実行時゚ラヌに぀ながる可胜性がありたす。 たずえば、PostalCodeフィヌルドが数倀になる前に、郵䟿番号に文字が含たれおいる可胜性があるずいう指瀺が䞊から来たした。 デヌタ型は倉曎されたしたが、英数字の郵䟿番号が挿入されるずすぐに、叀いリク゚ストは機胜しなくなりたす。

 SELECT AddressID FROM Person.[Address] WHERE PostalCode = 92700 SELECT AddressID FROM Person.[Address] WHERE PostalCode = '92700' 

 Msg 245, Level 16, State 1, Line 16 Conversion failed when converting the nvarchar value 'K4B 1S2' to data type int. 

さらに興味深いこずに、プロゞェクトがEntityFrameworkを䜿甚する堎合、デフォルトですべおの文字列フィヌルドをUnicodeずしお解釈したす。

 SELECT CustomerID, AccountNumber FROM Sales.Customer WHERE AccountNumber = N'AW00000009' SELECT CustomerID, AccountNumber FROM Sales.Customer WHERE AccountNumber = 'AW00000009' 

その結果、最適なク゚リは生成されたせん。


問題の解決策は非垞に簡単です-比范䞭にデヌタ型が䞀臎するように制埡する必芁がありたす。

10. LIKEおよび抑制されたむンデックス


カバリングむンデックスがある堎合でも、それが効果的に䜿甚されるずいう事実ではありたせん。 たずえば、...で始たるすべおの行を印刷する必芁がありたす。

 USE AdventureWorks2014 GO SET STATISTICS IO ON SELECT AddressLine1 FROM Person.[Address] WHERE SUBSTRING(AddressLine1, 1, 3) = '100' SELECT AddressLine1 FROM Person.[Address] WHERE LEFT(AddressLine1, 3) = '100' SELECT AddressLine1 FROM Person.[Address] WHERE CAST(AddressLine1 AS CHAR(3)) = '100' SELECT AddressLine1 FROM Person.[Address] WHERE AddressLine1 LIKE '100%' 

次の論理読み取り倀を取埗したす。

 Table 'Address'. Scan count 1, logical reads 216, ... Table 'Address'. Scan count 1, logical reads 216, ... Table 'Address'. Scan count 1, logical reads 216, ... Table 'Address'. Scan count 1, logical reads 4, ... 

すぐに勝者を芋぀けるこずができる実装蚈画


結果は、私たちが長い間話し合っおきた結果です。 むンデックスが存圚する堎合、型や関数などの蚈算や倉換は行われたせん。 そうしお初めお、 SQL Serverによっお効率的に䜿甚されたす 。

しかし、文字列内の郚分文字列のすべおの出珟を芋぀ける必芁がある堎合はどうでしょうか このタスクは明らかに興味深いものです。

 SELECT AddressLine1 FROM Person.[Address] WHERE AddressLine1 LIKE '%100%' 

しかし、最初に、文字列ずそのプロパティに぀いお倚くの興味深いこずを孊ぶ必芁がありたす。

11. UnicodeずANSI


最初に芚えおおくべきこずは、文字列がUNICODEずANSIであるこずです。 前者の堎合、 NVARCHAR / NCHARデヌタ型が提䟛されたす文字あたり2バむト-残念ながら、 UTF8は配信されたせんでした。 ANSI文字列を栌玍するには-VARCHAR / CHAR 1バむト-1文字。 TEXT / NTEXTもありたすが、最初はそれらを忘れおおく方がよいでしょうそれらを䜿甚するずパフォヌマンスが倧幅に䜎䞋する可胜性があるため。

そしお、これは終了する可胜性がありたすが、いいえ...

ナニコヌド定数がリク゚ストで指定されおいる堎合、シンボルNの前にそれが必芁です。 違いを瀺すには、単玔なク゚リで十分です。

 SELECT '文本 ANSI' , N'文本 UNICODE' 

 ------- ------------ ?? ANSI 文本 UNICODE 

定数の前にNを指定しない堎合、 SQL ServerはANSI゚ンコヌディングで適切な文字を探したす。 芋぀からない堎合は、疑問笊に眮き換えたす。

12.コレヌト


ミドル/シニアDB開発者のポゞションの面接時に圌らが尋ねたいず思う非垞に興味深い䟋を思い出したした。 次のク゚リはデヌタを返したすか

 DECLARE @a NCHAR(1) = '' , @b NCHAR(1) = '' SELECT @a, @b WHERE @a = @b 

そしお、はい...そしおいいえ...ここに幞運がありたす。 私は通垞そのように答えたす。

なぜそんな曖昧な答えなのでしょうか たず、文字列定数にはNがないため、 ANSIずしお解釈されたす。 2぀目-珟圚のCOLLATEに倧きく䟝存したす。これは、文字列デヌタの䞊べ替えず比范のための䞀連のルヌルです。

 USE [master] GO IF DB_ID('test') IS NOT NULL BEGIN ALTER DATABASE test SET SINGLE_USER WITH ROLLBACK IMMEDIATE DROP DATABASE test END GO CREATE DATABASE test COLLATE Latin1_General_100_CI_AS GO USE test GO DECLARE @a NCHAR(1) = '' , @b NCHAR(1) = '' SELECT @a, @b WHERE @a = @b 

このCOLLATEでは、キリル文字の代わりに疑問笊が衚瀺されたす。疑問笊の文字が等しいためです。

 ---- ---- ? ? 

COLLATEを他のいく぀かに倉曎する必芁がありたす。

 ALTER DATABASE test COLLATE Cyrillic_General_100_CI_AS 

たた、キリル文字が正しく解釈されるため、ク゚リは䜕も返したせん。

したがっお、ここでの教蚓は単玔です。文字列定数がUNICODEを受け入れる必芁がある堎合、その前にNを眮くのを怠らないでください。 Nが可胜な限りスタックしおいる堎合、コむンの裏偎もあり、オプティマむザヌは型倉換を実行する必芁がありたす。

行に蚀及するこずを他に䜕を忘れたしたか 「レッツむンタビュヌ」サむクルからの別の良い質問

 DECLARE @a VARCHAR(10) = 'TEXT' , @b VARCHAR(10) = 'text' SELECT IIF(@a = @b, 'TRUE', 'FALSE') 

これらの線は等しいですか そしお、はい...そしおいいえ...再び私は答えたす。 明確な比范が必芁な堎合は、明瀺的にCOLLATEを指定する必芁がありたす。

 DECLARE @a VARCHAR(10) = 'TEXT' , @b VARCHAR(10) = 'text' SELECT IIF(@a COLLATE Latin1_General_CS_AS = @b COLLATE Latin1_General_CS_AS, 'TRUE', 'FALSE') 

COLLATEは、文字列を比范および゜ヌトするずきに、倧文字ず小文字を区別する CS か、倧文字ず小文字を区別しない CI こずができるためです。 クラむアントずテストベヌスの異なるCOLLATEは、ビゞネスロゞックの論理゚ラヌだけでなく朜圚的な原因です。

タヌゲットベヌスずtempdbの間のCOLLATEが䞀臎しない堎合、さらに楜しくなりたす 。 デフォルトずは異なるCOLLATEでベヌスを䜜成したしょう

 USE [master] GO IF DB_ID('test') IS NOT NULL BEGIN ALTER DATABASE test SET SINGLE_USER WITH ROLLBACK IMMEDIATE DROP DATABASE test END GO CREATE DATABASE test COLLATE Albanian_100_CS_AS GO USE test GO CREATE TABLE t (c CHAR(1)) INSERT INTO t VALUES ('a') GO IF OBJECT_ID('tempdb.dbo.#t1') IS NOT NULL DROP TABLE #t1 IF OBJECT_ID('tempdb.dbo.#t2') IS NOT NULL DROP TABLE #t2 IF OBJECT_ID('tempdb.dbo.#t3') IS NOT NULL DROP TABLE #t3 GO CREATE TABLE #t1 (c CHAR(1)) INSERT INTO #t1 VALUES ('a') CREATE TABLE #t2 (c CHAR(1) COLLATE database_default) INSERT INTO #t2 VALUES ('a') SELECT c = CAST('a' AS CHAR(1)) INTO #t3 DECLARE @t TABLE (c VARCHAR(100)) INSERT INTO @t VALUES ('a') SELECT 'tempdb', DATABASEPROPERTYEX('tempdb', 'collation') UNION ALL SELECT 'test', DATABASEPROPERTYEX(DB_NAME(), 'collation') UNION ALL SELECT 't', SQL_VARIANT_PROPERTY(c, 'collation') FROM t UNION ALL SELECT '#t1', SQL_VARIANT_PROPERTY(c, 'collation') FROM #t1 UNION ALL SELECT '#t2', SQL_VARIANT_PROPERTY(c, 'collation') FROM #t2 UNION ALL SELECT '#t3', SQL_VARIANT_PROPERTY(c, 'collation') FROM #t3 UNION ALL SELECT '@t', SQL_VARIANT_PROPERTY(c, 'collation') FROM @t 

テヌブルが䜜成されるず、 COLLATEはデヌタベヌスから継承されたす。 唯䞀の違いは、 COLLATEを指定せずに構造を明瀺的に定矩する最初の䞀時テヌブルです。 この堎合、 tempdbベヌスからCOLLATEを継承したす 。

 ------ -------------------------- tempdb Cyrillic_General_CI_AS test Albanian_100_CS_AS t Albanian_100_CS_AS #t1 Cyrillic_General_CI_AS #t2 Albanian_100_CS_AS #t3 Albanian_100_CS_AS @t Albanian_100_CS_AS 

COLLATEが䞀臎しない堎合、朜圚的な問題に぀ながる可胜性があるため、 t1を䜿甚した䟋に぀いお説明したす。

たずえば、 COLLATEでは倧文字ず小文字が区別されないため、デヌタは正しくフィルタリングされたせん。

 SELECT * FROM #t1 WHERE c = 'A' 

たたは、 SQL Serverは、 COLLATEが異なるためにテヌブルを結合できないこずを誓いたす 。

 SELECT * FROM #t1 JOIN t ON [#t1].c = tc 

埌者の䟋は非垞に䞀般的です。 テストサヌバヌではすべおが完璧であり、バックアップをクラむアントサヌバヌに展開するず、゚ラヌが発生したす。

 Msg 468, Level 16, State 9, Line 93 Cannot resolve the collation conflict between "Albanian_100_CS_AS" and "Cyrillic_General_CI_AS" in the equal to operation. 

それから、どこでも束葉杖を䜜らなければなりたせん

 SELECT * FROM #t1 JOIN t ON [#t1].c = tc COLLATE database_default 

13.バむナリコレヌト


軟膏のフラむが終わったので、 COLLATEをどのように掻甚できるかを芋おみたしょう。 文字列内の郚分文字列を芋぀ける䟋に぀いお芚えおいたすか

 SELECT AddressLine1 FROM Person.[Address] WHERE AddressLine1 LIKE '%100%' 

この芁求は倧幅に最適化され、実行時間が短瞮されたす。

しかし、違いを芋えるようにするには、倧きなテヌブルを生成する必芁がありたす。

 USE [master] GO IF DB_ID('test') IS NOT NULL BEGIN ALTER DATABASE test SET SINGLE_USER WITH ROLLBACK IMMEDIATE DROP DATABASE test END GO CREATE DATABASE test COLLATE Latin1_General_100_CS_AS GO ALTER DATABASE test MODIFY FILE (NAME = N'test', SIZE = 64MB) GO ALTER DATABASE test MODIFY FILE (NAME = N'test_log', SIZE = 64MB) GO USE test GO CREATE TABLE t ( ansi VARCHAR(100) NOT NULL , unicod NVARCHAR(100) NOT NULL ) GO ;WITH E1(N) AS ( SELECT * FROM ( VALUES (1),(1),(1),(1),(1), (1),(1),(1),(1),(1) ) t(N) ), E2(N) AS (SELECT 1 FROM E1 a, E1 b), E4(N) AS (SELECT 1 FROM E2 a, E2 b), E8(N) AS (SELECT 1 FROM E4 a, E4 b) INSERT INTO t SELECT v, v FROM ( SELECT TOP(50000) v = REPLACE(CAST(NEWID() AS VARCHAR(36)) + CAST(NEWID() AS VARCHAR(36)), '-', '') FROM E8 ) t 

むンデックスの䜜成を忘れずに、バむナリCOLLATEを䜿甚しお蚈算列を䜜成したす。

 ALTER TABLE t ADD ansi_bin AS UPPER(ansi) COLLATE Latin1_General_100_Bin2 ALTER TABLE t ADD unicod_bin AS UPPER(unicod) COLLATE Latin1_General_100_BIN2 CREATE NONCLUSTERED INDEX ansi ON t (ansi) CREATE NONCLUSTERED INDEX unicod ON t (unicod) CREATE NONCLUSTERED INDEX ansi_bin ON t (ansi_bin) CREATE NONCLUSTERED INDEX unicod_bin ON t (unicod_bin) 

フィルタリングを実行したす。

 SET STATISTICS TIME, IO ON SELECT COUNT_BIG(*) FROM t WHERE ansi LIKE '%AB%' SELECT COUNT_BIG(*) FROM t WHERE unicod LIKE '%AB%' SELECT COUNT_BIG(*) FROM t WHERE ansi_bin LIKE '%AB%' --COLLATE Latin1_General_100_BIN2 SELECT COUNT_BIG(*) FROM t WHERE unicod_bin LIKE '%AB%' --COLLATE Latin1_General_100_BIN2 SET STATISTICS TIME, IO OFF 

そしお、実行の結果を芋るこずができたす。これは嬉しい驚きです。

 SQL Server Execution Times: CPU time = 350 ms, elapsed time = 354 ms. SQL Server Execution Times: CPU time = 335 ms, elapsed time = 355 ms. SQL Server Execution Times: CPU time = 16 ms, elapsed time = 18 ms. SQL Server Execution Times: CPU time = 17 ms, elapsed time = 18 ms. 

党䜓のポむントは、バむナリ比范に基づいた怜玢がはるかに高速であり、行の出珟を迅速か぀迅速に怜玢する必芁がある堎合、デヌタはBINで終わるCOLLATEで栌玍できるこずです。芚えおおくべき唯䞀のこずは、比范する際に倧文字ず小文字を区別するすべおのバむナリCOLLATEです。

14.コヌドスタむル


コヌドの蚘述スタむルは厳密に個別ですが、開発に混乱をもたらさないために、誰もが特定のルヌルを長く守っおきたした。最も逆説的なこずは、ク゚リを䜜成するずきに、1぀のたずもなルヌルセットを芋たこずがないずいうこずです。それらのすべおは、「䞻なこずは働くこずです」ずいう原則に埓っお曞いおいたす。ただし、クラむアントサヌバヌにデヌタベヌスを展開する堎合、䌑憩を取るリスクがありたす。

別のデヌタベヌスずその䞭のテヌブルを䜜成したしょう

 USE [master] GO IF DB_ID('test') IS NOT NULL BEGIN ALTER DATABASE test SET SINGLE_USER WITH ROLLBACK IMMEDIATE DROP DATABASE test END GO CREATE DATABASE test COLLATE Latin1_General_CI_AS GO USE test GO CREATE TABLE dbo.Employee (EmployeeID INT PRIMARY KEY) 

次のク゚リを䜜成したす。

 select employeeid from employee 

動䜜したすかCOLLATEを倧文字ず小文字を区別しお倉曎しおみおください。

 ALTER DATABASE test COLLATE Latin1_General_CS_AI 

そしお、リク゚ストを再実行しおみおください

 Msg 208, Level 16, State 1, Line 19 Invalid object name 'employee'. 

オプティマむザヌは、実行プランを䜜成するずきに珟圚のCOLLATEルヌルを䜿甚したす。より正確には、バむンディング段階で、テヌブル、列、その他のオブゞェクトの存圚を確認し、構文ツリヌの各オブゞェクトをシステムカタログの実際のオブゞェクトず比范するずき。

どこでも動䜜するペンを䜿甚しおク゚リを生成する堎合は、ク゚リで䜿甚されるオブゞェクトの名前の正しいレゞスタに垞に準拠する必芁がありたす。

物事は倉数でさらにおもしろい...

それらのために、COLLATEはマスタヌベヌスから継承されたす。したがっお、倉数を操䜜するずきは正しいレゞスタヌに埓う必芁がありたす。

 SELECT DATABASEPROPERTYEX('master', 'collation') DECLARE @EmpID INT = 1 SELECT @empid 

その゚ラヌはほずんどの堎合そうではありたせん

 ----------------------- Cyrillic_General_CI_AS ----------- 1 

同時に、別のサヌバヌでは、登録の゚ラヌが感じられる堎合がありたす。

 -------------------------- Latin1_General_CS_AS 

 Msg 137, Level 15, State 2, Line 4 Must declare the scalar variable "@empid". 

15. [var] char


, ( CHAR , NCHAR ) ( VARCHAR , NVARCHAR ):

 DECLARE @a CHAR(20) = 'text' , @b VARCHAR(20) = 'text' SELECT LEN(@a) , LEN(@b) , DATALENGTH(@a) , DATALENGTH(@b) , '"' + @a + '"' , '"' + @b + '"' SELECT [a = b] = IIF(@a = @b, 'TRUE', 'FALSE') , [b = a] = IIF(@b = @a, 'TRUE', 'FALSE') , [a LIKE b] = IIF(@a LIKE @b, 'TRUE', 'FALSE') , [b LIKE a] = IIF(@b LIKE @a, 'TRUE', 'FALSE') 

20 , 4, SQL Server 16 ( LEN DATALENGTH -):

 --- --- ---- ---- ---------------------- ---------------------- 4 4 20 4 "text " "text" 

, — :

 a = bb = aa LIKE bb LIKE a ----- ----- -------- -------- TRUE TRUE TRUE FALSE 

LIKE :

 SELECT 1 WHERE 'a ' LIKE 'a' SELECT 1 WHERE 'a' LIKE 'a ' -- !!! SELECT 1 WHERE 'a' LIKE 'a' SELECT 1 WHERE 'a' LIKE 'a%' 

.

16. Data length


, :

 DECLARE @a DECIMAL , @b VARCHAR(10) = '0.1' , @c SQL_VARIANT SELECT @a = @b , @c = @a SELECT @a , @c , SQL_VARIANT_PROPERTY(@c,'BaseType') , SQL_VARIANT_PROPERTY(@c,'Precision') , SQL_VARIANT_PROPERTY(@c,'Scale') 

この問題の本質は䜕ですか型の次元を明瀺的に指定せず、小数倀の代わりに「敎数の䞊べ替え」を取埗したす。

 ---- ---- ---------- ----- ----- 0 0 decimal 18 0 

ただ面癜い文字列で

 DECLARE @t1 VARCHAR(MAX) = '123456789_123456789_123456789_123456789_' DECLARE @t2 VARCHAR = @t1 SELECT LEN(@t1) , @t1 , LEN(@t2) , @t2 , LEN(CONVERT(VARCHAR, @t1)) , LEN(CAST(@t1 AS VARCHAR)) 

次元が明瀺的に瀺されおいない堎合、文字列の長さは1文字になりたす。

 ----- ------------------------------------------ ---- ---- ---- ---- 40 123456789_123456789_123456789_123456789_ 1 1 30 30 

この堎合、型倉換の動䜜には独自の特性がありたす。CAST / CONVERTでディメンションを指定しなかった堎合、最初の30文字が䜿甚されたす。

17. ISNULL察COALESCE


他に朜圚的に興味深いものは䜕を瀺すこずができたすかISNULLずCOALESCEの 2぀の関数がありたす。䞀方では、すべおが単玔です。最初の挔算子がNULLの堎合、2番目の挔算子を返したす。COALESCEに぀いお話しおいる堎合は、次の挔算子を返したす。䞀方、2぀の間には陰湿な違いがありたす。

これらの関数は䜕を返したすか

 DECLARE @a CHAR(1) = NULL SELECT ISNULL(@a, 'NULL'), COALESCE(@a, 'NULL') DECLARE @i INT = NULL SELECT ISNULL(@i, 7.1), COALESCE(@i, 7.1) 

答えは本圓に明癜ではありたせん

 ---- ---- N NULL ---- ---- 7 7.1 

なんでISNULL関数は、2぀のオペランドの最小タむプに倉換したす。COALESCEは最倧の型に倉換されたす。だから私たちは、「䜕が間違っおいるのか」を理解しようずしお非垞に長い時間座ったのが初めおだ。

パフォヌマンスの芳点から、ISNULLは倚くのケヌスで少し速く動䜜したすが、COALESCEはCASE WHENステヌトメントで拡匵されたす。これに぀いおは以䞋で説明したす。

18.æ•°å­Š


SQL Serverで数孊に出くわすず、さらに興味深いものになりたす。違いはないようです

 SELECT 1 / 3 SELECT 1.0 / 3 

しかし実際には、違いがあるこずがわかりたす-それはすべお、リク゚ストに含たれるデヌタに䟝存したす。敎数の堎合、結果は敎数になりたす。

 ----------- 0 ----------- 0.333333 

面接でよく芋られる別の興味深い䟋

 SELECT COUNT(*) , COUNT(1) , COUNT(val) , COUNT(DISTINCT val) , SUM(val) , SUM(DISTINCT val) FROM ( VALUES (1), (2), (2), (NULL), (NULL) ) t (val) SELECT AVG(val) , SUM(val) / COUNT(val) , AVG(val * 1.) , AVG(CAST(val AS FLOAT)) FROM ( VALUES (1), (2), (2), (NULL), (NULL) ) t (val) 

䜕がリク゚ストを返したすかCOUNT*/ COUNT1は行の総数を返したす。COUNT列は、非NULL行の数を返したす。DISTINCTを远加するず、NULLでない䞀意の倀の数。

平均の蚈算でより興味深い。AVG操䜜は、オプティマむザヌによっおSUMずCOUNTに分解されたす。そしお、ここで䞊蚘の䟋を思い出しおください-平均を蚈算するずき、NULLは考慮されたせん。たた、倀が敎数の堎合、結果はどうなりたすか敎数。これはしばしば忘れられたす。

19. UNION vs UNION ALL


ここではすべおが単玔です。デヌタが亀差しないこずがわかっおおり、重耇を気にしない堎合は、パフォヌマンスの芳点から、UNION ALLを䜿甚するこずをお勧めしたす。重耇を削陀する必芁がある堎合は、UNIONを倧胆に䜿甚したす。

たずえば、重耇が間違いなくUNION ALLを䜿甚する方が適切でない堎合

 SELECT [object_id] FROM sys.system_objects UNION SELECT [object_id] FROM sys.objects SELECT [object_id] FROM sys.system_objects UNION ALL SELECT [object_id] FROM sys.objects 


2぀の構成芁玠の興味深い違いに぀いお知るこずは䟝然ずしお重芁です。UNIONステヌトメントは䞊列で実行され、UNION ALLは盎列で実行されたす。これは䞊列プランには圓おはたりたせん。最適化に圹立぀のは、たさにこのようなデヌタアクセスの機胜です。

異なる条件セットに基づいお1行を返す必芁があるずしたす。

 DECLARE @AddressLine NVARCHAR(60) SET @AddressLine = '4775 Kentucky Dr.' SELECT TOP(1) AddressID FROM Person.[Address] WHERE AddressLine1 = @AddressLine OR AddressLine2 = @AddressLine 

次に、条件でORを䜿甚しお、IndexScanを取埗したす。



 Table 'Address'. Scan count 1, logical reads 90, ... 

UNION ALLを䜿甚しおク゚リを曞き換えたす。

 SELECT TOP(1) AddressID FROM ( SELECT TOP(1) AddressID FROM Person.[Address] WHERE AddressLine1 = @AddressLine UNION ALL SELECT TOP(1) AddressID FROM Person.[Address] WHERE AddressLine2 = @AddressLine ) t 

SQL Serverは、最初のサブク゚リを実行した埌、結果を返すのに十分な1行が返されたこずを確認し、2番目の条件による怜玢を続行したせん。


 Table 'Worktable'. Scan count 0, logical reads 0, ... Table 'Address'. Scan count 1, logical reads 3, ... 

20.再読み蟌み


倚くの堎合、1぀のJOINの助けを借りおデヌタを匕き出すこずができる状況が発生したしたが、倚くのサブク゚リが芁求を誇っおいたした。

 USE AdventureWorks2014 GO SET STATISTICS IO ON SELECT e.BusinessEntityID , ( SELECT p.LastName FROM Person.Person p WHERE e.BusinessEntityID = p.BusinessEntityID ) , ( SELECT p.FirstName FROM Person.Person p WHERE e.BusinessEntityID = p.BusinessEntityID ) FROM HumanResources.Employee e SELECT e.BusinessEntityID , p.LastName , p.FirstName FROM HumanResources.Employee e JOIN Person.Person p ON e.BusinessEntityID = p.BusinessEntityID 

実際、テヌブルぞの䞍必芁なアクセスが少ないほど、論理的な枬定倀は少なくなりたす。

 Table 'Person'. Scan count 0, logical reads 1776, ... Table 'Employee'. Scan count 1, logical reads 2, ... Table 'Person'. Scan count 0, logical reads 888, ... Table 'Employee'. Scan count 1, logical reads 2, ... 

21.サブク゚リ


前の䟋は、テヌブル間の接続が1察1の堎合にのみ機胜するため、非垞に明らかになっおいたす。

テヌブルPerson.PersonずSales.SalesPersonQuotaHistoryの間にこのような関係が生じる前に、1人の埓業員に぀いお、クォヌタサむズごずに最倧1぀のレコヌドがあるこずが刀明したずしたす。

 USE AdventureWorks2014 GO SET STATISTICS IO ON SELECT p.BusinessEntityID , ( SELECT s.SalesQuota FROM Sales.SalesPersonQuotaHistory s WHERE s.BusinessEntityID = p.BusinessEntityID ) FROM Person.Person p 

クラむアントサヌバヌは異なる堎合があり、この芁求により次の゚ラヌが発生したす。

 Msg 512, Level 16, State 1, Line 6 Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression. 

このような問題はどのように解決されたすかTOP1ずORDER BYが

远加され、問題はなくなりたした。ただし、すべおが芋た目ほど単玔ではありたせん。TOP操䜜を䜿甚するず、オプティマむザヌはIndexSeekの䜿甚を匷制したす。TOPず䞀緒にOUTER / CROSS APPLYを䜿甚するず、同じ結果が生じたす。

 SELECT p.BusinessEntityID , ( SELECT TOP(1) s.SalesQuota FROM Sales.SalesPersonQuotaHistory s WHERE s.BusinessEntityID = p.BusinessEntityID ORDER BY s.QuotaDate DESC ) FROM Person.Person p SELECT p.BusinessEntityID , t.SalesQuota FROM Person.Person p OUTER APPLY ( SELECT TOP(1) s.SalesQuota FROM Sales.SalesPersonQuotaHistory s WHERE s.BusinessEntityID = p.BusinessEntityID ORDER BY s.QuotaDate DESC ) t 

それらが実行されるず、同じ問題が発生したす-耇数のIndexSeek操䜜


 Table 'SalesPersonQuotaHistory'. Scan count 19972, logical reads 39944, ... Table 'Person'. Scan count 1, logical reads 67, ... 

りィンドり関数で歊装しお、リク゚ストを曞き換えたす

 SELECT p.BusinessEntityID , t.SalesQuota FROM Person.Person p LEFT JOIN ( SELECT s.BusinessEntityID , s.SalesQuota , RowNum = ROW_NUMBER() OVER (PARTITION BY s.BusinessEntityID ORDER BY s.QuotaDate DESC) FROM Sales.SalesPersonQuotaHistory s ) t ON p.BusinessEntityID = t.BusinessEntityID AND t.RowNum = 1 

そしお、䜕が倉わったのか芋おみたしょう


 Table 'Person'. Scan count 1, logical reads 67, ... Table 'SalesPersonQuotaHistory'. Scan count 1, logical reads 4, ... 

22.ケヌス


この蚀語構​​成䜓に぀いお䜕が蚀えるでしょうかよく䜿甚され、あたり知られおいない機胜に぀いお知っおおく必芁がありたす。かかわらず、我々はオペレヌタ曞き方のCASEのを

 USE AdventureWorks2014 GO SELECT BusinessEntityID , Gender , Gender = CASE Gender WHEN 'M' THEN 'Male' WHEN 'F' THEN 'Female' ELSE 'Unknown' END FROM HumanResources.Employee 

SQL Serverは匏を次の圢匏に展開したす。

 SELECT BusinessEntityID , Gender , Gender = CASE WHEN Gender = 'M' THEN 'Male' WHEN Gender = 'F' THEN 'Female' ELSE 'Unknown' END FROM HumanResources.Employee 

これが䞻な問題です。各条件は、そのうちの1぀がTRUEを返すか、ELSEブロックに到達するたで順次実行されたす。

問題をより明確に瀺したしょう。これを行うには、郵送先䜏所の右偎を返すスカラヌ関数を䜜成したす。

 IF OBJECT_ID('dbo.GetMailUrl') IS NOT NULL DROP FUNCTION dbo.GetMailUrl GO CREATE FUNCTION dbo.GetMailUrl ( @Email NVARCHAR(50) ) RETURNS NVARCHAR(50) AS BEGIN RETURN SUBSTRING(@Email, CHARINDEX('@', @Email) + 1, LEN(@Email)) END 

SQL Profiler SQL:StmtStarting / SP:StmtCompleted ( XEvents : sp_statement_starting / sp_statement_completed ).

:

 SELECT TOP(10) EmailAddressID , EmailAddress , CASE dbo.GetMailUrl(EmailAddress) --WHEN 'microsoft.com' THEN 'Microsoft' WHEN 'adventure-works.com' THEN 'AdventureWorks' END FROM Person.EmailAddress 

10 . :

 SELECT TOP(10) EmailAddressID , EmailAddress , CASE dbo.GetMailUrl(EmailAddress) WHEN 'microsoft.com' THEN 'Microsoft' WHEN 'adventure-works.com' THEN 'AdventureWorks' END FROM Person.EmailAddress 

20 . , CASE . - . , CASE — .

:

 SELECT EmailAddressID , EmailAddress , CASE MailUrl WHEN 'microsoft.com' THEN 'Microsoft' WHEN 'adventure-works.com' THEN 'AdventureWorks' END FROM ( SELECT TOP(10) EmailAddressID , EmailAddress , MailUrl = dbo.GetMailUrl(EmailAddress) FROM Person.EmailAddress ) t 

関数は10回実行されたす。

さらに、CASEステヌトメントを重耇しおロヌドしないようにする必芁がありたす。

 SELECT DISTINCT CASE WHEN Gender = 'M' THEN 'Male' WHEN Gender = 'M' THEN '...' WHEN Gender = 'M' THEN '......' WHEN Gender = 'F' THEN 'Female' WHEN Gender = 'F' THEN '...' ELSE 'Unknown' END FROM HumanResources.Employee 

CASEの匏は順番に蚈算されたすが蚘述したずおりの順序で。堎合によっおは、このステヌトメントは集玄された関数を䜿甚しおSQL Serverによっお実行されたす。

 DECLARE @i INT = 1 SELECT CASE WHEN @i = 1 THEN 1 ELSE 1/0 END GO DECLARE @i INT = 1 SELECT CASE WHEN @i = 1 THEN 1 ELSE MIN(1/0) END 

23.スカラヌ関数


特にOOPが奜きな人- 倚数の行を操䜜するT-SQLク゚リではスカラヌ関数を䜿甚しないでください。

これは、スカラヌ関数の朜圚的な欠点に぀いおただ知らなかったずきに苊しんだ人生の䟋です。

 USE AdventureWorks2014 GO UPDATE TOP(1) Person.[Address] SET AddressLine2 = AddressLine1 GO IF OBJECT_ID('dbo.isEqual') IS NOT NULL DROP FUNCTION dbo.isEqual GO CREATE FUNCTION dbo.isEqual ( @val1 NVARCHAR(100), @val2 NVARCHAR(100) ) RETURNS BIT AS BEGIN RETURN CASE WHEN (@val1 IS NULL AND @val2 IS NULL) OR @val1 = @val2 THEN 1 ELSE 0 END END 

ク゚リは同䞀のデヌタを返したす

 SET STATISTICS TIME ON SELECT AddressID, AddressLine1, AddressLine2 FROM Person.[Address] WHERE dbo.IsEqual(AddressLine1, AddressLine2) = 1 SELECT AddressID, AddressLine1, AddressLine2 FROM Person.[Address] WHERE (AddressLine1 IS NULL AND AddressLine2 IS NULL) OR AddressLine1 = AddressLine2 SELECT AddressID, AddressLine1, AddressLine2 FROM Person.[Address] WHERE AddressLine1 = ISNULL(AddressLine2, '') SET STATISTICS TIME OFF 

しかし、スカラヌ関数の各呌び出しはリ゜ヌスを集䞭的に䜿甚するため、この違いが生じたす。

 SQL Server Execution Times: CPU time = 63 ms, elapsed time = 57 ms. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms. 

さらに、ク゚リでスカラヌ関数を䜿甚するず、SQL Serverが䞊列実行プランを䜜成できなくなりたす。これにより、倧量のデヌタがある堎合、パフォヌマンスが倧幅に䜎䞋する可胜性がありたす。

すべおの堎合においお、スカラヌ関数は悪ですかいや オプションを䜿甚しお関数を䜜成できたす SCHEMABINDINGを䜿甚しお、入力パラメヌタヌを䜿甚せずにたす。

 IF OBJECT_ID('dbo.GetPI') IS NOT NULL DROP FUNCTION dbo.GetPI GO CREATE FUNCTION dbo.GetPI () RETURNS FLOAT WITH SCHEMABINDING AS BEGIN RETURN PI() END GO SELECT dbo.GetPI() FROM Sales.Currency 

この堎合、関数は確定的であるず芋なされ、1回だけ実行されたす。

24.ビュヌ


誰かがパフォヌマンスを愛しおいたす...誰かは奜きではありたせん。ビュヌを䜿甚しないこずに぀いお意芋を述べる方が費甚はかかりたすが、ビュヌを操䜜する際にはいく぀かの機胜に぀いお知る必芁がありたす。

テストテヌブルずそれに基づいたビュヌを䜜成したす。

 IF OBJECT_ID('dbo.tbl', 'U') IS NOT NULL DROP TABLE dbo.tbl GO CREATE TABLE dbo.tbl (a INT, b INT) GO INSERT INTO dbo.tbl VALUES (0, 1) GO IF OBJECT_ID('dbo.vw_tbl', 'V') IS NOT NULL DROP VIEW dbo.vw_tbl GO CREATE VIEW dbo.vw_tbl AS SELECT * FROM dbo.tbl GO SELECT * FROM dbo.vw_tbl 

倀は正しく返されたす。

 ab ----------- ----------- 0 1 

次に、テヌブルに新しい列を远加し、ビュヌからデヌタの枛算を再詊行したす。

 ALTER TABLE dbo.tbl ADD c INT NOT NULL DEFAULT 2 GO SELECT * FROM dbo.vw_tbl 

同じ結果が埗られたす。

 ab ----------- ----------- 0 1 

そしお、すべお明瀺的に列を蚭定するか、スクリプトオブゞェクトを再コンパむルする必芁があるためです。

 EXEC sys.sp_refreshview @viewname = N'dbo.vw_tbl' GO SELECT * FROM dbo.vw_tbl 

正しい結果を埗るには

 abc ----------- ----------- ----------- 0 1 2 

テヌブルぞの盎接参照では、このような冗談はありたせん。

すべおのデヌタを結合し、すべおを1぀のビュヌにたずめるずいう1぀の芁求にアマチュアがいたす。䟋を挙げお説明するのではなく、AdventureWorksの「良いパタヌン」を芋おみたしょう。

 ALTER VIEW HumanResources.vEmployee AS SELECT e.BusinessEntityID , p.Title , p.FirstName , p.MiddleName , p.LastName , p.Suffix , e.JobTitle , pp.PhoneNumber , pnt.[Name] AS PhoneNumberType , ea.EmailAddress , p.EmailPromotion , a.AddressLine1 , a.AddressLine2 , a.City , sp.[Name] AS StateProvinceName , a.PostalCode , cr.[Name] AS CountryRegionName , p.AdditionalContactInfo FROM HumanResources.Employee e JOIN Person.Person p ON p.BusinessEntityID = e.BusinessEntityID JOIN Person.BusinessEntityAddress bea ON bea.BusinessEntityID = e.BusinessEntityID JOIN Person.[Address] a ON a.AddressID = bea.AddressID JOIN Person.StateProvince sp ON sp.StateProvinceID = a.StateProvinceID JOIN Person.CountryRegion cr ON cr.CountryRegionCode = sp.CountryRegionCode LEFT JOIN Person.PersonPhone pp ON pp.BusinessEntityID = p.BusinessEntityID LEFT JOIN Person.PhoneNumberType pnt ON pp.PhoneNumberTypeID = pnt.PhoneNumberTypeID LEFT JOIN Person.EmailAddress ea ON p.BusinessEntityID = ea.BusinessEntityID 

そしお今、質問は...すべおの情報ではなく、その䞀郚のみを取埗する必芁がある堎合はどうなりたすかたずえば、埓業員の名前ず姓を返したす。

 SELECT BusinessEntityID , FirstName , LastName FROM HumanResources.vEmployee SELECT p.BusinessEntityID , p.FirstName , p.LastName FROM Person.Person p WHERE p.BusinessEntityID IN ( SELECT e.BusinessEntityID FROM HumanResources.Employee e ) 

ビュヌを䜿甚する堎合の実行蚈画を芋おみたしょう。


 Table 'EmailAddress'. Scan count 290, logical reads 640, ... Table 'PersonPhone'. Scan count 290, logical reads 636, ... Table 'BusinessEntityAddress'. Scan count 290, logical reads 636, ... Table 'Person'. Scan count 0, logical reads 897, ... Table 'Employee'. Scan count 1, logical reads 2, ... 

そしお、ペンで有意矩に曞いたク゚リず比范したす。


 Table 'Person'. Scan count 0, logical reads 897, ... Table 'Employee'. Scan count 1, logical reads 2, ... 

オプティマむザヌ SQL Server非垞に巧劙に䜜成されおおり、実行プランを構築するずきに挔算子ツリヌを簡玠化する段階で、未䜿甚の接続を砎棄できたす。

ただし、圌は垞にこれを効率的に行うこずはできたせん。「接続が遞択結果に圱響するかどうか」を確認する方法がない堎合、テヌブル間に有効な倖郚キヌがないために劚げられるこずがありたす。たたは、たずえば、接続が耇数のフィヌルドを経由する堎合...たあ、オプティマむザヌはいく぀かのこずを知りたせんが、これは䞍必芁な䜜業でそれをロヌドする理由ではありたせん。

25.カヌ゜ル


SQL Serverを䜿甚する堎合、 1぀の真実を芚えおおいおください。カヌ゜ルを䜿甚しおデヌタを繰り返し倉曎しないでください。これはOracleではありたせん

倚くの堎合、このコヌドを芋぀けるこずができたす。

 DECLARE @BusinessEntityID INT DECLARE cur CURSOR FOR SELECT BusinessEntityID FROM HumanResources.Employee OPEN cur FETCH NEXT FROM cur INTO @BusinessEntityID WHILE @@FETCH_STATUS = 0 BEGIN UPDATE HumanResources.Employee SET VacationHours = 0 WHERE BusinessEntityID = @BusinessEntityID FETCH NEXT FROM cur INTO @BusinessEntityID END CLOSE cur DEALLOCATE cur 

このコヌドは次のように曞き換えるこずができたす。

 UPDATE HumanResources.Employee SET VacationHours = 0 WHERE VacationHours <> 0 

ランタむムず論理読み取りの数をもたらす䟡倀はありたせんが、信じおください、本圓に違いがありたす。オプションずしお、人生の最近の䟋に぀いおお話ししたす。2぀のネストされたカヌ゜ルが存圚するスクリプトを満たしたした。このコヌドを実行するず、クラむアントでタむムアりトが発生し、合蚈で玄38秒間実行されたした。リク゚ストから最初のカヌ゜ルを攟り出し、リク゚ストは600ミリ秒間実行され始めたした。2番目のカヌ゜ル-200ミリ秒を捚おたした。SQL Serverの

カヌ゜ル -悪

26. STRING_CONCAT


— , . ?

STRING_CONCAT , 
 2016 , , SQL Server . - ?

:

 IF OBJECT_ID('tempdb.dbo.#t') IS NOT NULL DROP TABLE #t GO CREATE TABLE #t (i CHAR(1)) INSERT INTO #t VALUES ('1'), ('2'), ('3') 

«» — :

 DECLARE @txt VARCHAR(50) = '' SELECT @txt += i FROM #t SELECT @txt 

 -------- 123 

, MS , , :

 DECLARE @txt VARCHAR(50) = '' SELECT @txt += i FROM #t ORDER BY LEN(i) SELECT @txt 

 -------- 3 

率盎に蚀っお、初めお、䌚蚈報告曞に最埌の行しか衚瀺されない理由を自分で考えたした。このゞョヌクの埌、CLR、UPDATE、䞀時テヌブル、再垰、ルヌプなど、さらに倚くのこずが行われたした。それが行の接着です。

実際には、90のケヌスで、XMLを䜿甚するだけで十分です。

 SELECT [text()] = i FROM #t FOR XML PATH('') 

 -------- 123 

ただし、ここではいく぀かのニュアンスが期埅できたす。最初に、すべおのデヌタではなく、䞀郚のデヌタのコンテキストで行を接着する必芁が非垞に頻繁にありたす。

 SELECT [name], STUFF(( SELECT ', ' + c.[name] FROM sys.columns c WHERE c.[object_id] = t.[object_id] FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') FROM sys.objects t WHERE t.[type] = 'U' 

 ------------------------ ------------------------------------ ScrapReason ScrapReasonID, Name, ModifiedDate Shift ShiftID, Name, StartTime, EndTime 

XMLメ゜ッドは構文解析に䜿甚しないようにするこずを匷くお勧めしたす。XMLメ゜ッドは非垞にリ゜ヌスを消費するためです。


䜎コストにするこずができたす

 SELECT [name], STUFF(( SELECT ', ' + c.[name] FROM sys.columns c WHERE c.[object_id] = t.[object_id] FOR XML PATH(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 2, '') FROM sys.objects t WHERE t.[type] = 'U' 

しかし、これは本質を根本的に倉えるものではありたせん。倀メ゜ッドを䜿甚しないようにしおください

 SELECT t.name , STUFF(( SELECT ', ' + c.name FROM sys.columns c WHERE c.[object_id] = t.[object_id] FOR XML PATH('')), 1, 2, '') FROM sys.objects t WHERE t.[type] = 'U' 


, «». :

 SELECT t.name , STUFF(( SELECT ', ' + CHAR(13) + c.name FROM sys.columns c WHERE c.[object_id] = t.[object_id] FOR XML PATH('')), 1, 2, '') FROM sys.objects t WHERE t.[type] = 'U' 

, , , .

: , value , value('(./text())[1]'... .

27. SQL Injection


sql injection , . :

 DECLARE @param VARCHAR(MAX) SET @param = 1 DECLARE @SQL NVARCHAR(MAX) SET @SQL = 'SELECT TOP(5) name FROM sys.objects WHERE schema_id = ' + @param PRINT @SQL EXEC (@SQL) 

:

 SELECT TOP(5) name FROM sys.objects WHERE schema_id = 1 

- :

 SET @param = '1; select ''hack''' 

:

 SELECT TOP(5) name FROM sys.objects WHERE schema_id = 1; select 'hack' 

, sql injection , - «». — :)

String.Format ( ), , sql injection :

 using (SqlConnection conn = new SqlConnection()) { conn.ConnectionString = @"Server=.;Database=AdventureWorks2014;Trusted_Connection=true"; conn.Open(); SqlCommand command = new SqlCommand( string.Format("SELECT TOP(5) name FROM sys.objects WHERE schema_id = {0}", value), conn); using (SqlDataReader reader = command.ExecuteReader()) { while (reader.Read()) {} } } 

, sp_executesql :

 DECLARE @param VARCHAR(MAX) SET @param = '1; select ''hack''' DECLARE @SQL NVARCHAR(MAX) SET @SQL = 'SELECT TOP(5) name FROM sys.objects WHERE schema_id = @schema_id' PRINT @SQL EXEC sys.sp_executesql @SQL , N'@schema_id INT' , @schema_id = @param 

, - .

:

 using (SqlConnection conn = new SqlConnection()) { conn.ConnectionString = @"Server=.;Database=AdventureWorks2014;Trusted_Connection=true"; conn.Open(); SqlCommand command = new SqlCommand( "SELECT TOP(5) name FROM sys.objects WHERE schema_id = @schema_id", conn); command.Parameters.Add(new SqlParameter("schema_id", value)); ... } 

, 


38


— 8- . , T-SQL . , .

, « » SQL Server , , - . .

この蚘事を英語圏の聎衆ず共有したい堎合
SQL Server: Useful Tips for Newbies

映像


, «» russianVC : . .

船倖に残っおいるものは䜕ですか


最初に、䞀時テヌブルずテヌブル倉数の違いに぀いお詳现に蚘述するこずを蚈画したす。最終的に、私はこれを1月に完了するのを埅っおいる別のポストに配眮するこずにしたした。

さらに、パラメヌタヌスニッフィングに぀いおすぐに話したいず思いたしたが、車茪を再発明せずに、Dmitry PilyuginSlowでアプリケヌションの速床が遅い、優れた投皿ぞのリンクを提䟛する方が良いでしょう。

質問、建蚭的な提案、合理的な批刀がある堎合は、プロファむル内のすべおの連絡先。

Source: https://habr.com/ru/post/J315142/


All Articles